1<?php
2/**
3 * Action Plugin
4 *
5 * @license     GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author      Greg BELLAMY <garlik.crx@gmail.com> [Gag]
7 * @author      José Torrecilla <qky669@gmail.com>
8 * @version     0.11beta
9 */
10// must be run within Dokuwiki
11if(!defined('DOKU_INC')) die();
12if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
13
14class action_plugin_odtplus2dw extends DokuWiki_Action_Plugin {
15
16  /**
17  * Registers a callback function for a given event
18  */
19  function register(Doku_Event_Handler $controller) {
20    // File Parser hook
21    $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, '_parser', array());
22    // Display form hook before the wiki page (on top); Maybe create a param to display the form after the page
23    $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, '_render', array());
24    $controller->register_hook('TEMPLATE_PAGETOOLS_DISPLAY', 'BEFORE', $this, 'addbutton', array());
25    //Add MENU_ITEMS_ASSEMBLY
26    $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'addsvgbutton', array());
27  }
28
29  /**
30   * Add 'import'-button to menu
31   *
32   * @param Doku_Event $event
33   * @param mixed      $param not defined
34   */
35  public function addsvgbutton(&$event, $param) {
36    global $conf;
37
38    if($event->data['view'] == 'page') {
39      array_push($event->data['items'],new \dokuwiki\plugin\odtplus2dw\MenuItem());
40    }
41  }
42
43
44  /**
45   * Add 'import'-button to pagetools
46   *
47   * @param Doku_Event $event
48   * @param mixed      $param not defined
49   */
50  public function addbutton(&$event, $param) {
51    global $ID, $REV, $conf;
52
53    if($this->getConf('showimportbutton') && $event->data['view'] == 'main') {
54      $params = array('do' => 'odtplus2dw');
55      if($REV) {
56        $params['rev'] = $REV;
57      }
58
59      $event->data['items'] = array_slice($event->data['items'], 0, -1, true) +
60        array('import_file' =>
61          '<li>'
62          .'<a href='.wl($ID, $params).'  class="action import_file" rel="nofollow" title="'.$this->getLang('import_button').'">'
63          .'<span>'.$this->getLang('import_button').'</span>'
64          .'</a>'
65          .'</li>') +
66        array_slice($event->data['items'], -1, 1, true);
67    }
68  }
69
70  function _render(&$event, $param) {
71    ### _render : displays the upload form in the pages according to authorized action
72    # INPUT : it's a dokuwiki event function
73    # OUTPUT : void
74    # DISPLAY : upload form
75    global $ID, $lang;
76    // Check if the current action is in the action allow table
77    if ( strpos( $this->getConf('formDisplayRule'), $event->data) === false ) return;
78    // Check if the page exists
79    if ( page_exists( $ID ) && $event->data != "odtplus2dw" ) return;
80    if ( page_exists( $ID ) ) echo p_render('xhtml',p_get_instructions( $this->getLang( 'formPageExistMessage' ) ), $info );
81    // Check auth user can edit this page
82    if ( auth_quickaclcheck( $ID ) < AUTH_EDIT ) return;
83    // If all check is ok, display the form
84    $message = $this->getConf('formIntroMessage');
85    if ( $message == 'default' ) $message = $this->getLang('formIntroMessage');
86    if ($message) echo p_render('xhtml',p_get_instructions($message),$info);
87    // FIXME create the form with dokuwiki method ?
88    echo '<form method="post" action="" enctype="multipart/form-data">
89            <fieldset>
90              <legend>'.$this->getLang('formLegend').'</legend>
91              <input type="hidden" name="MAX_FILE_SIZE" value="'.$this->getConf('formMaxFileSize').'"/>
92              <input type="hidden" name="do" value="odtplus2dw"/>
93              <input type="hidden" name="id" value="'.$ID.'"/>
94              <input type="file" name="userFile"/>
95              <input type="submit" value="'.$lang['btn_upload'].'"/>
96            </fieldset>
97          </form>';
98    if ( $event->data == 'odtplus2dw' ) $event->preventDefault();
99  }
100
101  function _parser(&$event, $param) {
102    ### _parser : check if a file migth be uploaded, then call the odtplus2dw converter
103    # INPUT : it's a dokuwiki event function
104    # OUTPUT : void
105
106    // Check action is odt2dw
107    if ( $event->data != 'odtplus2dw' ) return;
108
109    ###Preparation of the message renderer
110    //Set the debug lvl
111    $this->debug = $this->getConf( 'debugLvl' );
112    //If used, open the logFile
113    if ( $this->debug >= 2 ) {
114      $this->logFile = $this->getConf( 'logFile' );
115      if ( isset( $this->logFile ) ) if ( file_exists( dirname( $this->logFile ) ) || mkdir( dirname( $this->logFile ) ) ) {
116        if ( ! ( $this->logFileHandle = @fopen( $this->logFile, 'a' ) ) ) unset( $this->logFileHandle, $this->logFile );
117      } else unset( $this->logFile );
118      if ( ! isset( $this->logFileHandle ) ) $this->_msg( 'er_logFile' );
119    }
120    ###
121
122    // Check upload file defined
123    $retour = false;
124    if ( $_FILES['userFile'] ) {
125      // If parse work, change action to defined one in conf/local.php file
126      $retour = $this->_odtplus2dw();
127      # Delete temp file
128      $this->_purge_env();
129    }
130    //if the file is correctly parsed, change the action to the action defined in the conf
131    //otherwise the action stay odt2dw -> the display form hook will be call by render trigger
132    if ( $retour === true ) {
133      $event->data = $this->getConf('parserPostDisplay');
134    } else {
135      $event->preventDefault();
136    }
137
138    ### Clear the message renderer
139    // Close the log file if used
140    if ( isset( $this->logFileHandle ) ) @fclose( $this->logFileHandle );
141    ###
142  }
143
144  function _odtplus2dw() {
145    ### _odtplus2dw : Translate a supported file into dokuwiki syntax
146    # OUTPUT :
147    #   * true -> process successfully finished
148    #   * false -> something wrong; using _msg to display what's wrong
149
150    global $ID, $conf;
151
152    //Table use to convert urn to url -> without this, xslProc won't parse correctly
153    //Table corrigeant les attributs de la racine du fichier content.xml : urn -> url
154    $this->conversion = array(
155      "xmlns:office" => "http://openoffice.org/2000/office",
156      "xmlns:style" => "http://openoffice.org/2000/style",
157      "xmlns:text" => "http://openoffice.org/2000/text",
158      "xmlns:table" => "http://openoffice.org/2000/table",
159      "xmlns:draw" => "http://openoffice.org/2000/drawing",
160      "xmlns:fo" => "http://www.w3.org/1999/XSL/Format",
161      "xmlns:xlink" => "http://www.w3.org/1999/xlink",
162      "xmlns:dc" => "http://purl.org/dc/elements/1.1/",
163      "xmlns:meta" => "http://openoffice.org/2000/meta",
164      "xmlns:number" => "http://openoffice.org/2000/datastyle",
165      "xmlns:svg" => "http://www.w3.org/2000/svg",
166      "xmlns:chart" => "http://openoffice.org/2000/chart",
167      "xmlns:dr3d" => "http://openoffice.org/2000/dr3d",
168      "xmlns:math" => "http://www.w3.org/1998/Math/MathML",
169      "xmlns:form" => "http://openoffice.org/2000/form",
170      "xmlns:script" => "http://openoffice.org/2000/script",
171      "xmlns:config" => "http://openoffice.org/2001/config",
172      "xmlns:ooo" => "http://openoffice.org/2004/office",
173      "xmlns:ooow" => "http://openoffice.org/2004/writer",
174      "xmlns:oooc" => "http://openoffice.org/2004/calc",
175      "xmlns:dom" => "http://www.w3.org/2001/xml-events",
176      "xmlns:xforms" => "http://www.w3.org/2002/xforms",
177      "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
178      "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
179      "xmlns:rpt" => "http://openoffice.org/2005/report",
180      "xmlns:of" => "urn:oasis:names:tc:opendocument:xmlns:of:1.2",
181      "xmlns:xhtml" => "http://www.w3.org/1999/xhtml",
182      "xmlns:grddl" => "http://www.w3.org/2003/g/data-view#",
183      "xmlns:tableooo" => "http://openoffice.org/2009/table",
184      "xmlns:css3t" => "http://www.w3.org/TR/css3-text/"
185    );
186    // urn wont be/need to convert -- keep for further odt file version
187    // "xmlns:field" => "urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
188    //"xmlns:formx" => "urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
189    // CONSTANT : Content file extract from the odt file
190    $this->xmlFile = "content.xml";
191
192    ### Check parameter ###
193
194    // Page receive content
195    if ( ! $this->pageName = $ID ) return $this->_msg('er_id');
196    $this->nsName = getNS($this->pageName);
197    // Check right to change the page
198    if ( page_exists($ID) ) {
199      if ( auth_quickaclcheck($ID) < AUTH_EDIT ) return $this->_msg('er_acl_edit');
200    } else {
201      if ( auth_quickaclcheck($ID) < AUTH_CREATE ) return $this->_msg('er_acl_create');
202    }
203
204    // Check the file uploaded
205    if ( ! $this->_checkUploadFile() ) return $this->_msg('er_checkUploadResult');
206
207    // Check the xslFile
208    if ( ! $this->getConf( 'parserXslFile' ) )  return $this->_msg('er_xslFile_notset');
209    $this->xslFile = DOKU_PLUGIN.'odtplus2dw/'.$this->getConf('parserXslFile');
210    if ( ! file_exists($this->xslFile) ) return $this->_msg('er_xslFile_exists');
211    if ( ! is_file($this->xslFile) ) return $this->_msg('er_xslFile_isfile');
212
213    // Class Control
214    if ( ! class_exists( "XSLTProcessor" ) ) return $this->_msg('er_class_xsltProcessor');
215    if ( ! class_exists( "ZipArchive" ) ) return $this->_msg('er_class_zipArchive');
216    if ( ! class_exists( "DOMDocument" ) ) return $this->_msg('er_class_domDocument');
217    // Create instance of needed class
218    $this->XSLT = new XSLTProcessor;
219    $this->ZIP  = new ZipArchive;
220    $this->XSL  = new DOMDocument;
221    $this->XML  = new DOMDocument;
222
223    // Load the xslFile
224    if ( ! ($this->XSL->load( $this->xslFile ) ) ) return $this->_msg('er_loadXsl');
225    // Build the xsl processor
226    if ( ! $this->_set_xsltProcessor() ) return $this->_msg('er_xsltProc');
227    // Extract content file from odt file
228    if ( ! $this->_unzip( $this->xmlFile ) ) return $this->_msg('er_file_unzip');
229    // Load the xmlFile
230    if ( ! $this->XML->load($this->uploadDir.'/'.$this->xmlFile) ) return $this->_msg('er_loadXml');
231    if ( ! $this->racine = $this->XML->getElementsByTagName('document-content')->item(0) ) return $this->_msg('er_invalidRoot');
232    // Correction for urn bug
233    foreach ( $this->conversion as $attr => $value ) if ( $this->racine->hasAttribute($attr) ) $this->racine->setAttributeNS( "http://www.w3.org/2000/xmlns/", $attr, $value );
234    // Transformation du fichier XML
235    $this->result = '====== '.basename($this->odtFileName,'.odt').' ======
236';
237    if ( $this->getConf('parserLinkToOriginalFile') && auth_quickaclcheck($ID) >= AUTH_UPLOAD ) $this->result .= '<sub>{{'.$this->userFileName.'|'.$this->getLang('parserOriginalFile').'}}</sub>
238
239';
240
241    ### Parameters have been checked successfully ###
242
243
244    // Set specific time out to parse the odt file into dw syntax
245    set_time_limit( $this->getConf('parserCoreTimeOut') );
246    // Parse the content - This is the CORE
247    if ( ! $tmp = html_entity_decode($this->XSLT->transformToDoc( $this->XML )->saveHTML(), ENT_COMPAT, 'UTF-8') ) return $this->_msg('er_transform');
248    $this->result .= $tmp;
249    // Set the time out to default
250    set_time_limit(30);
251    // Extract and store image files from odt file to Dokuwiki mediaManager
252    $this->_parse_image();
253
254    // Store the result
255    if ( ! $this->_apply_result() ) return $this->_msg('er_apply');
256
257    return true;
258  }
259
260  function _msg( $message, $type=null, $force=false ) {
261    ### _msg : display message using the debugLvl value
262    # $message : mixed :
263    #   * string : key for $this->getLang() function
264    #   * array :
265    #       $message[0] : string : key for $this->getLang() function
266    #       $message[1] : string : additional information
267    # $type : integer : (check the dokuwiki msg function)
268    #   * -1 : error message
269    #   *  0 : normal message
270    #   *  1 : info message
271    # if type == null, the first 3 char of the key define the message type
272    #   * er_ : -1
273    #   * ok_ :  1
274    #   * otherwise : 0
275    # $force : boolean : force displaying the message without checking debugLvl
276    # OUTPUT :
277    #   * true -> display a normal message
278    #   * false -> display an error message
279    # DISPLAY : call dokuwiki msg function
280    if ( is_array( $message ) ) {
281      $output = $message[0];
282    } else {
283      $output = $message;
284    }
285    // If output is empty, crash with error display;
286    if ( ! $output ) die( $this->getLang( 'er_msg_nomessage' ) );
287    if ( is_null( $type ) ) {
288      $val = substr( $output, 0, strpos( $output, '_' )+1 );
289      switch ($val) {
290        case 'er_' :
291          $err = -1;
292          break;
293        case 'ok_' :
294          $err = 1;
295          break;
296        default :
297          $err = 0;
298      }
299    } else {
300      if ( $type < -1 || $type > 1 ) return false;
301      $err = $type;
302    }
303    // Dev debugging mode; manually set to 4; this dirtily display some informations
304    if ( $this->debug > 3 ) echo '<p>message : '.$message.' |output : '.$output.' |val : '.$val.' |err : '.$err.'</p>';
305
306    // Debug = 0 => No message
307    if ( !$force && $this->debug == 0 ) return ( $err == -1 ? false : true );
308
309    // Debug < 3 => Only error message; If it s not an error message, message return true;
310    if ( !$force && $err != -1 && $this->debug < 3 ) return true;
311    // Otherwise display the message
312    $content = $output.' : '.$this->getLang( $output ).( is_array( $message ) ? ' : '.$message[1] : '' );
313    msg( 'odtplus2dw : '.$content, $err );
314    if ( isset( $this->logFileHandle ) ) fwrite( $this->logFileHandle, date(DATE_ATOM).':'.$_SERVER['REMOTE_USER'].':'.$content.'
315' );
316    // If error message, return false
317    if ( $err == -1 ) return false;
318    // Otherwise return true;
319    return true;
320  }
321
322
323
324  function _checkUploadFile() {
325    ### _checkUploadFile : group all process about the uploadFile, like uploadStatus, file format, move it in a working directory, etc. ###
326    # OUTPUT :
327    #   * true -> process successfully
328    #   * false -> something wrong; using _msg to display what's wrong
329    // Check a file will be upload
330    if ( ! $_FILES['userFile'] ) return $this->_msg('er_file_miss');
331    // Check the file status
332    if ( $_FILES['userFile']['error'] > 0 ) return $this->_msg( array( 'er_file_upload', $_FILES['userFile']['error'] ) );
333    // Check the file has an authorized mimetype
334    if ( $this->getConf( 'parserMimeTypeAuthorized' ) != "" && strpos( $this->getConf( 'parserMimeTypeAuthorized' ), $_FILES['userFile']['type'] ) === false ) return $this->_msg( array( 'er_file_format', $_FILES['userFile']['type'] ) );
335
336    // Create an unique temp work dir name
337    while ( file_exists( $this->uploadDir = $this->getConf( 'parserUploadDir' ).rand( 10000, 100000 ) ) ) {};
338    // Create the directory
339    if ( ! mkdir( $this->uploadDir, 0777, true ) ) return $this->_msg( 'er_file_tmpDir' );
340    // Chmod. Maybe not required, but we keep it beacause using soffice sometimes is not easy...
341    chmod( $this->uploadDir, 0777 );
342    // Move the upload file into the work directory
343    $this->userFileName = $_FILES['userFile']['name'];
344    $this->userFile = $this->uploadDir.'/'.$this->userFileName;
345    if ( ! move_uploaded_file( $_FILES['userFile']['tmp_name'], $this->userFile ) ) return $this->_msg('er_file_getFromDownload');
346
347    // Pandoc/SOffice support: Set odtFile/odtFileName to be the same as userFile/userFileName.
348    // Will change later if neccesary
349    $this->odtFileName = substr($this->userFileName, 0);
350    $this->odtFile = substr($this->userFile, 0);
351
352    // Add Pandoc support
353    if ( $this->getConf( 'parserMimeTypePandoc' ) != "" && strpos( $this->getConf( 'parserMimeTypePandoc' ), $_FILES['userFile']['type'] ) !== false ) {
354
355      $this->_prepareOdtFileName();
356
357      $output = array();
358      // Conversion to odt file
359      exec( 'pandoc -s -w odt -o "' . $this->odtFile . '" "' . $this->userFile . '"', $output, $return_var );
360    }
361
362    // Add SOffice support
363    if ( $this->getConf( 'parserMimeTypeSOffice' ) != "" && strpos( $this->getConf( 'parserMimeTypeSOffice' ), $_FILES['userFile']['type'] ) !== false ) {
364
365      $this->_prepareOdtFileName();
366
367      $output = array();
368      // Conversion to odt file
369      exec( 'cd ' . $this->uploadDir . ' && sudo soffice --nofirststartwizard --headless --convert-to odt:"writer8" "' . $this->userFileName . '"', $output, $return_var );
370    }
371
372    // All upload file checking are OK
373    return true;
374  }
375
376  function _prepareOdtFileName() {
377    // Change original extension to ".odt"
378    $info = pathinfo($this->userFile);
379    $this->odtFileName = $info['filename'] . '.odt';
380    $this->odtFile = $this->uploadDir.'/'. $this->odtFileName;
381  }
382
383  function _purge_env() {
384    ### _purge_env : clean the system from temporary file ###
385    # OUTPUT :
386    #   void
387    # Display some error message if something wrong in the delete process (might delete the file manually)
388
389    // Perhaps this would not be needed if use temp dir.
390    // No timeOut : the cleanning process wont be interrupted.
391    set_time_limit(0);
392    // use @ to catch the system error message
393    // If exists, delete the download file
394    if ( file_exists( $this->odtFile ) ) if ( ! @unlink( $this->odtFile ) ) $this->_msg( array( 'er_pg_file', $this->odtFile ) );
395    if ( file_exists( $this->userFile ) ) if ( ! @unlink( $this->userFile ) ) $this->_msg( array( 'er_pg_file', $this->userFile ) );
396    // Delete each file extracted for the uploaded file
397    if ( $this->file_extract ) foreach ($this->file_extract as $file) if ( file_exists( $file ) ) if ( ! @unlink( $file ) ) $this->_msg( array( 'er_pg_file', $file ) );
398    // Delete each image than would be renamed and not moved to the wiki
399    if ( $this->file_import ) foreach ( $this->file_import as $file ) if ( file_exists( $this->uploadDir.'/'.$this->pictpath.'/'.$file ) ) if ( ! @unlink( $this->uploadDir.'/'.$this->pictpath.'/'.$file ) ) $this->_msg( array( 'er_pg_file', $this->uploadDir.'/'.$this->pictpath.'/'.$file ) );
400    // Delete the Pictures directory
401    if ( file_exists( $this->uploadDir.'/'.$this->pictpath) ) if ( ! @rmdir( $this->uploadDir.'/'.$this->pictpath ) ) $this->_msg( array( 'er_pg_dir', $this->uploadDir.'/'.$this->pictpath ) );
402    // Than delete the temporary directory
403    if ( file_exists( $this->uploadDir ) ) if ( ! @rmdir( $this->uploadDir ) ) $this->_msg( array( 'er_pg_dir', $this->uploadDir ) );
404    // Set back default timeOut
405    set_time_limit(30);
406  }
407
408  function _set_xsltProcessor(){
409    ### _set_xsltProcessor : set all xslt param regarding the dokuwiki plugin installed ###
410    # OUTPUT :
411    #   * true -> process successfully
412    #   * false -> something wrong; using _msg to display what's wrong
413    # _msg info report ( debugLvl >= 2 ) display message about active plugin
414
415    // Gag : I think it s a Nasty way to check plugin - must be rewriten but i don t know how
416    $tmp_plugin_lst = plugin_list();
417    if ( ! $this->XSLT->importStylesheet( $this->XSL ) ) return $this->_msg('er_xslt_invalid');
418    foreach ( array('numberedheadings') as $param ) if ( array_search( $param, $tmp_plugin_lst ) !== false ) {
419      if ( ! $this->XSLT->setParameter( '', $param, '1' ) ) return $this->_msg( array( 'inf_xslt_param', $param ), -1 );
420      // _msg info report
421      $this->_msg( array( 'ok_infoPlugin', $param ), 1 );
422    }
423    //
424    foreach ( array('subtable_message') as $lang_elt ) if ( ! $this->XSLT->setParameter( '', $lang_elt, $this->getLang('xsl_'.$lang_elt ) ) ) $this->_msg( array( 'inf_xslt_lang', $param ), 0 );
425    return true;
426  }
427
428  function _apply_result() {
429    ### _apply_result : store the content in dokuwiki page and the attache file (img) in dokuwiki media
430    # OUTPUT :
431    #   * true -> process successfully
432    #   * false -> something wrong; using _msg to display what's wrong
433    global $INFO;
434    // Save the content in data/page
435    saveWikiText( $this->pageName, $this->result, $this->getLang( 'parserSummary' ).$this->userFileName );
436    if ( ! page_exists($this->pageName) ) return $this->_msg('er_apply_content');
437    // Check if the user could upload file (ACL : permission lvl 8)
438    if ( auth_quickaclcheck($ID) >= AUTH_UPLOAD ) {
439      // Import the image file in the mediaManager (data/media)
440      $destDir = mediaFN( $this->nsName );
441      if ( ! ( file_exists( $destDir ) || mkdir( $destDir, 0777, true ) ) ) return $this->_msg( array( 'er_apply_dirCreate' ) );
442      if ( $this->file_import ) foreach ( $this->file_import as $pict ) {
443        $destFile = mediaFN( $this->nsName.':'.$pict );
444        list( $ext, $mime ) = mimetype( $this->uploadDir.'/'.$this->pictpath.'/'.$pict );
445        if ( media_upload_finish($this->uploadDir.'/'.$this->pictpath.'/'.$pict, $destFile, $this->nsName, $mime, @file_exists($destFile), 'rename' ) != $this->nsName ) return $this->_msg( array( 'er_apply_img', $this->uploadDir.'/'.$this->pictpath.'/'.$pict ) );
446      }
447      // Keep the original file (import the upload file in the mediaManager)
448      $destFile = mediaFN( $this->nsName.':'.$this->userFileName );
449      list( $ext, $mime ) = mimetype( $this->uploadDir.'/'.$this->userFileName );
450      if ( media_upload_finish($this->uploadDir.'/'.$this->userFileName, $destFile, $this->nsName, $mime, @file_exists($destFile), 'rename' ) != $this->nsName ) return $this->_msg( array( 'er_apply_file' ) );
451    } else {
452      // If not allowed to upload, display a message.
453      $this->_msg( 'inf_acl_upload', 0, true );
454    }
455    # Refresh info about the current page (see doku.php where $INFO is initiate) - Needed for edit or preview "parserPostDisplay" option
456    $INFO = pageinfo();
457    return true;
458  }
459
460  function _parse_image() {
461    ### _parse_image : search dokuwiki img markup in $this->result than extract the img file and rename it to easier name ###
462    # OUTPUT :
463    #   void
464    # using _msg to display each img file wont be process successfully
465
466    global $ID;
467    $imgs = array();
468    if ( preg_match_all( '|{{((?:[^/}]+/)*[^/}]+)/([0-9a-zA-Z]+)(\.[a-z]+)(\?[0-9]+(?:x[0-9]+)?)?}}|', $this->result, $imgs, PREG_SET_ORDER ) ) {
469      if ( auth_quickaclcheck( $ID ) < AUTH_UPLOAD ) return $this->_msg( 'er_acl_upload' );
470      $this->err['ok'] = array();
471      foreach ( $imgs as $key => $value ) {
472        set_time_limit(20);
473        $this->pictpath = $value[1];
474        $pict = $value[2].$value[3];
475        $ext  = $value[3];
476        $other = $value[4];
477        if ( $this->_unzip($this->pictpath.'/'.$pict) ) {
478          # Do not overwrite existing images
479	        # Hash to see if files are identical, prevents multiple files with same content but different names in media manager
480          $newFileHash = hash_file('sha512', $this->uploadDir.'/'.$this->pictpath.'/'.$pict);
481          $newFileName = '';
482          do {
483            $newFileName = noNS($this->pageName).'_image_'.$key.$ext;
484            $existingFile = mediaFN( $this->nsName.':'.$newFileName);
485            $key = $key + 1;
486          } while ( file_exists( $existingFile ) && hash_file('sha512', $existingFile) != $newFileHash );
487
488          if ( rename( $this->uploadDir.'/'.$this->pictpath.'/'.$pict, $this->uploadDir.'/'.$this->pictpath.'/'.$newFileName ) ) {
489            $this->result = str_replace( '{{'.$this->pictpath.'/'.$pict.$other.'}}' , '{{'.$newFileName.$other.'}}' , $this->result );
490            $this->file_import[] = $newFileName;
491            if ( $this->debug ) $this->err['ok'][] = $pict.' : '.$newFileName;
492          } else $this->err[$pict] = 'rename';
493        } else $this->err[$pict] = 'unzip';
494      }
495    }
496    if ( $this->err ) foreach ( $this->err as $key => $value ) {
497      switch ( $key ) {
498        case 'ok':
499          foreach ( $value as $msg ) $this->_msg( array( 'ok_img', $msg ) );
500          break;
501        default :
502          // $value E ( rename, unzip) => er_img_rename, er_img_unzip
503          $this->_msg( array( 'er_img_'.$value, $key ) );
504      }
505    }
506  }
507
508  function _unzip( $entrie ) {
509    ### _unzip : extract $entrie file from $this->odtFile to $this->uploadDir using $this->ZIP object instance of ZipArchive Class ###
510    # $entrie : string : fullFileName (with the internal path in the archive)
511    # OUTPUT :
512    #   * true -> extraction ok
513    #   * false -> something wrong; using _msg to display what's wrong
514
515    if ( ! $this->ZIP ) return $this->_msg('er_unzip_object');
516    if ( ! file_exists( $this->odtFile ) ) return $this->_msg('er_unzip_nofile');
517    if ( ! ( $this->ZIP->open( $this->odtFile ) === true ) ) return $this->_msg( 'er_unzip_open' );
518    $res = $this->ZIP->extractTo( $this->uploadDir, $entrie );
519    $this->ZIP->close();
520    if ( ! $res ) return $this->_msg( array( 'er_unzip_error', $entrie ) );
521    $this->file_extract[] = $this->uploadDir.'/'.$entrie;
522    return $this->_msg( array( 'ok_unzip', $entrie ) );
523  }
524
525}
526