source: spip-zone/_plugins_/convertisseur/extract/postoffice/class.DOCX-HTML.php @ 57290

Last change on this file since 57290 was 57290, checked in by erational@…, 8 years ago

doc2spip: correction pb chemin sur les images extraites qui tombaient à la racine du site, on les place ds IMG/convertisseur/

File size: 25.7 KB
Line 
1<?php
2/**CLASS DOCXtoHTML Premium will convert a .docx file to (x)html.
3 *
4 * This class uses the dUnzip2 class from phpclasses.org and requires extension ZLib
5 * This class can only handle 1 single file per instance.
6 * This class might overrun the execution time limit.
7 *
8 * Variables that can be set:
9 * @see *$docxPath
10 * @see $paragraphs
11 * @see *$image_max_width
12 * @see $mediaDir
13 * @see $imagePathPrefix
14 *
15 * Variables Returned by Class:
16 * @see $status
17 * @see $output
18 * @see $error
19 * @see $time
20 */
21class DOCXtoHTML {
22    /**
23     * @var String This is the path to the file that should be read
24     * @since 1.0
25     */
26    var $docxPath = "";
27    /**
28     * @var String This is where the ziped contents will be extracted to to process
29     * @since 1.0
30     */
31    var $tempDir = "";
32    /**
33     *
34     * @var String This is the html data that is returned from this class
35     * @since 1.0
36     */
37    var $output = array();
38    /**
39     *
40     * @var Bool Should colored text be allowed or not
41     * @since 1.0
42     */
43    var $allowColor = false;
44    /**
45     * @var Int This is the maximum width of an image after the process
46     * @since 1.0
47     * @update 1.2
48     */
49    var $image_max_width = 0;
50    /**
51     * @var String The path to where the content is extracted
52     * @since 1.0
53     */
54    var $content_folder = "";
55    /**
56     * @var String The current Status of the class
57     * @since 1.0
58     */
59    var $status = "";
60    /**
61     * @var String The path to where the media files of the document should be extracted
62     * @since 1.0
63     */
64    var $mediaDir = "";
65    /**
66     * @var String The value of this variable will be prefixed to the path of the image. This class will create a folder 2 levels up, inside an 'upload' folder and this value should go to there.
67     * @since 1.0
68     */
69    var $imagePathPrefix = "";
70    /**
71     * @var Array This contains the relationships of different elements inside the word document and is used to link to the correct image.
72     * @since 1.1
73     */
74    var $rels = array();
75    /**
76     * @val String The error number generated and the meaning of the error
77     * @since 1.0
78     */
79    var $error = NULL;
80    /**
81     * @val String This will contain the closing tag of a paragraph level opened tag that can't be specified explicitly
82     * @since 1.1
83     */
84    var $tagclosep = "";
85    /**
86     * @val String This will contain the closing tag of a text opened tag that can't be specified explicitly
87     * @since 1.1
88     */
89     var $tagcloset = "";
90    /**
91     * @val Bool SWhould a thumbnail be created as well as to keep the original image in the folder
92     * @since 1.3
93     */
94     var $keepOriginalImage = false;
95    /**
96     * @val Bool Split the document into multiple posts/pages
97     * @since 1.3
98     */
99     var $split = false;
100
101    /**
102     * This function will set the status to Ready when the class is called. The Constructor Method.
103     * @return Bool True when ready
104     * @since 1.0
105     */
106    function __construct(){
107        $this->status = "Ready";
108        return true;
109    }
110    /**
111     * This function call the Constructor Method
112     * @return Bool True when ready
113     * @since 1.0
114     */
115    function DOCXtoHTML(){
116        return __construct();
117    }
118    /**
119     * This function will initialize the process as well as handle the process automatically.
120     * This requires that the vars be set to start
121     * @return Bool True when successfully completed
122     * @since 1.0
123     * @modified 1.2.3
124     */
125    function Init(){
126        global $PostOffice;
127        if($this->extractRelXML()==false){
128            $this->DeleteTemps();
129            $PostOffice->__destruct();
130            $this->error = "12. The file data could not be found or read.";
131            return false;
132        }
133        if($this->extractMedia()==false){
134            $this->DeleteTemps();
135            $PostOffice->__destruct();
136            $this->error = "13. The Media could not be found.";
137            return false;
138        }
139        if($this->extractXML()==false){
140            $this->DeleteTemps();
141            $PostOffice->__destruct();
142            $this->error = "14. The file data could not be found or read.";
143            return false;
144        }
145        if($this->DeleteTemps()==false){
146            $PostOffice->__destruct();
147            $this->error = "15. The temporary files created during the process could not be deleted.
148                The contents, however, might still have been extracted.";
149            return false;
150        }
151        return true;
152    }
153    /**
154     * This function handles the extraction of the XML building the Rels array
155     * @return Bool True on success
156     * @since 1.1
157     * @modified 1.2.3
158     */
159    function extractRelXML(){
160        $xmlFile = $this->tempDir."/word/_rels/document.xml.rels";
161        if(is_file($xmlFile)){
162            $xml = file_get_contents($xmlFile);
163        } else {
164            echo "the file ".$xmlFile." could not be found<br /><br />";
165            return false;
166        }
167        if($xml == false){
168            return false;
169        }
170        $xml = mb_convert_encoding($xml, 'UTF-8', mb_detect_encoding($xml));
171        $parser = xml_parser_create('UTF-8');
172        $data = array();
173        xml_parse_into_struct($parser, $xml, $data);
174        foreach($data as $value){
175            if($value['tag']=="RELATIONSHIP"){
176                //it is an relationship tag, get the ID attr as well as the TARGET and (if set, the targetmode)set into var.
177                if(isset($value['attributes']['TARGETMODE'])){
178                    $this->rels[$value['attributes']['ID']] = array(0 => $value['attributes']['TARGET'], 3=> $value['attributes']['TARGETMODE']);
179                } else {
180                    $this->rels[$value['attributes']['ID']] = array(0 => $value['attributes']['TARGET']);
181                }
182            }
183        }
184        return true;
185    }
186    /**
187     * This function handles the extraction of the Media
188     * @return Bool True on success
189     * @since 1.1
190     * @modified 1.2.3
191     */
192    function extractMedia(){
193        $wordFolder = $this->tempDir."/word/";
194        if(!is_dir($wordFolder."media")){
195            return true;
196            //there are no images to extract
197        }
198        $this->getMediaFolder();
199        $i = false;
200        foreach($this->rels as $key => $value){
201            if(strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="png" || strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="gif" || strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="jpg"
202                || strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="jpeg"){
203                //this really is an image that we are working with
204                $fileType = strtolower(pathinfo($value[0],PATHINFO_EXTENSION));
205                //set the file type so that the correct image creation function can be called
206                if(is_file($wordFolder.$value[0])){
207                    if($this->keepOriginalImage == true){
208                        $image = $this->processImage($wordFolder.$value[0], $this->image_max_width);
209                        $imageorr = $this->processImage($wordFolder.$value[0]);
210                        /*if( imageSx( $imageorr ) == $this->image_max_width ){
211                            //they are the extact same width, so it is not needed to create it again
212                            imageDestroy ( $imageorr );
213                            $imageorr = false;
214                        }*/
215                    } else {
216                        $image = $this->processImage($wordFolder.$value[0], $this->image_max_width);
217                        $imageorr = false;
218                    }
219                    if($image){
220                        $i = true;//this have been resourceful, do not return false
221                        //the image was successfully created, now write to file
222                        $filename = pathinfo($value[0],PATHINFO_BASENAME);
223                        if($fileType=="png"){
224                            if(imagePng($image,$this->mediaDir."/".$filename,0,PNG_NO_FILTER)){
225                                imagedestroy($image);
226                                $this->rels[$key][1] = $this->mediaDir."/".$filename;
227                            }
228                        } elseif($fileType=="gif"){
229                            if(imageGif($image,$this->mediaDir."/".$filename,0)){
230                                imagedestroy($image);
231                                $this->rels[$key][1] = $this->mediaDir."/".$filename;
232                            }
233                        } else {
234                            if(imageJpeg($image,$this->mediaDir."/".$filename,100)){
235                                imagedestroy($image);
236                                $this->rels[$key][1] = $this->mediaDir."/".$filename;
237                            }
238                        }
239                    }
240                    if($imageorr){
241                        $i = true;//this have been resourceful, do not return false
242                        //the image was successfully created, now write to file
243                        $pathinfo = pathinfo($value[0]);
244                        $filename = $pathinfo['filename']."_big.".$pathinfo['extension'];
245                        if($fileType=="png"){
246                            if(imagePng($imageorr,$this->mediaDir."/".$filename,0,PNG_NO_FILTER)){
247                                imagedestroy($imageorr);
248                                $this->rels[$key][2] = $this->mediaDir."/".$filename;
249                            }
250                        } elseif($fileType=="gif"){
251                            if(imageGif($imageorr,$this->mediaDir."/".$filename,0)){
252                                imagedestroy($imageorr);
253                                $this->rels[$key][2] = $this->mediaDir."/".$filename;
254                            }
255                        } else {
256                            if(imageJpeg($imageorr,$this->mediaDir."/".$filename,100)){
257                                imagedestroy($imageorr);
258                                $this->rels[$key][2] = $this->mediaDir."/".$filename;
259                            }
260                        }
261                    }
262                }
263            }
264        }
265        return $i;
266    }
267    /**
268     * This function creates the folder that will contain the media after the move
269     * @return Bool True on success
270     * @since 1.0
271     */
272    function getMediaFolder(){
273        if(empty($this->content_folder)){
274            $mediaFolder = pathinfo($this->docxPath,PATHINFO_BASENAME);
275            $ext = pathinfo($this->docxPath,PATHINFO_EXTENSION);
276            $MediaFolder = strtolower(str_replace(".".$ext,"",str_replace(" ","-",$mediaFolder)));
277            $this->mediaDir = "../../uploads/media/".$MediaFolder;             
278        } else {
279           //$this->mediaDir = "../../uploads/mediaDDD55/".$this->content_folder;
280           $this->mediaDir = "../IMG/".$this->content_folder;           
281        }
282        if($this->mkdir_p($this->mediaDir)){
283            return true;
284        } else {
285            return false;
286        }
287    }
288    /**
289     * This function handles the image proccessing
290     * @param String $url Path to the file to proccess
291     * @param Int $thumb The maximum width of an proccessed image
292     * @return String The binary of the image that was created
293     * @since 1.0
294     */
295    function processImage($url, $thumb=0) {
296        $tmp0 = imageCreateFromString(fread(fopen($url, "rb"), filesize( $url )));
297        if ($tmp0) {
298            if($thumb == 0) {
299                $dim = Array ('w' => imageSx($tmp0), 'h' => imageSy($tmp0));
300            } else {
301                if(imagesx($tmp0)<=$thumb){
302                    if (imageSy($tmp0) > imageSx($tmp0)){
303                        $dim = Array ('w' => imageSx($tmp0), 'h' => imageSy($tmp0));
304                    } else {
305                        $dim = Array ('w' => imageSx($tmp0), 'h' => imageSy($tmp0));
306                    }
307                } else {
308                    $dim = Array ('w' => $thumb, 'h' => round(imageSy($tmp0)*$thumb/imageSx($tmp0)));
309                }
310            }
311            $tmp1 = imageCreateTrueColor ( $dim [ 'w' ], $dim [ 'h' ] );
312            if ( imagecopyresized  ( $tmp1 , $tmp0, 0, 0, 0, 0, $dim [ 'w' ], $dim [ 'h' ], imageSx ( $tmp0 ), imageSy ( $tmp0 ) ) ) {
313                imageDestroy ( $tmp0 );
314                return $tmp1;
315            } else {
316                imageDestroy ( $tmp0 );
317                imageDestroy ( $tmp1 );
318                return $this -> null;
319            }
320        } else {
321            return $this -> null;
322        }
323    }
324    /**
325     * This function handles the extraction of the XML file data used to construct the HTML
326     * @return Bool True on success
327     * @since 1.0
328     * @modified 1.2.3
329     */
330    function extractXML(){
331        $xmlFile = $this->tempDir."/word/document.xml";
332        $xml = file_get_contents($xmlFile);
333        if($xml == false){
334            return false;
335        }
336        $xml = mb_convert_encoding($xml, 'UTF-8', mb_detect_encoding($xml));
337        $parser = xml_parser_create('UTF-8');
338        $data = array();
339        xml_parse_into_struct($parser, $xml, $data);
340        //echo "<pre>";
341        //print_r($data);
342        //echo "</pre>";
343        $html4output = "";
344        $i = 0;//var of post part id
345        $h1 = 0;//var of heading 1s done
346        foreach($data as $key => $value){
347            if(is_array($value)){
348                switch ($value['tag']) {
349                    case "W:P"://the paragrah begins
350                        if($value['type'] == "open"){
351                            $html4output .= "<p>";
352                        } elseif($value['type'] == "close"){
353                            $html4output .= $this->tagclosep."</p>";
354                            $this->tagclosep = "";
355                        }
356                        break;
357                    case "W:TBL"://the table is initiated
358                        if($value['type'] == "open"){
359                            $html4output .= "<table border='1'>";
360                        } elseif($value['type'] == "close"){
361                            $html4output .= "</table>";
362                        }
363                        break;
364                    case "W:TR"://the table row is initiated
365                        if($value['type'] == "open"){
366                            $html4output .= "<tr>";
367                        } elseif($value['type'] == "close"){
368                            $html4output .= "</tr>";
369                        }
370                        break;
371                    case "W:TC"://the table cell is initiated
372                        if($value['type'] == "open"){
373                            $html4output .= "<td>";
374                        } elseif($value['type'] == "close"){
375                            $html4output .= "</td>";
376                        }
377                        break;
378                    case "W:HYPERLINK"://the hyperlink is initiated
379                        if($value['type'] == "open"){
380                            if(array_key_exists('R:ID', $value['attributes'])){
381                                $rid = $value['attributes']['R:ID'];
382                                $path = $this->rels[$rid][0];
383                                $target = $this->rels[$rid][3];
384                            } else {
385                                $target = false;
386                            }
387                            //now determine which type of link it is
388                            if(strtolower($target) == "external"){
389                                //this is an external link to a website
390                                $html4output .= "<a href='".$path."'>";
391                            } elseif(isset($value['attributes']['W:ANCHOR'])){
392                                $html4output .= "<a href='#".$value['attributes']['W:ANCHOR']."'>";
393                            }
394                        } elseif($value['type'] == "close"){
395                            $html4output .= "</a>";
396                        }
397                        break;
398                    case "A:BLIP"://the image data
399                        if($value['type'] == "open" || $value['type'] == "complete"){
400                            $rid = $value['attributes']['R:EMBED'];
401                            $imagepath = $this->rels[$rid][1];
402                            if(array_key_exists(2,$this->rels[$rid])){
403                                $imagebigpath = $this->rels[$rid][2];
404                            } else {
405                                $imagebigpath = false;
406                            }
407                            if($this->keepOriginalImage == true && $imagebigpath){
408                                $html4output .= "<a href='".$this->imagePathPrefix.$imagebigpath."' target='_blank' >
409                                    <img style='display:inline;' src='".$this->imagePathPrefix.$imagepath."' alt='' />
410                                    </a>";
411                            } else {
412                                $html4output .= "<img style='display:inline;' src='".$this->imagePathPrefix.$imagepath."' alt='' />";
413                            }
414                        }
415                        break;
416                    case "W:T":
417                        if($value['type'] == "complete"){
418                            $html4output .= $value['value'].$this->tagcloset;//return the text (add spaces after)
419                            $this->tagcloset = "";
420                        }
421                        break;
422                    case "W:COLOR":
423                        if($value['type'] == "complete" && $this->allowColor){
424                            $html4output .= "<span style='color:#".$value['attributes']['W:VAL'].";'>";//add colored text
425                            $this->tagcloset = "</span>";
426                        }
427                        break;
428                    case "V:TEXTPATH":
429                        if($value['type'] == "complete"){
430                            if(array_key_exists('STRING',$value['attributes'])){
431                                $html4output .= $value['attributes']['STRING'];//add word art text (this is also important)
432                            }
433                        }
434                        break;
435                    case "W:PSTYLE"://word styles used for headings etc.
436                        if($value['type'] == "complete"){
437                            if($value['attributes']['W:VAL'] == "Heading1"){
438                                // -- || Determine if should split or not here || -- //
439                                if($this->split){
440                                    //the document may be split
441                                    if($h1 > 0){
442                                        //it should split if there already have been an heading 1
443                                        $this->output[$i] = $html4output;
444                                        $i++;
445                                        $html4output = "<p>";//it should start on a paragraph
446                                    }
447                                }//then just continue an add the H1 tag
448                                $html4output .= "<h1>";
449                                $h1++;
450                                $this->tagclosep = "</h1>";
451                            }elseif($value['attributes']['W:VAL'] == "Heading2"){
452                                $html4output .= "<h2>";
453                                $this->tagclosep = "</h2>";
454                            }elseif($value['attributes']['W:VAL'] == "Heading3"){
455                                $html4output .= "<h3>";
456                                $this->tagclosep = "</h3>";
457                            }
458                        }
459                        break;
460                    case "W:B"://word style for bold
461                        if($value['type'] == "complete"){
462                            if($this->tagcloset == "</strong>"){
463                                break;
464                            }
465                            $html4output .= "<strong>";//return the text (add spaces after)
466                            $this->tagcloset = "</strong>";
467                        }
468                        break;
469                    case "W:I"://word style for italics
470                        if($value['type'] == "complete"){
471                            if($this->tagcloset == "</em>"){
472                                break;
473                            }
474                            $html4output .= "<em>";//return the text (add spaces after)
475                            $this->tagcloset = "</em>";
476                        }
477                        break;
478                    case "W:U"://word style for underline
479                        if($value['type'] == "complete"){
480                            if($this->tagcloset == "</span>"){
481                                break;
482                            }
483                            $html4output .= "<span style='text-decoration:underline;'>";//return the text (add spaces after)
484                            $this->tagcloset = "</span>";
485                        }
486                        break;
487                    case "W:STRIKE"://word style for strike-throughs
488                        if($value['type'] == "complete"){
489                            if($this->tagcloset == "</span>"){
490                                break;
491                            }
492                            $html4output .= "<span style='text-decoration:line-through;'>";//return the text (add spaces after)
493                            $this->tagcloset = "</span>";
494                        }
495                        break;
496                    case "W:VERTALIGN": //word style for super- and subscripts
497                        if($value['type'] == "complete"){
498                            if($value['attributes']['W:VAL'] == "subscript"){
499                                $html4output .= "<sub>";
500                                $this->tagcloset = "</sub>";
501                            }elseif($value['attributes']['W:VAL'] == "superscript"){
502                                $html4output .= "<sup>";
503                                $this->tagcloset = "</sup>";
504                            }
505                        }
506                        break;
507                    case "W:BOOKMARKSTART": //word style for bookmarks/internal links
508                        if($value['type'] == "complete"){
509                            $html4output .= "<a id='".$value['attributes']['W:NAME']."'></a>";
510                        }
511                        break;
512                    default:
513                        break;
514                }
515            }
516        }
517        $this->output[] = $html4output;//this should output the last part to the output var
518        $this->status = "Contents Extracted...";
519        if(empty($html4output)){
520            return false;
521        }
522        return true;
523    }
524    /**
525     * Recursive directory creation based on full path.
526     * Will attempt to set permissions on folders.
527     * @param string $target Full path to attempt to create.
528     * @return bool Whether the path was created or not. True if path already exists.
529     * @since 1.0
530     */
531    function mkdir_p( $target ) {
532        // from php.net/mkdir user contributed notes
533        $target = str_replace( '//', '/', $target );
534        if ( file_exists( $target ) ){
535            return @is_dir( $target );
536        }
537        // Attempting to create the directory may clutter up our display.
538        if ( @mkdir( $target ) ) {
539            $stat = @stat( dirname( $target ) );
540            $dir_perms = $stat['mode'] & 0007777;  // Get the permission bits.
541            @chmod( $target, $dir_perms );
542            return true;
543        } elseif ( is_dir( dirname( $target ) ) ) {
544            return false;
545        }
546        // If the above failed, attempt to create the parent node, then try again.
547        if ( ( $target != '/' ) && ( $this->mkdir_p( dirname( $target ) ) ) ){
548            return $this->mkdir_p( $target );
549        }
550        return false;
551    }
552    /**
553     * This function concludes the class by removing all te temporary files and folders as well as unsetting all variables not required
554     * @return Bool True on success
555     * @since 1.0
556     */
557    function DeleteTemps(){
558        //this function will delete all the temp files except the word document
559        //(.docx) itself. If this was uploaded it will be removed when the
560        //script terminates
561        if(is_dir($this->tempDir)){
562            //the temp directory still exist
563            $this->rrmdir($this->tempDir);
564            unset($this->content_folder);
565            unset($this->docxPath);
566            unset($this->imagePathPrefix);
567            unset($this->image_max_width);
568            unset($this->tempDir);
569            unset($this->rels);
570            unset($this->tagclosep);
571            unset($this->tagcloset);
572            return true;
573        }
574        return false;
575    }
576    /**
577     * This function will remove files and directories recursivly
578     * @param String $dir The path to the folder to be removed
579     */
580    function rrmdir($dir) {
581        if (is_dir($dir)) {
582            $objects = scandir($dir);
583            foreach ($objects as $object) {
584                if ($object != "." && $object != "..") {
585                    if (filetype($dir."/".$object) == "dir"){
586                        $this->rrmdir($dir."/".$object);
587                    } else {
588                        unlink($dir."/".$object);
589                    }
590                }
591            }
592            reset($objects);
593            rmdir($dir);
594        }
595    }
596}
597#EOF-----------
Note: See TracBrowser for help on using the repository browser.