1 | <?php |
---|
2 | require 'zip/PclZipProxy.php'; |
---|
3 | require 'zip/PhpZipProxy.php'; |
---|
4 | require 'Segment.php'; |
---|
5 | class OdfException extends Exception |
---|
6 | {} |
---|
7 | /** |
---|
8 | * Templating class for odt file |
---|
9 | * You need PHP 5.2 at least |
---|
10 | * You need Zip Extension or PclZip library |
---|
11 | * Encoding : ISO-8859-1 |
---|
12 | * Last commit by $Author: neveldo $ |
---|
13 | * Date - $Date: 2009-06-06 22:17:14 +0200 (sam., 06 juin 2009) $ |
---|
14 | * SVN Revision - $Rev: 40 $ |
---|
15 | * Id : $Id: odf.php 40 2009-06-06 20:17:14Z neveldo $ |
---|
16 | * |
---|
17 | * @copyright GPL License 2008 - Julien Pauli - Cyril PIERRE de GEYER - Anaska (http://www.anaska.com) |
---|
18 | * @license http://www.gnu.org/copyleft/gpl.html GPL License |
---|
19 | * @version 1.3 |
---|
20 | */ |
---|
21 | class Odf |
---|
22 | { |
---|
23 | protected $config = array( |
---|
24 | 'ZIP_PROXY' => 'PclZipProxy', |
---|
25 | 'DELIMITER_LEFT' => '{', |
---|
26 | 'DELIMITER_RIGHT' => '}' |
---|
27 | ); |
---|
28 | protected $file; |
---|
29 | protected $contentXml; |
---|
30 | protected $tmpfile; |
---|
31 | protected $images = array(); |
---|
32 | protected $vars = array(); |
---|
33 | protected $segments = array(); |
---|
34 | const PIXEL_TO_CM = 0.026458333; |
---|
35 | /** |
---|
36 | * Constructeur de classe |
---|
37 | * |
---|
38 | * @param string $filename nom du fichier odt |
---|
39 | * @throws OdfException |
---|
40 | */ |
---|
41 | public function __construct($filename, $config = array(), $tmpdir="/tmp") |
---|
42 | { |
---|
43 | if (! is_array($config)) { |
---|
44 | throw new OdfException('Configuration data must be passed as array'); |
---|
45 | } |
---|
46 | foreach ($config as $configKey => $configValue) { |
---|
47 | if (array_key_exists($configKey, $this->config)) { |
---|
48 | $this->config[$configKey] = $configValue; |
---|
49 | } |
---|
50 | } |
---|
51 | if (! class_exists($this->config['ZIP_PROXY'])) { |
---|
52 | throw new OdfException($this->config['ZIP_PROXY'] . ' class not found - check your php settings'); |
---|
53 | } |
---|
54 | $zipHandler = $this->config['ZIP_PROXY']; |
---|
55 | $this->file = new $zipHandler(); |
---|
56 | $this->file->TMP_DIR = $tmpdir; |
---|
57 | if ($this->file->open($filename) !== true) { |
---|
58 | throw new OdfException("Error while Opening the file '$filename' - Check your odt file"); |
---|
59 | } |
---|
60 | if (($this->contentXml = $this->file->getFromName('content.xml')) === false) { |
---|
61 | throw new OdfException("Nothing to parse - check that the content.xml file is correctly formed"); |
---|
62 | } |
---|
63 | |
---|
64 | $this->file->close(); |
---|
65 | |
---|
66 | $tmp = tempnam(null, md5(uniqid())); |
---|
67 | copy($filename, $tmp); |
---|
68 | $this->tmpfile = $tmp; |
---|
69 | $this->_moveRowSegments(); |
---|
70 | } |
---|
71 | /** |
---|
72 | * Affecte une variable de template |
---|
73 | * |
---|
74 | * @param string $key nom de la variable dans le template |
---|
75 | * @param string $value valeur de remplacement |
---|
76 | * @param bool $encode si true, les caractères spéciaux XML seront encodés |
---|
77 | * @throws OdfException |
---|
78 | * @return odf |
---|
79 | */ |
---|
80 | public function setVars($key, $value, $encode = true, $charset = 'ISO-8859') |
---|
81 | { |
---|
82 | if (strpos($this->contentXml, $this->config['DELIMITER_LEFT'] . $key . $this->config['DELIMITER_RIGHT']) === false) { |
---|
83 | throw new OdfException("var $key not found in the document"); |
---|
84 | } |
---|
85 | $value = $encode ? htmlspecialchars($value) : $value; |
---|
86 | $value = ($charset == 'ISO-8859') ? utf8_encode($value) : $value; |
---|
87 | $this->vars[$this->config['DELIMITER_LEFT'] . $key . $this->config['DELIMITER_RIGHT']] = str_replace("\n", "<text:line-break/>", $value); |
---|
88 | return $this; |
---|
89 | } |
---|
90 | /** |
---|
91 | * Affecte une variable de template en tant qu'image |
---|
92 | * |
---|
93 | * @param string $key nom de la variable dans le template |
---|
94 | * @param string $value chemin vers une image |
---|
95 | * @throws OdfException |
---|
96 | * @return odf |
---|
97 | */ |
---|
98 | public function setImage($key, $value) |
---|
99 | { |
---|
100 | $filename = strtok(strrchr($value, '/'), '/.'); |
---|
101 | $file = substr(strrchr($value, '/'), 1); |
---|
102 | $size = @getimagesize($value); |
---|
103 | if ($size === false) { |
---|
104 | throw new OdfException("Invalid image"); |
---|
105 | } |
---|
106 | list ($width, $height) = $size; |
---|
107 | $width *= self::PIXEL_TO_CM; |
---|
108 | $height *= self::PIXEL_TO_CM; |
---|
109 | $xml = <<<IMG |
---|
110 | <draw:frame draw:style-name="fr1" draw:name="$filename" text:anchor-type="char" svg:width="{$width}cm" svg:height="{$height}cm" draw:z-index="3"><draw:image xlink:href="Pictures/$file" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/></draw:frame> |
---|
111 | IMG; |
---|
112 | $this->images[$value] = $file; |
---|
113 | $this->setVars($key, $xml, false); |
---|
114 | return $this; |
---|
115 | } |
---|
116 | /** |
---|
117 | * Déplace les balises des Segments pour les lignes de tableaux |
---|
118 | * Appelée automatiquement dans le constructeur |
---|
119 | * |
---|
120 | * @return void |
---|
121 | */ |
---|
122 | private function _moveRowSegments() |
---|
123 | { |
---|
124 | // Search all possible rows in the document |
---|
125 | $reg1 = "#<table:table-row[^>]*>(.*)</table:table-row>#smU"; |
---|
126 | preg_match_all($reg1, $this->contentXml, $matches); |
---|
127 | for ($i = 0, $size = count($matches[0]); $i < $size; $i++) { |
---|
128 | // Check if the current row contains a segment row.* |
---|
129 | $reg2 = '#\[!--\sBEGIN\s(row.[\S]*)\s--\](.*)\[!--\sEND\s\\1\s--\]#sm'; |
---|
130 | if (preg_match($reg2, $matches[0][$i], $matches2)) { |
---|
131 | $balise = str_replace('row.', '', $matches2[1]); |
---|
132 | // Move segment tags around the row |
---|
133 | $replace = array( |
---|
134 | '[!-- BEGIN ' . $matches2[1] . ' --]' => '', |
---|
135 | '[!-- END ' . $matches2[1] . ' --]' => '', |
---|
136 | '<table:table-row' => '[!-- BEGIN ' . $balise . ' --]<table:table-row', |
---|
137 | '</table:table-row>' => '</table:table-row>[!-- END ' . $balise . ' --]' |
---|
138 | ); |
---|
139 | $replacedXML = str_replace(array_keys($replace), array_values($replace), $matches[0][$i]); |
---|
140 | $this->contentXml = str_replace($matches[0][$i], $replacedXML, $this->contentXml); |
---|
141 | } |
---|
142 | } |
---|
143 | } |
---|
144 | /** |
---|
145 | * Fusionne les variables de template |
---|
146 | * Appelée automatiquement lors d'une sauvegarde |
---|
147 | * |
---|
148 | * @return void |
---|
149 | */ |
---|
150 | protected function _parse() |
---|
151 | { |
---|
152 | $this->contentXml = str_replace(array_keys($this->vars), array_values($this->vars), $this->contentXml); |
---|
153 | } |
---|
154 | /** |
---|
155 | * Rajoute le segment fusionné au document |
---|
156 | * |
---|
157 | * @param Segment $segment |
---|
158 | * @throws OdfException |
---|
159 | * @return odf |
---|
160 | */ |
---|
161 | public function mergeSegment(Segment $segment) |
---|
162 | { |
---|
163 | if (! array_key_exists($segment->getName(), $this->segments)) { |
---|
164 | throw new OdfException($segment->getName() . 'cannot be parsed, has it been set yet?'); |
---|
165 | } |
---|
166 | $string = $segment->getName(); |
---|
167 | // $reg = '@<text:p[^>]*>\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]<\/text:p>@smU'; |
---|
168 | $reg = '@\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]@smU'; |
---|
169 | $this->contentXml = preg_replace($reg, $segment->getXmlParsed(), $this->contentXml); |
---|
170 | return $this; |
---|
171 | } |
---|
172 | /** |
---|
173 | * Affiche toutes les variables de templates actuelles |
---|
174 | * |
---|
175 | * @return string |
---|
176 | */ |
---|
177 | public function printVars() |
---|
178 | { |
---|
179 | return print_r('<pre>' . print_r($this->vars, true) . '</pre>', true); |
---|
180 | } |
---|
181 | /** |
---|
182 | * Affiche le fichier de contenu xml du document odt |
---|
183 | * tel qu'il est à cet instant |
---|
184 | * |
---|
185 | * @return string |
---|
186 | */ |
---|
187 | public function __toString() |
---|
188 | { |
---|
189 | return $this->contentXml; |
---|
190 | } |
---|
191 | /** |
---|
192 | * Affiche les segments de boucles déclarés avec setSegment() |
---|
193 | * |
---|
194 | * @return string |
---|
195 | */ |
---|
196 | public function printDeclaredSegments() |
---|
197 | { |
---|
198 | return '<pre>' . print_r(implode(' ', array_keys($this->segments)), true) . '</pre>'; |
---|
199 | } |
---|
200 | /** |
---|
201 | * Déclare un segment pour une utilisation en boucle |
---|
202 | * |
---|
203 | * @param string $segment |
---|
204 | * @throws OdfException |
---|
205 | * @return Segment |
---|
206 | */ |
---|
207 | public function setSegment($segment) |
---|
208 | { |
---|
209 | if (array_key_exists($segment, $this->segments)) { |
---|
210 | return $this->segments[$segment]; |
---|
211 | } |
---|
212 | // $reg = "#\[!--\sBEGIN\s$segment\s--\]<\/text:p>(.*)<text:p\s.*>\[!--\sEND\s$segment\s--\]#sm"; |
---|
213 | $reg = "#\[!--\sBEGIN\s$segment\s--\](.*)\[!--\sEND\s$segment\s--\]#sm"; |
---|
214 | if (preg_match($reg, html_entity_decode($this->contentXml), $m) == 0) { |
---|
215 | throw new OdfException("'$segment' segment not found in the document"); |
---|
216 | } |
---|
217 | $this->segments[$segment] = new Segment($segment, $m[1], $this); |
---|
218 | return $this->segments[$segment]; |
---|
219 | } |
---|
220 | /** |
---|
221 | * Sauvegarde le fichier odt sur le disque |
---|
222 | * |
---|
223 | * @param string $file nom du fichier désiré |
---|
224 | * @throws OdfException |
---|
225 | * @return void |
---|
226 | */ |
---|
227 | public function saveToDisk($file = null) |
---|
228 | { |
---|
229 | if ($file !== null && is_string($file)) { |
---|
230 | if (file_exists($file) && !(is_file($file) && is_writable($file))) { |
---|
231 | throw new OdfException('Permission denied : can\'t create ' . $file); |
---|
232 | } |
---|
233 | $this->_save(); |
---|
234 | copy($this->tmpfile, $file); |
---|
235 | } else { |
---|
236 | $this->_save(); |
---|
237 | } |
---|
238 | } |
---|
239 | /** |
---|
240 | * Sauvegarde interne |
---|
241 | * |
---|
242 | * @throws OdfException |
---|
243 | * @return void |
---|
244 | */ |
---|
245 | protected function _save() |
---|
246 | { |
---|
247 | $this->file->open($this->tmpfile); |
---|
248 | $this->_parse(); |
---|
249 | if (! $this->file->addFromString('content.xml', $this->contentXml)) { |
---|
250 | throw new OdfException('Error during file export'); |
---|
251 | } |
---|
252 | foreach ($this->images as $imageKey => $imageValue) { |
---|
253 | $this->file->addFile($imageKey, 'Pictures/' . $imageValue); |
---|
254 | } |
---|
255 | $this->file->close(); // seems to bug on windows CLI sometimes |
---|
256 | } |
---|
257 | /** |
---|
258 | * Exporte le fichier en fichier attaché via HTTP |
---|
259 | * |
---|
260 | * @param string $name (optionnal) |
---|
261 | * @throws OdfException |
---|
262 | * @return void |
---|
263 | */ |
---|
264 | public function exportAsAttachedFile($name="") |
---|
265 | { |
---|
266 | $this->_save(); |
---|
267 | if (headers_sent($filename, $linenum)) { |
---|
268 | throw new OdfException("headers already sent ($filename at $linenum)"); |
---|
269 | } |
---|
270 | |
---|
271 | if( $name == "" ) |
---|
272 | { |
---|
273 | $name = md5(uniqid()) . ".odt"; |
---|
274 | } |
---|
275 | |
---|
276 | header('Content-type: application/vnd.oasis.opendocument.text'); |
---|
277 | header('Content-Disposition: attachment; filename="'.$name.'"'); |
---|
278 | readfile($this->tmpfile); |
---|
279 | } |
---|
280 | /** |
---|
281 | * retourne une variable de configuration |
---|
282 | * |
---|
283 | * @return string la variable de configuration demandée |
---|
284 | */ |
---|
285 | public function getConfig($configKey) |
---|
286 | { |
---|
287 | if (array_key_exists($configKey, $this->config)) { |
---|
288 | return $this->config[$configKey]; |
---|
289 | } |
---|
290 | return false; |
---|
291 | } |
---|
292 | /** |
---|
293 | * retourne le fichier temporaire de travail |
---|
294 | * |
---|
295 | * @return string le chemin vers le fichier temporaire de travail |
---|
296 | */ |
---|
297 | public function getTmpfile() |
---|
298 | { |
---|
299 | return $this->tmpfile; |
---|
300 | } |
---|
301 | /** |
---|
302 | * Supprime le fichier temporaire à la destruction de l'objet |
---|
303 | */ |
---|
304 | public function __destruct() { |
---|
305 | if (file_exists($this->tmpfile)) { |
---|
306 | unlink($this->tmpfile); |
---|
307 | } |
---|
308 | } |
---|
309 | } |
---|
310 | |
---|
311 | ?> |
---|