MDL-24343 core: Deprecate zip_files
[moodle.git] / lib / odslib.class.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * ODS file writer.
19  * The xml used here is derived from output of LibreOffice 3.6.4
20  *
21  * The design is based on Excel writer abstraction by Eloy Lafuente and others.
22  *
23  * @package   core
24  * @copyright 2006 Petr Skoda {@link http://skodak.org}
25  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 defined('MOODLE_INTERNAL') || die();
31 /**
32  * ODS workbook abstraction.
33  *
34  * @package   core
35  * @copyright 2006 Petr Skoda {@link http://skodak.org}
36  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class MoodleODSWorkbook {
39     protected $worksheets = array();
40     protected $filename;
42     public function __construct($filename) {
43         $this->filename = $filename;
44     }
46     /**
47      * Create one Moodle Worksheet.
48      *
49      * @param string $name Name of the sheet
50      * @return MoodleODSWorksheet
51      */
52     public function add_worksheet($name = '') {
53         $ws = new MoodleODSWorksheet($name, $this->worksheets);
54         $this->worksheets[] = $ws;
55         return $ws;
56     }
58     /**
59      * Create one Moodle Format.
60      *
61      * @param array $properties array of properties [name]=value;
62      *                          valid names are set_XXXX existing
63      *                          functions without the set_ part
64      *                          i.e: [bold]=1 for set_bold(1)...Optional!
65      * @return MoodleODSFormat
66      */
67     public function add_format($properties = array()) {
68         return new MoodleODSFormat($properties);
69     }
71     /**
72      * Close the Moodle Workbook.
73      */
74     public function close() {
75         global $CFG;
76         require_once($CFG->libdir . '/filelib.php');
78         $writer = new MoodleODSWriter($this->worksheets);
79         $contents = $writer->get_file_content();
81         send_file($contents, $this->filename, 0, 0, true, true, $writer->get_ods_mimetype());
82     }
84     /**
85      * Not required to use.
86      * @param string $filename Name of the downloaded file
87      */
88     public function send($filename) {
89         $this->filename = $filename;
90     }
92 }
95 /**
96  * ODS Cell abstraction.
97  *
98  * @package   core
99  * @copyright 2013 Petr Skoda {@link http://skodak.org}
100  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
101  */
102 class MoodleODSCell {
103     public $value;
104     public $type;
105     public $format;
106     public $formula;
110 /**
111  * ODS Worksheet abstraction.
112  *
113  * @package   core
114  * @copyright 2006 Petr Skoda {@link http://skodak.org}
115  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
116  */
117 class MoodleODSWorksheet {
118     public $data = array();
119     public $columns = array();
120     public $rows = array();
121     public $showgrid = true;
122     public $name;
124     /**
125      * Constructs one Moodle Worksheet.
126      *
127      * @param string $name The name of the file
128      * @param array $worksheets existing worksheets
129      */
130     public function __construct($name, array $worksheets) {
131         // Replace any characters in the name that Excel cannot cope with.
132         $name = strtr($name, '[]*/\?:', '       ');
134         if ($name === '') {
135             // Name is required!
136             $name = 'Sheet'.(count($worksheets)+1);
137         }
139         $this->name = $name;
140     }
142     /**
143      * Write one string somewhere in the worksheet.
144      *
145      * @param integer $row    Zero indexed row
146      * @param integer $col    Zero indexed column
147      * @param string  $str    The string to write
148      * @param mixed   $format The XF format for the cell
149      */
150     public function write_string($row, $col, $str, $format = null) {
151         if (!isset($this->data[$row][$col])) {
152             $this->data[$row][$col] = new MoodleODSCell();
153         }
154         if (is_array($format)) {
155             $format = new MoodleODSFormat($format);
156         }
157         $this->data[$row][$col]->value = $str;
158         $this->data[$row][$col]->type = 'string';
159         $this->data[$row][$col]->format = $format;
160         $this->data[$row][$col]->formula = null;
161     }
163     /**
164      * Write one number somewhere in the worksheet.
165      *
166      * @param integer $row    Zero indexed row
167      * @param integer $col    Zero indexed column
168      * @param float   $num    The number to write
169      * @param mixed   $format The XF format for the cell
170      */
171     public function write_number($row, $col, $num, $format = null) {
172         if (!isset($this->data[$row][$col])) {
173             $this->data[$row][$col] = new MoodleODSCell();
174         }
175         if (is_array($format)) {
176             $format = new MoodleODSFormat($format);
177         }
178         $this->data[$row][$col]->value = $num;
179         $this->data[$row][$col]->type = 'float';
180         $this->data[$row][$col]->format = $format;
181         $this->data[$row][$col]->formula = null;
182     }
184     /**
185      * Write one url somewhere in the worksheet.
186      *
187      * @param integer $row    Zero indexed row
188      * @param integer $col    Zero indexed column
189      * @param string  $url    The url to write
190      * @param mixed   $format The XF format for the cell
191      */
192     public function write_url($row, $col, $url, $format = null) {
193         if (!isset($this->data[$row][$col])) {
194             $this->data[$row][$col] = new MoodleODSCell();
195         }
196         if (is_array($format)) {
197             $format = new MoodleODSFormat($format);
198         }
199         $this->data[$row][$col]->value = $url;
200         $this->data[$row][$col]->type = 'string';
201         $this->data[$row][$col]->format = $format;
202         $this->data[$row][$col]->formula = null;
203     }
205     /**
206      * Write one date somewhere in the worksheet.
207      *
208      * @param integer $row    Zero indexed row
209      * @param integer $col    Zero indexed column
210      * @param string  $date    The url to write
211      * @param mixed   $format The XF format for the cell
212      */
213     public function write_date($row, $col, $date, $format = null) {
214         if (!isset($this->data[$row][$col])) {
215             $this->data[$row][$col] = new MoodleODSCell();
216         }
217         if (is_array($format)) {
218             $format = new MoodleODSFormat($format);
219         }
220         $this->data[$row][$col]->value = $date;
221         $this->data[$row][$col]->type = 'date';
222         $this->data[$row][$col]->format = $format;
223         $this->data[$row][$col]->formula = null;
224     }
226     /**
227      * Write one formula somewhere in the worksheet.
228      *
229      * @param integer $row    Zero indexed row
230      * @param integer $col    Zero indexed column
231      * @param string  $formula The formula to write
232      * @param mixed   $format The XF format for the cell
233      */
234     public function write_formula($row, $col, $formula, $format = null) {
235         if (!isset($this->data[$row][$col])) {
236             $this->data[$row][$col] = new MoodleODSCell();
237         }
238         if (is_array($format)) {
239             $format = new MoodleODSFormat($format);
240         }
241         $this->data[$row][$col]->formula = $formula;
242         $this->data[$row][$col]->format = $format;
243         $this->data[$row][$col]->value = null;
244         $this->data[$row][$col]->format = null;
245     }
247     /**
248      * Write one blank somewhere in the worksheet.
249      *
250      * @param integer $row    Zero indexed row
251      * @param integer $col    Zero indexed column
252      * @param mixed   $format The XF format for the cell
253      */
254     public function write_blank($row, $col, $format = null) {
255         if (is_array($format)) {
256             $format = new MoodleODSFormat($format);
257         }
258         $this->write_string($row, $col, '', $format);
259     }
261     /**
262      * Write anything somewhere in the worksheet,
263      * type will be automatically detected.
264      *
265      * @param integer $row    Zero indexed row
266      * @param integer $col    Zero indexed column
267      * @param mixed   $token  What we are writing
268      * @param mixed   $format The XF format for the cell
269      */
270     public function write($row, $col, $token, $format = null) {
271         // Analyse what are we trying to send.
272         if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/", $token)) {
273             // Match number
274             return $this->write_number($row, $col, $token, $format);
275         } elseif (preg_match("/^[fh]tt?p:\/\//", $token)) {
276             // Match http or ftp URL
277             return $this->write_url($row, $col, $token, '', $format);
278         } elseif (preg_match("/^mailto:/", $token)) {
279             // Match mailto:
280             return $this->write_url($row, $col, $token, '', $format);
281         } elseif (preg_match("/^(?:in|ex)ternal:/", $token)) {
282             // Match internal or external sheet link
283             return $this->write_url($row, $col, $token, '', $format);
284         } elseif (preg_match("/^=/", $token)) {
285             // Match formula
286             return $this->write_formula($row, $col, $token, $format);
287         } elseif (preg_match("/^@/", $token)) {
288             // Match formula
289             return $this->write_formula($row, $col, $token, $format);
290         } elseif ($token == '') {
291             // Match blank
292             return $this->write_blank($row, $col, $format);
293         } else {
294             // Default: match string
295             return $this->write_string($row, $col, $token, $format);
296         }
297     }
299     /**
300      * Sets the height (and other settings) of one row.
301      *
302      * @param integer $row    The row to set
303      * @param integer $height Height we are giving to the row (null to set just format without setting the height)
304      * @param mixed   $format The optional format we are giving to the row
305      * @param bool    $hidden The optional hidden attribute
306      * @param integer $level  The optional outline level (0-7)
307      */
308     public function set_row($row, $height, $format = null, $hidden = false, $level = 0) {
309         if (is_array($format)) {
310             $format = new MoodleODSFormat($format);
311         }
312         if ($level < 0) {
313             $level = 0;
314         } else if ($level > 7) {
315             $level = 7;
316         }
317         if (!isset($this->rows[$row])) {
318             $this->rows[$row] = new stdClass();
319         }
320         if (isset($height)) {
321             $this->rows[$row]->height = $height;
322         }
323         $this->rows[$row]->format = $format;
324         $this->rows[$row]->hidden = $hidden;
325         $this->rows[$row]->level  = $level;
326     }
328     /**
329      * Sets the width (and other settings) of one column.
330      *
331      * @param integer $firstcol first column on the range
332      * @param integer $lastcol  last column on the range
333      * @param integer $width    width to set (null to set just format without setting the width)
334      * @param mixed   $format   The optional format to apply to the columns
335      * @param bool    $hidden   The optional hidden attribute
336      * @param integer $level    The optional outline level (0-7)
337      */
338     public function set_column($firstcol, $lastcol, $width, $format = null, $hidden = false, $level = 0) {
339         if (is_array($format)) {
340             $format = new MoodleODSFormat($format);
341         }
342         if ($level < 0) {
343             $level = 0;
344         } else if ($level > 7) {
345             $level = 7;
346         }
347         for($i=$firstcol; $i<=$lastcol; $i++) {
348             if (!isset($this->columns[$i])) {
349                 $this->columns[$i] = new stdClass();
350             }
351             if (isset($width)) {
352                 $this->columns[$i]->width = $width*6.15; // 6.15 is a magic constant here!
353             }
354             $this->columns[$i]->format = $format;
355             $this->columns[$i]->hidden = $hidden;
356             $this->columns[$i]->level  = $level;
357         }
358     }
360     /**
361      * Set the option to hide gridlines on the printed page.
362      */
363     public function hide_gridlines() {
364         // Not implemented - always off.
365     }
367     /**
368      * Set the option to hide gridlines on the worksheet (as seen on the screen).
369      */
370     public function hide_screen_gridlines() {
371         $this->showgrid = false;
372     }
374     /**
375      * Insert a 24bit bitmap image in a worksheet.
376      *
377      * @param integer $row     The row we are going to insert the bitmap into
378      * @param integer $col     The column we are going to insert the bitmap into
379      * @param string  $bitmap  The bitmap filename
380      * @param integer $x       The horizontal position (offset) of the image inside the cell.
381      * @param integer $y       The vertical position (offset) of the image inside the cell.
382      * @param integer $scale_x The horizontal scale
383      * @param integer $scale_y The vertical scale
384      */
385     public function insert_bitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1) {
386         // Not implemented.
387     }
389     /**
390      * Merges the area given by its arguments.
391      *
392      * @param integer $first_row First row of the area to merge
393      * @param integer $first_col First column of the area to merge
394      * @param integer $last_row  Last row of the area to merge
395      * @param integer $last_col  Last column of the area to merge
396      */
397     public function merge_cells($first_row, $first_col, $last_row, $last_col) {
398         if ($first_row > $last_row or $first_col > $last_col) {
399             return;
400         }
402         if (!isset($this->data[$first_row][$first_col])) {
403             $this->data[$first_row][$first_col] = new MoodleODSCell();
404         }
406         $this->data[$first_row][$first_col]->merge = array('rows'=>($last_row-$first_row+1), 'columns'=>($last_col-$first_col+1));
407     }
411 /**
412  * ODS cell format abstraction.
413  *
414  * @package   core
415  * @copyright 2006 Petr Skoda {@link http://skodak.org}
416  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
417  */
418 class MoodleODSFormat {
419     public $id;
420     public $properties = array();
422     /**
423      * Constructs one Moodle Format.
424      *
425      * @param array $properties
426      */
427     public function __construct($properties = array()) {
428         static $fid = 1;
430         $this->id = $fid++;
432         foreach($properties as $property => $value) {
433             if (method_exists($this, "set_$property")) {
434                 $aux = 'set_'.$property;
435                 $this->$aux($value);
436             }
437         }
438     }
440     /**
441      * Set the size of the text in the format (in pixels).
442      * By default all texts in generated sheets are 10pt.
443      *
444      * @param integer $size Size of the text (in points)
445      */
446     public function set_size($size) {
447         $this->properties['size'] = $size;
448     }
450     /**
451      * Set weight of the format.
452      *
453      * @param integer $weight Weight for the text, 0 maps to 400 (normal text),
454      *                        1 maps to 700 (bold text). Valid range is: 100-1000.
455      *                        It's Optional, default is 1 (bold).
456      */
457     public function set_bold($weight = 1) {
458         if ($weight == 1) {
459             $weight = 700;
460         }
461         $this->properties['bold'] = ($weight > 400);
462     }
464     /**
465      * Set underline of the format.
466      *
467      * @param integer $underline The value for underline. Possible values are:
468      *                           1 => underline, 2 => double underline
469      */
470     public function set_underline($underline = 1) {
471         if ($underline == 1) {
472             $this->properties['underline'] = 1;
473         } else if ($underline == 2) {
474             $this->properties['underline'] = 2;
475         } else {
476             unset($this->properties['underline']);
477         }
478     }
480     /**
481      * Set italic of the format.
482      */
483     public function set_italic() {
484         $this->properties['italic'] = true;
485     }
487     /**
488      * Set strikeout of the format
489      */
490     public function set_strikeout() {
491         $this->properties['strikeout'] = true;
492     }
494     /**
495      * Set outlining of the format.
496      */
497     public function set_outline() {
498         // Not implemented.
499     }
501     /**
502      * Set shadow of the format.
503      */
504     public function set_shadow() {
505         // Not implemented.
506     }
508     /**
509      * Set the script of the text.
510      *
511      * @param integer $script The value for script type. Possible values are:
512      *                        1 => superscript, 2 => subscript
513      */
514     public function set_script($script) {
515         if ($script == 1) {
516             $this->properties['super_script'] = true;
517             unset($this->properties['sub_script']);
519         } else if ($script == 2) {
520             $this->properties['sub_script'] = true;
521             unset($this->properties['super_script']);
523         } else {
524             unset($this->properties['sub_script']);
525             unset($this->properties['super_script']);
526         }
527     }
529     /**
530      * Set color of the format.
531      *
532      * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63])
533      */
534     public function set_color($color) {
535         $this->properties['color'] = $this->parse_color($color);
536     }
538     /**
539      * Not used.
540      *
541      * @param mixed $color
542      */
543     public function set_fg_color($color) {
544         // Not implemented.
545     }
547     /**
548      * Set background color of the cell.
549      *
550      * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63])
551      */
552     public function set_bg_color($color) {
553         $this->properties['bg_color'] = $this->parse_color($color);
554     }
556     /**
557      * Set the cell fill pattern.
558      *
559      * @deprecated use set_bg_color() instead.
560      * @param integer
561      */
562     public function set_pattern($pattern=1) {
563         if ($pattern > 0) {
564             if (!isset($this->properties['bg_color'])) {
565                 $this->properties['bg_color'] = $this->parse_color('black');
566             }
567         } else {
568             unset($this->properties['bg_color']);
569         }
571     }
573     /**
574      * Set text wrap of the format
575      */
576     public function set_text_wrap() {
577         $this->properties['wrap'] = true;
578     }
580     /**
581      * Set the cell alignment of the format.
582      *
583      * @param string $location alignment for the cell ('left', 'right', 'justify', etc...)
584      */
585     public function set_align($location) {
586         if (in_array($location, array('left', 'centre', 'center', 'right', 'fill', 'merge', 'justify', 'equal_space'))) {
587             $this->set_h_align($location);
589         } else if (in_array($location, array('top', 'vcentre', 'vcenter', 'bottom', 'vjustify', 'vequal_space'))) {
590             $this->set_v_align($location);
591         }
592     }
594     /**
595      * Set the cell horizontal alignment of the format.
596      *
597      * @param string $location alignment for the cell ('left', 'right', 'justify', etc...)
598      */
599     public function set_h_align($location) {
600         switch ($location) {
601             case 'left':
602                 $this->properties['align'] = 'start';
603                 break;
604             case 'center':
605             case 'centre':
606             $this->properties['align'] = 'center';
607                 break;
608             case 'right':
609                 $this->properties['align'] = 'end';
610                 break;
611         }
612     }
614     /**
615      * Set the cell vertical alignment of the format.
616      *
617      * @param string $location alignment for the cell ('top', 'bottom', 'center', 'justify')
618      */
619     public function set_v_align($location) {
620         switch ($location) {
621             case 'top':
622                 $this->properties['v_align'] = 'top';
623                 break;
624             case 'vcentre':
625             case 'vcenter':
626             case 'centre':
627             case 'center':
628                 $this->properties['v_align'] = 'middle';
629                 break;
630             default:
631                 $this->properties['v_align'] = 'bottom';
632         }
633     }
635     /**
636      * Set the top border of the format.
637      *
638      * @param integer $style style for the cell. 1 => thin, 2 => thick
639      */
640     public function set_top($style) {
641         if ($style == 1) {
642             $style = 0.2;
643         } else if ($style == 2) {
644             $style = 0.5;
645         } else {
646             return;
647         }
648         $this->properties['border_top'] = $style;
649     }
651     /**
652      * Set the bottom border of the format.
653      *
654      * @param integer $style style for the cell. 1 => thin, 2 => thick
655      */
656     public function set_bottom($style) {
657         if ($style == 1) {
658             $style = 0.2;
659         } else if ($style == 2) {
660             $style = 0.5;
661         } else {
662             return;
663         }
664         $this->properties['border_bottom'] = $style;
665     }
667     /**
668      * Set the left border of the format.
669      *
670      * @param integer $style style for the cell. 1 => thin, 2 => thick
671      */
672     public function set_left($style) {
673         if ($style == 1) {
674             $style = 0.2;
675         } else if ($style == 2) {
676             $style = 0.5;
677         } else {
678             return;
679         }
680         $this->properties['border_left'] = $style;
681     }
683     /**
684      * Set the right border of the format.
685      *
686      * @param integer $style style for the cell. 1 => thin, 2 => thick
687      */
688     public function set_right($style) {
689         if ($style == 1) {
690             $style = 0.2;
691         } else if ($style == 2) {
692             $style = 0.5;
693         } else {
694             return;
695         }
696         $this->properties['border_right'] = $style;
697     }
699     /**
700      * Set cells borders to the same style
701      * @param integer $style style to apply for all cell borders. 1 => thin, 2 => thick.
702      */
703     public function set_border($style) {
704         $this->set_top($style);
705         $this->set_bottom($style);
706         $this->set_left($style);
707         $this->set_right($style);
708     }
710     /**
711      * Set the numerical format of the format.
712      * It can be date, time, currency, etc...
713      *
714      * @param mixed $num_format The numeric format
715      */
716     public function set_num_format($num_format) {
718         $numbers = array();
720         $numbers[1] = '0';
721         $numbers[2] = '0.00';
722         $numbers[3] = '#,##0';
723         $numbers[4] = '#,##0.00';
724         $numbers[11] = '0.00E+00';
725         $numbers[12] = '# ?/?';
726         $numbers[13] = '# ??/??';
727         $numbers[14] = 'mm-dd-yy';
728         $numbers[15] = 'd-mmm-yy';
729         $numbers[16] = 'd-mmm';
730         $numbers[17] = 'mmm-yy';
731         $numbers[22] = 'm/d/yy h:mm';
732         $numbers[49] = '@';
734         if ($num_format !== 0 and in_array($num_format, $numbers)) {
735             $flipped = array_flip($numbers);
736             $this->properties['num_format'] = $flipped[$num_format];
737         }
738         if (!isset($numbers[$num_format])) {
739             return;
740         }
742         $this->properties['num_format'] = $num_format;
743     }
745     /**
746      * Standardise colour name.
747      *
748      * @param mixed $color name of the color (i.e.: 'blue', 'red', etc..), or an integer (range is [8...63]).
749      * @return string the RGB color value
750      */
751     protected function parse_color($color) {
752         if (strpos($color, '#') === 0) {
753             // No conversion should be needed.
754             return $color;
755         }
757         if ($color > 7 and $color < 53) {
758             $numbers = array(
759                 8  => 'black',
760                 12 => 'blue',
761                 16 => 'brown',
762                 15 => 'cyan',
763                 23 => 'gray',
764                 17 => 'green',
765                 11 => 'lime',
766                 14 => 'magenta',
767                 18 => 'navy',
768                 53 => 'orange',
769                 33 => 'pink',
770                 20 => 'purple',
771                 10 => 'red',
772                 22 => 'silver',
773                 9  => 'white',
774                 13 => 'yellow',
775             );
776             if (isset($numbers[$color])) {
777                 $color = $numbers[$color];
778             } else {
779                 $color = 'black';
780             }
781         }
783         $colors = array(
784             'aqua'    => '00FFFF',
785             'black'   => '000000',
786             'blue'    => '0000FF',
787             'brown'   => 'A52A2A',
788             'cyan'    => '00FFFF',
789             'fuchsia' => 'FF00FF',
790             'gray'    => '808080',
791             'grey'    => '808080',
792             'green'   => '00FF00',
793             'lime'    => '00FF00',
794             'magenta' => 'FF00FF',
795             'maroon'  => '800000',
796             'navy'    => '000080',
797             'orange'  => 'FFA500',
798             'olive'   => '808000',
799             'pink'    => 'FAAFBE',
800             'purple'  => '800080',
801             'red'     => 'FF0000',
802             'silver'  => 'C0C0C0',
803             'teal'    => '008080',
804             'white'   => 'FFFFFF',
805             'yellow'  => 'FFFF00',
806         );
808         if (isset($colors[$color])) {
809             return('#'.$colors[$color]);
810         }
812         return('#'.$colors['black']);
813     }
817 /**
818  * ODS file writer.
819  *
820  * @package   core
821  * @copyright 2013 Petr Skoda {@link http://skodak.org}
822  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
823  */
824 class MoodleODSWriter {
825     protected $worksheets;
827     public function __construct(array $worksheets) {
828         $this->worksheets = $worksheets;
829     }
831     /**
832      * Fetch the file ocntnet for the ODS.
833      *
834      * @return string
835      */
836     public function get_file_content() {
837         $dir = make_request_directory();
838         $filename = $dir . '/result.ods';
840         $files = [
841                 'mimetype'              => [$this->get_ods_mimetype()],
842                 'content.xml'           => [$this->get_ods_content($this->worksheets)],
843                 'meta.xml'              => [$this->get_ods_meta()],
844                 'styles.xml'            => [$this->get_ods_styles()],
845                 'settings.xml'          => [$this->get_ods_settings()],
846                 'META-INF/manifest.xml' => [$this->get_ods_manifest()],
847             ];
849         $packer = get_file_packer('application/zip');
850         $packer->archive_to_pathname($files, $filename);
852         $contents = file_get_contents($filename);
854         remove_dir($dir);
855         return $contents;
856     }
858     protected function get_ods_content() {
860         // Find out the size of worksheets and used styles.
861         $formats = array();
862         $formatstyles = '';
863         $rowstyles = '';
864         $colstyles = '';
866         foreach($this->worksheets as $wsnum=>$ws) {
867             $this->worksheets[$wsnum]->maxr = 0;
868             $this->worksheets[$wsnum]->maxc = 0;
869             foreach($ws->data as $rnum=>$row) {
870                 if ($rnum > $this->worksheets[$wsnum]->maxr) {
871                     $this->worksheets[$wsnum]->maxr = $rnum;
872                 }
873                 foreach($row as $cnum=>$cell) {
874                     if ($cnum > $this->worksheets[$wsnum]->maxc) {
875                         $this->worksheets[$wsnum]->maxc = $cnum;
876                     }
877                     if (!empty($cell->format)) {
878                         if (!array_key_exists($cell->format->id, $formats)) {
879                             $formats[$cell->format->id] = $cell->format;
880                         }
881                     }
882                 }
883             }
885             foreach($ws->rows as $rnum=>$row) {
886                 if (!empty($row->format)) {
887                     if (!array_key_exists($row->format->id, $formats)) {
888                         $formats[$row->format->id] = $row->format;
889                     }
890                 }
891                 if ($rnum > $this->worksheets[$wsnum]->maxr) {
892                     $this->worksheets[$wsnum]->maxr = $rnum;
893                 }
894                 // Define all column styles.
895                 if (!empty($ws->rows[$rnum])) {
896                     $rowstyles .= '<style:style style:name="ws'.$wsnum.'ro'.$rnum.'" style:family="table-row">';
897                     if (isset($row->height)) {
898                         $rowstyles .= '<style:table-row-properties style:row-height="'.$row->height.'pt"/>';
899                     }
900                     $rowstyles .= '</style:style>';
901                 }
902             }
904             foreach($ws->columns as $cnum=>$col) {
905                 if (!empty($col->format)) {
906                     if (!array_key_exists($col->format->id, $formats)) {
907                         $formats[$col->format->id] = $col->format;
908                     }
909                 }
910                 if ($cnum > $this->worksheets[$wsnum]->maxc) {
911                     $this->worksheets[$wsnum]->maxc = $cnum;
912                 }
913                 // Define all column styles.
914                 if (!empty($ws->columns[$cnum])) {
915                     $colstyles .= '<style:style style:name="ws'.$wsnum.'co'.$cnum.'" style:family="table-column">';
916                     if (isset($col->width)) {
917                         $colstyles .= '<style:table-column-properties style:column-width="'.$col->width.'pt"/>';
918                     }
919                     $colstyles .= '</style:style>';
920                 }
921             }
922         }
924         foreach($formats as $format) {
925             $textprop = '';
926             $cellprop = '';
927             $parprop  = '';
928             $dataformat = '';
929             foreach($format->properties as $pname=>$pvalue) {
930                 switch ($pname) {
931                     case 'size':
932                         if (!empty($pvalue)) {
933                             $textprop .= ' fo:font-size="'.$pvalue.'pt"';
934                         }
935                         break;
936                     case 'bold':
937                         if (!empty($pvalue)) {
938                             $textprop .= ' fo:font-weight="bold"';
939                         }
940                         break;
941                     case 'italic':
942                         if (!empty($pvalue)) {
943                             $textprop .= ' fo:font-style="italic"';
944                         }
945                         break;
946                     case 'underline':
947                         if (!empty($pvalue)) {
948                             $textprop .= ' style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-width="auto"';
949                             if ($pvalue == 2) {
950                                 $textprop .= ' style:text-underline-type="double"';
951                             }
952                         }
953                         break;
954                     case 'strikeout':
955                         if (!empty($pvalue)) {
956                             $textprop .= ' style:text-line-through-style="solid"';
957                         }
958                         break;
959                     case 'color':
960                         if ($pvalue !== false) {
961                             $textprop .= ' fo:color="'.$pvalue.'"';
962                         }
963                         break;
964                     case 'bg_color':
965                         if ($pvalue !== false) {
966                             $cellprop .= ' fo:background-color="'.$pvalue.'"';
967                         }
968                         break;
969                     case 'align':
970                         $parprop .= ' fo:text-align="'.$pvalue.'"';
971                         break;
972                     case 'v_align':
973                         $cellprop .= ' style:vertical-align="'.$pvalue.'"';
974                         break;
975                     case 'wrap':
976                         if ($pvalue) {
977                             $cellprop .= ' fo:wrap-option="wrap"';
978                         }
979                         break;
980                     case 'border_top':
981                         $cellprop .= ' fo:border-top="'.$pvalue.'pt solid #000000"';
982                         break;
983                     case 'border_left':
984                         $cellprop .= ' fo:border-left="'.$pvalue.'pt solid #000000"';
985                         break;
986                     case 'border_bottom':
987                         $cellprop .= ' fo:border-bottom="'.$pvalue.'pt solid #000000"';
988                         break;
989                     case 'border_right':
990                         $cellprop .= ' fo:border-right="'.$pvalue.'pt solid #000000"';
991                         break;
992                     case 'num_format':
993                         $dataformat = ' style:data-style-name="NUM'.$pvalue.'"';
994                         break;
995                 }
996             }
997             if (!empty($textprop)) {
998                 $textprop = '
999    <style:text-properties'.$textprop.'/>';
1000             }
1002             if (!empty($cellprop)) {
1003                 $cellprop = '
1004    <style:table-cell-properties'.$cellprop.'/>';
1005             }
1007             if (!empty($parprop)) {
1008                 $parprop = '
1009    <style:paragraph-properties'.$parprop.'/>';
1010             }
1012             $formatstyles .= '
1013   <style:style style:name="format'.$format->id.'" style:family="table-cell"'.$dataformat.'>'.$textprop.$cellprop.$parprop.'
1014   </style:style>';
1015         }
1017         // The text styles may be breaking older ODF validators.
1018         $scriptstyles ='
1019   <style:style style:name="T1" style:family="text">
1020     <style:text-properties style:text-position="33% 58%"/>
1021   </style:style>
1022   <style:style style:name="T2" style:family="text">
1023     <style:text-properties style:text-position="-33% 58%"/>
1024   </style:style>
1025 ';
1026         // Header.
1027         $buffer =
1028 '<?xml version="1.0" encoding="UTF-8"?>
1029 <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
1030                          xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
1031                          xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
1032                          xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
1033                          xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
1034                          xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
1035                          xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
1036                          xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
1037                          xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
1038                          xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"
1039                          xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
1040                          xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
1041                          xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
1042                          xmlns:math="http://www.w3.org/1998/Math/MathML"
1043                          xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
1044                          xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
1045                          xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer"
1046                          xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events"
1047                          xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
1048                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1049                          xmlns:rpt="http://openoffice.org/2005/report"
1050                          xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"
1051                          xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#"
1052                          xmlns:tableooo="http://openoffice.org/2009/table"
1053                          xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"
1054                          xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"
1055                          xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"
1056                          xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2">
1057   <office:scripts/>
1058   <office:font-face-decls>
1059     <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="swiss"
1060                      style:font-pitch="variable"/>
1061     <style:font-face style:name="Arial Unicode MS" svg:font-family="&apos;Arial Unicode MS&apos;"
1062                      style:font-family-generic="system" style:font-pitch="variable"/>
1063     <style:font-face style:name="Tahoma" svg:font-family="Tahoma" style:font-family-generic="system"
1064                      style:font-pitch="variable"/>
1065   </office:font-face-decls>
1066   <office:automatic-styles>';
1067         $buffer .= $this->get_num_styles();
1068         $buffer .= '
1069     <style:style style:name="ta1" style:family="table" style:master-page-name="Standard1">
1070       <style:table-properties table:display="true"/>
1071     </style:style>';
1073         $buffer .= $formatstyles;
1074         $buffer .= $rowstyles;
1075         $buffer .= $colstyles;
1076         $buffer .= $scriptstyles;
1078          $buffer .= '
1079   </office:automatic-styles>
1080   <office:body>
1081     <office:spreadsheet>
1082 ';
1084         foreach($this->worksheets as $wsnum=>$ws) {
1086             // Worksheet header.
1087             $buffer .= '<table:table table:name="' . htmlspecialchars($ws->name, ENT_QUOTES, 'utf-8') . '" table:style-name="ta1">'."\n";
1089             // Define column properties.
1090             $level = 0;
1091             for($c=0; $c<=$ws->maxc; $c++) {
1092                 if (array_key_exists($c, $ws->columns)) {
1093                     $column = $ws->columns[$c];
1094                     if ($column->level > $level) {
1095                         while ($column->level > $level) {
1096                             $buffer .= '<table:table-column-group>';
1097                             $level++;
1098                         }
1099                     } else if ($column->level < $level) {
1100                         while ($column->level < $level) {
1101                             $buffer .= '</table:table-column-group>';
1102                             $level--;
1103                         }
1104                     }
1105                     $extra = '';
1106                     if (!empty($column->format)) {
1107                         $extra .= ' table:default-cell-style-name="format'.$column->format->id.'"';
1108                     }
1109                     if ($column->hidden) {
1110                         $extra .= ' table:visibility="collapse"';
1111                     }
1112                     $buffer .= '<table:table-column table:style-name="ws'.$wsnum.'co'.$c.'"'.$extra.'/>'."\n";
1113                 } else {
1114                     while ($level > 0) {
1115                         $buffer .= '</table:table-column-group>';
1116                         $level--;
1117                     }
1118                     $buffer .= '<table:table-column/>'."\n";
1119                 }
1120             }
1121             while ($level > 0) {
1122                 $buffer .= '</table:table-column-group>';
1123                 $level--;
1124             }
1126             // Print all rows.
1127             $level = 0;
1128             for($r=0; $r<=$ws->maxr; $r++) {
1129                 if (array_key_exists($r, $ws->rows)) {
1130                     $row = $ws->rows[$r];
1131                     if ($row->level > $level) {
1132                         while ($row->level > $level) {
1133                             $buffer .= '<table:table-row-group>';
1134                             $level++;
1135                         }
1136                     } else if ($row->level < $level) {
1137                         while ($row->level < $level) {
1138                             $buffer .= '</table:table-row-group>';
1139                             $level--;
1140                         }
1141                     }
1142                     $extra = '';
1143                     if (!empty($row->format)) {
1144                         $extra .= ' table:default-cell-style-name="format'.$row->format->id.'"';
1145                     }
1146                     if ($row->hidden) {
1147                         $extra .= ' table:visibility="collapse"';
1148                     }
1149                     $buffer .= '<table:table-row table:style-name="ws'.$wsnum.'ro'.$r.'"'.$extra.'>'."\n";
1150                 } else {
1151                     while ($level > 0) {
1152                         $buffer .= '</table:table-row-group>';
1153                         $level--;
1154                     }
1155                     $buffer .= '<table:table-row>'."\n";
1156                 }
1157                 for($c=0; $c<=$ws->maxc; $c++) {
1158                     if (isset($ws->data[$r][$c])) {
1159                         $cell = $ws->data[$r][$c];
1160                         $extra = '';
1161                         if (!empty($cell->format)) {
1162                             $extra .= ' table:style-name="format'.$cell->format->id.'"';
1163                         }
1164                         if (!empty($cell->merge)) {
1165                             $extra .= ' table:number-columns-spanned="'.$cell->merge['columns'].'" table:number-rows-spanned="'.$cell->merge['rows'].'"';
1166                         }
1167                         $pretext = '<text:p>';
1168                         $posttext = '</text:p>';
1169                         if (!empty($cell->format->properties['sub_script'])) {
1170                             $pretext = $pretext.'<text:span text:style-name="T2">';
1171                             $posttext = '</text:span>'.$posttext;
1172                         } else if (!empty($cell->format->properties['super_script'])) {
1173                             $pretext = $pretext.'<text:span text:style-name="T1">';
1174                             $posttext = '</text:span>'.$posttext;
1175                         }
1177                         if (isset($cell->formula)) {
1178                             $buffer .= '<table:table-cell table:formula="of:'.$cell->formula.'"'.$extra.'></table:table-cell>'."\n";
1179                         } else if ($cell->type == 'date') {
1180                             $buffer .= '<table:table-cell office:value-type="date" office:date-value="' . strftime('%Y-%m-%dT%H:%M:%S', $cell->value) . '"'.$extra.'>'
1181                                      . $pretext . strftime('%Y-%m-%dT%H:%M:%S', $cell->value) . $posttext
1182                                      . '</table:table-cell>'."\n";
1183                         } else if ($cell->type == 'float') {
1184                             $buffer .= '<table:table-cell office:value-type="float" office:value="' . htmlspecialchars($cell->value, ENT_QUOTES, 'utf-8') . '"'.$extra.'>'
1185                                      . $pretext . htmlspecialchars($cell->value, ENT_QUOTES, 'utf-8') . $posttext
1186                                      . '</table:table-cell>'."\n";
1187                         } else if ($cell->type == 'string') {
1188                             $buffer .= '<table:table-cell office:value-type="string"'.$extra.'>'
1189                                      . $pretext . htmlspecialchars($cell->value, ENT_QUOTES, 'utf-8') . $posttext
1190                                      . '</table:table-cell>'."\n";
1191                         } else {
1192                             $buffer .= '<table:table-cell office:value-type="string"'.$extra.'>'
1193                                      . $pretext . '!!Error - unknown type!!' . $posttext
1194                                      . '</table:table-cell>'."\n";
1195                         }
1196                     } else {
1197                         $buffer .= '<table:table-cell/>'."\n";
1198                     }
1199                 }
1200                 $buffer .= '</table:table-row>'."\n";
1201             }
1202             while ($level > 0) {
1203                 $buffer .= '</table:table-row-group>';
1204                 $level--;
1205             }
1206             $buffer .= '</table:table>'."\n";
1208         }
1210         // Footer.
1211         $buffer .= '
1212     </office:spreadsheet>
1213   </office:body>
1214 </office:document-content>';
1216         return $buffer;
1217     }
1219     public function get_ods_mimetype() {
1220         return 'application/vnd.oasis.opendocument.spreadsheet';
1221     }
1223     protected function get_ods_settings() {
1224         $buffer =
1225 '<?xml version="1.0" encoding="UTF-8"?>
1226 <office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
1227                           xmlns:xlink="http://www.w3.org/1999/xlink"
1228                           xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"
1229                           xmlns:ooo="http://openoffice.org/2004/office" office:version="1.2">
1230     <office:settings>
1231       <config:config-item-set config:name="ooo:view-settings">
1232         <config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
1233         <config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
1234         <config:config-item-map-indexed config:name="Views">
1235           <config:config-item-map-entry>
1236             <config:config-item config:name="ViewId" config:type="string">view1</config:config-item>
1237             <config:config-item-map-named config:name="Tables">
1238 ';
1239         foreach ($this->worksheets as $ws) {
1240             $buffer .= '               <config:config-item-map-entry config:name="'.htmlspecialchars($ws->name, ENT_QUOTES, 'utf-8').'">'."\n";
1241             $buffer .= '                 <config:config-item config:name="ShowGrid" config:type="boolean">'.($ws->showgrid ? 'true' : 'false').'</config:config-item>'."\n";
1242             $buffer .= '               </config:config-item-map-entry>."\n"';
1243         }
1244             $buffer .=
1245 '           </config:config-item-map-named>
1246           </config:config-item-map-entry>
1247         </config:config-item-map-indexed>
1248       </config:config-item-set>
1249       <config:config-item-set config:name="ooo:configuration-settings">
1250         <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
1251       </config:config-item-set>
1252     </office:settings>
1253 </office:document-settings>';
1255         return $buffer;
1256     }
1257     protected function get_ods_meta() {
1258         global $CFG, $USER;
1260         return
1261 '<?xml version="1.0" encoding="UTF-8"?>
1262 <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
1263                       xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
1264                       xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
1265                       xmlns:ooo="http://openoffice.org/2004/office" xmlns:grddl="http://www.w3.org/2003/g/data-view#"
1266                       office:version="1.2">
1267     <office:meta>
1268         <meta:generator>Moodle '.$CFG->release.'</meta:generator>
1269         <meta:initial-creator>' . htmlspecialchars(fullname($USER, true), ENT_QUOTES, 'utf-8') . '</meta:initial-creator>
1270         <meta:creation-date>'.strftime('%Y-%m-%dT%H:%M:%S').'</meta:creation-date>
1271         <meta:document-statistic meta:table-count="1" meta:cell-count="0" meta:object-count="0"/>
1272     </office:meta>
1273 </office:document-meta>';
1274     }
1276     protected function get_ods_styles() {
1277         return
1278 '<?xml version="1.0" encoding="UTF-8"?>
1279 <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
1280                         xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
1281                         xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
1282                         xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
1283                         xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
1284                         xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
1285                         xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
1286                         xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
1287                         xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
1288                         xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"
1289                         xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
1290                         xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
1291                         xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
1292                         xmlns:math="http://www.w3.org/1998/Math/MathML"
1293                         xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
1294                         xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
1295                         xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer"
1296                         xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events"
1297                         xmlns:rpt="http://openoffice.org/2005/report"
1298                         xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"
1299                         xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#"
1300                         xmlns:tableooo="http://openoffice.org/2009/table"
1301                         xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"
1302                         xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2">
1303     <office:font-face-decls>
1304         <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="swiss"
1305                          style:font-pitch="variable"/>
1306         <style:font-face style:name="Arial Unicode MS" svg:font-family="&apos;Arial Unicode MS&apos;"
1307                          style:font-family-generic="system" style:font-pitch="variable"/>
1308         <style:font-face style:name="Tahoma" svg:font-family="Tahoma" style:font-family-generic="system"
1309                          style:font-pitch="variable"/>
1310     </office:font-face-decls>
1311     <office:styles>
1312         <style:default-style style:family="table-cell">
1313             <style:paragraph-properties style:tab-stop-distance="1.25cm"/>
1314         </style:default-style>
1315         <number:number-style style:name="N0">
1316             <number:number number:min-integer-digits="1"/>
1317         </number:number-style>
1318         <style:style style:name="Default" style:family="table-cell">
1319             <style:text-properties style:font-name-asian="Arial Unicode MS" style:font-name-complex="Arial Unicode MS"/>
1320         </style:style>
1321         <style:style style:name="Result" style:family="table-cell" style:parent-style-name="Default">
1322             <style:text-properties fo:font-style="italic" style:text-underline-style="solid"
1323                                    style:text-underline-width="auto" style:text-underline-color="font-color"
1324                                    fo:font-weight="bold"/>
1325         </style:style>
1326         <style:style style:name="Result2" style:family="table-cell" style:parent-style-name="Result"
1327                      style:data-style-name="N104"/>
1328         <style:style style:name="Heading" style:family="table-cell" style:parent-style-name="Default">
1329             <style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
1330             <style:paragraph-properties fo:text-align="center"/>
1331             <style:text-properties fo:font-size="16pt" fo:font-style="italic" fo:font-weight="bold"/>
1332         </style:style>
1333         <style:style style:name="Heading1" style:family="table-cell" style:parent-style-name="Heading">
1334             <style:table-cell-properties style:rotation-angle="90"/>
1335         </style:style>
1336     </office:styles>
1337     <office:automatic-styles>
1338         <style:page-layout style:name="Mpm1">
1339             <style:page-layout-properties style:writing-mode="lr-tb"/>
1340             <style:header-style>
1341                 <style:header-footer-properties fo:min-height="0.75cm" fo:margin-left="0cm" fo:margin-right="0cm"
1342                                                 fo:margin-bottom="0.25cm"/>
1343             </style:header-style>
1344             <style:footer-style>
1345                 <style:header-footer-properties fo:min-height="0.75cm" fo:margin-left="0cm" fo:margin-right="0cm"
1346                                                 fo:margin-top="0.25cm"/>
1347             </style:footer-style>
1348         </style:page-layout>
1349         <style:page-layout style:name="Mpm2">
1350             <style:page-layout-properties style:writing-mode="lr-tb"/>
1351             <style:header-style>
1352                 <style:header-footer-properties fo:min-height="0.75cm" fo:margin-left="0cm" fo:margin-right="0cm"
1353                                                 fo:margin-bottom="0.25cm" fo:border="2.49pt solid #000000"
1354                                                 fo:padding="0.018cm" fo:background-color="#c0c0c0">
1355                     <style:background-image/>
1356                 </style:header-footer-properties>
1357             </style:header-style>
1358             <style:footer-style>
1359                 <style:header-footer-properties fo:min-height="0.75cm" fo:margin-left="0cm" fo:margin-right="0cm"
1360                                                 fo:margin-top="0.25cm" fo:border="2.49pt solid #000000"
1361                                                 fo:padding="0.018cm" fo:background-color="#c0c0c0">
1362                     <style:background-image/>
1363                 </style:header-footer-properties>
1364             </style:footer-style>
1365         </style:page-layout>
1366     </office:automatic-styles>
1367     <office:master-styles>
1368         <style:master-page style:name="Default" style:page-layout-name="Mpm1">
1369             <style:header>
1370                 <text:p>
1371                     <text:sheet-name>???</text:sheet-name>
1372                 </text:p>
1373             </style:header>
1374             <style:header-left style:display="false"/>
1375             <style:footer>
1376                 <text:p>Page
1377                     <text:page-number>1</text:page-number>
1378                 </text:p>
1379             </style:footer>
1380             <style:footer-left style:display="false"/>
1381         </style:master-page>
1382         <style:master-page style:name="Report" style:page-layout-name="Mpm2">
1383             <style:header>
1384                 <style:region-left>
1385                     <text:p>
1386                         <text:sheet-name>???</text:sheet-name>
1387                         (<text:title>???</text:title>)
1388                     </text:p>
1389                 </style:region-left>
1390                 <style:region-right>
1391                     <text:p><text:date style:data-style-name="N2" text:date-value="2013-01-05">00.00.0000</text:date>,
1392                         <text:time>00:00:00</text:time>
1393                     </text:p>
1394                 </style:region-right>
1395             </style:header>
1396             <style:header-left style:display="false"/>
1397             <style:footer>
1398                 <text:p>Page
1399                     <text:page-number>1</text:page-number>
1400                     /
1401                     <text:page-count>99</text:page-count>
1402                 </text:p>
1403             </style:footer>
1404             <style:footer-left style:display="false"/>
1405         </style:master-page>
1406     </office:master-styles>
1407 </office:document-styles>';
1408     }
1410     protected function get_ods_manifest() {
1411         return
1412 '<?xml version="1.0" encoding="UTF-8"?>
1413 <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">
1414  <manifest:file-entry manifest:full-path="/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
1415  <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
1416  <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
1417  <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
1418  <manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>
1419 </manifest:manifest>';
1420     }
1422     protected function get_num_styles() {
1423         return '
1424         <number:number-style style:name="NUM1">
1425             <number:number number:decimal-places="0" number:min-integer-digits="1"/>
1426         </number:number-style>
1427         <number:number-style style:name="NUM2">
1428             <number:number number:decimal-places="2" number:min-integer-digits="1"/>
1429         </number:number-style>
1430         <number:number-style style:name="NUM3">
1431             <number:number number:decimal-places="0" number:min-integer-digits="1" number:grouping="true"/>
1432         </number:number-style>
1433         <number:number-style style:name="NUM4">
1434             <number:number number:decimal-places="2" number:min-integer-digits="1" number:grouping="true"/>
1435         </number:number-style>
1436         <number:number-style style:name="NUM11">
1437             <number:scientific-number number:decimal-places="2" number:min-integer-digits="1"
1438                                       number:min-exponent-digits="2"/>
1439         </number:number-style>
1440         <number:number-style style:name="NUM12">
1441             <number:fraction number:min-integer-digits="0" number:min-numerator-digits="1"
1442                              number:min-denominator-digits="1"/>
1443         </number:number-style>
1444         <number:number-style style:name="NUM13">
1445             <number:fraction number:min-integer-digits="0" number:min-numerator-digits="2"
1446                              number:min-denominator-digits="2"/>
1447         </number:number-style>
1448         <number:date-style style:name="NUM14" number:automatic-order="true">
1449             <number:month number:style="long"/>
1450             <number:text>/</number:text>
1451             <number:day number:style="long"/>
1452             <number:text>/</number:text>
1453             <number:year/>
1454         </number:date-style>
1455         <number:date-style style:name="NUM15">
1456             <number:day/>
1457             <number:text>.</number:text>
1458             <number:month number:textual="true"/>
1459             <number:text>.</number:text>
1460             <number:year number:style="long"/>
1461         </number:date-style>
1462         <number:date-style style:name="NUM16" number:automatic-order="true">
1463             <number:month number:textual="true"/>
1464             <number:text></number:text>
1465             <number:day number:style="long"/>
1466         </number:date-style>
1467         <number:date-style style:name="NUM17">
1468             <number:month number:style="long"/>
1469             <number:text>-</number:text>
1470             <number:day number:style="long"/>
1471         </number:date-style>
1472         <number:date-style style:name="NUM22" number:automatic-order="true"
1473                            number:format-source="language">
1474             <number:month/>
1475             <number:text>/</number:text>
1476             <number:day/>
1477             <number:text>/</number:text>
1478             <number:year/>
1479             <number:text></number:text>
1480             <number:hours number:style="long"/>
1481             <number:text>:</number:text>
1482             <number:minutes number:style="long"/>
1483             <number:text></number:text>
1484             <number:am-pm/>
1485         </number:date-style>
1486         <number:text-style style:name="NUM49">
1487             <number:text-content/>
1488         </number:text-style>
1489 ';
1490     }