098558fee900a1241c525fccc7052e3e0d317740
[pear] / File / Convert.php
1 <?php
2 /**
3  * our moto... "To Convert and Serve"!!!
4  * usage:
5  * 
6  * $x=  new File_Convert("filename", "application/pdf");
7  * $fn = $x->convert("image/jpeg", 200, 0);
8  * $x->serve('inline');
9  * 
10  * generic serve
11  * $x=  new File_Convert("filename", "application/pdf");
12  * $fn = $x->convert("application/pdf"); // does no conversion (as it's the same.
13  * $x->serve('inline'); // can fix IE Mess...
14  * 
15  * options 
16  * {
17  *   delete_all : delete all the generated files after script execution when we call convert()
18  * }
19  * 
20  */
21 /*
22 // test: 
23 echo '<PRE>';
24 $x = new File_Convert(false);
25 print_r($x->getConvMethods('application/msword', 'application/pdf'));
26 print_r($x->getConvMethods('application/msword', 'image/jpeg'));
27 print_r($x->getConvMethods('application/acad', 'image/jpeg'));
28 var_dump($x->getConvMethods('application/acad', 'application/msword')); // impossible
29
30 $x = new File_Convert(file, 'app../excel',array('sheet'=>array(0,1,2...) ));
31 $out = $x->convert('text/csv');
32 */
33
34 class File_Convert
35 {
36    
37     
38     static $options = array();
39     
40     var $fn = ''; // filename
41     var $mimetype = '';
42     // for results..
43     var $debug = false; // set to true to turn on deubgging
44     var $to;
45     var $target;
46     var $lastaction = false;
47     var $log  = array();
48     var $solutions = array();
49     
50     function __construct($fn, $mimetype, $options=array())
51     {
52         $this->fn = $fn;
53         
54         if (!file_exists($fn)) {
55             throw new Exception("Source file does not exist:". $fn );
56         }
57         
58         $this->mimetype = $mimetype;
59         self::$options = $options;
60     }
61     
62     
63     
64     /**
65      * check if conversion exists, and return the filename if it does.
66      *
67      */
68     
69     function convertExists($toMimetype, $x= 0, $y =0) 
70     {
71         static $actions = array();
72         $fn = $this->fn;
73         if ($toMimetype != $this->mimetype) {
74             
75             if (!isset($actions["{$this->mimetype} => $toMimetype"])) {
76                 $actions["{$this->mimetype} => $toMimetype"] = $this->getConvMethods($this->mimetype, $toMimetype);;
77             }
78                 
79             $action = $actions["{$this->mimetype} => $toMimetype"];
80             
81            
82             // echo '<PRE>';print_r($action);
83             if (!$action) {
84                 return false;
85             }
86             if (!file_exists($this->fn)) {
87                 return false;
88             }
89             
90             $fn = $action->convertExists($this->fn, $x, $y);
91             
92         }
93        
94         if (!$fn) {
95             return false;
96         }
97         
98         if (!preg_match('#^image/#', $toMimetype) || ( empty($x) && empty($y))) {
99             return $fn;
100         }
101         
102         //echo "testing scale image";
103         require_once 'File/Convert/Solution/scaleimage.php';
104         $sc = new File_Convert_Solution_scaleimage($toMimetype, $toMimetype);
105         //$sc->convert = $this;
106         $sc->debug= $this->debug;
107         $this->solutions[] = $sc;
108             
109         if (strpos($x, 'x')) {
110             $bits = explode('x', $x);
111             $x = (int)$bits[0];
112             $y = empty($bits[1]) ?  0 : (int)$bits[1];;
113         }
114           
115         if (!file_Exists($fn)) {
116             return false;
117         }
118         $fn = $sc->convertExists($fn, (int)$x, (int)$y);
119              
120         
121         //$this->target = $fn;
122         //$this->to = $toMimetype;
123         return $fn;
124     }
125     /**
126      *
127      * actually run the convertion routine.
128      * 
129      */
130     
131     function convert($toMimetype, $x= 0, $y =0, $pg=false) 
132     {
133         //print_R(func_get_args());
134         if ($toMimetype == 'image/jpg') {
135             $toMimetype = 'image/jpeg';
136         }
137         
138         $pg = (int) $pg;
139          if(empty($pg) || is_nan($pg * 1)){
140             $pg = false;
141         }
142         $fn = $this->fn;
143          //echo '<PRE>'; print_r(array('convert', func_get_args()));
144         if (
145                 $toMimetype != $this->mimetype ||
146                 (
147                         $toMimetype == $this->mimetype &&
148                         $toMimetype == 'image/gif'
149                 )
150         ) {
151
152             $action = $this->getConvMethods($this->mimetype, $toMimetype);
153              
154             //echo '<PRE>';print_r($action);
155             if (!$action) {
156                 
157                 $this->debug("No methods found to convert {$this->mimetype} to {$toMimetype}");
158                 return false;
159             }
160             $action->debug = $this->debug;
161             $fn = $action->runconvert($this->fn, $x, $y, $pg);
162             // delete the generated files after script execution
163             if(!empty(self::$options['delete_all'])) {
164                 $this->deleteOnExitAdd($fn);
165             }
166
167             if (!$fn) {
168                 $this->to = $toMimetype;
169                 $this->lastaction = $action->last ? $action->last : $action; // what failed.
170                 return false;
171             }
172             
173             // let's assume that conversions can handle scaling??
174             
175             
176         }  
177              
178
179         if (preg_match('#^image/#', $toMimetype) && $toMimetype != 'image/gif' && ( !empty($x) || !empty($y))) {
180             //var_dump(array($toMimetype));
181                
182             require_once 'File/Convert/Solution.php';
183             $scf = (strpos($x, 'c')  !== false ? 'scaleimagec' : 'scaleimage' );
184             require_once 'File/Convert/Solution/'. $scf . '.php';
185             $scls = 'File_Convert_Solution_' . $scf;
186                 
187             $sc = new $scls($toMimetype, $toMimetype);
188             $sc->debug=  $this->debug;
189             $this->solutions[] = $sc;
190             $x  = str_replace('c', 'x', $x);
191             
192             if (strpos($x, 'x') !== false ) {
193                 $bits = explode('x', $x);
194                 $x = $bits[0];
195                 $y = !is_numeric($bits[1]) ?  '' : (int)$bits[1];
196             }
197             $x = strlen($x) ? (int) $x : '';
198             $y = strlen($y) ? (int) $y : '';
199             //print_r($x); print_r(' > '); print_r($y);exit;
200             
201             $fn = $sc->runconvert($fn,  $x, $y, $pg);
202
203             // delete the generated files after script execution
204             if(!empty(self::$options['delete_all'])) {
205                 $this->deleteOnExitAdd($fn);
206             }
207           
208         }
209 //        print_r($this->target);
210         $this->target = $fn;
211         $this->to = $toMimetype;
212         return $fn;
213         
214         
215     }
216     
217     function serveOnly($type=false, $filename =false, $delete_after = false)
218     {
219         $this->target = $this->fn;
220         $this->to = $this->mimetype;
221         $this->serve($type, $filename , $delete_after );
222     }
223     
224     
225     /**
226      * Serve the file to a browser so it can be downloaded, or viewed.
227      *
228      * @param type string      attachment or inline..
229      * @param filename string  name of file
230      * @param delete_after boolean (false)   delete file after sending..
231      *
232      */
233     function serve($type=false, $filename =false, $delete_after = false) // may die **/
234     {
235         if (empty($this->target)) {
236             // broken image? for images...
237             $cmd = isset($this->lastaction->cmd) ? $this->lastaction->cmd : "No Method";
238             die("not available in this format was: {$this->mimetype}, request: {$this->to}<BR>
239                 Running - $cmd\n" . print_r(is_object($this->lastaction) ? $this->lastaction->log : '',true));
240         }
241         clearstatcache();
242         if (!file_exists($this->target))
243         {
244             trigger_error("Target does not exist: {$this->target}");
245             print_r($this->target);
246             die("file missing");
247        }
248        
249         
250         $fn = $this->target;
251
252         $isIE = preg_match('#msie [0-9.]+#i', isset($_SERVER['HTTP_USER_AGENT']) ? isset($_SERVER['HTTP_USER_AGENT'])  : '');
253         
254         
255         
256         $ts = filemtime($fn);
257         
258         $etag = md5($ts. '!' . $fn);
259         
260         $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
261                 trim($_SERVER['HTTP_IF_NONE_MATCH'],"'\"") : false;
262
263         $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? 
264             stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
265         
266         $ts_string = gmdate("D, d M Y H:i:s",  $ts) . " GMT";
267         
268         if ((($if_none_match && $if_none_match == $etag) || (!$if_none_match)) &&
269             ($if_modified_since && $if_modified_since == $ts_string))
270         {
271             header('HTTP/1.1 304 Not Modified');
272             exit();
273         }
274         
275         
276         //if (empty($_REQUEST['ts']) && !$isIE && $ifModifiedSince && strtotime($ifModifiedSince) >= $ts) {
277         //    header('HTTP/1.0 304 Not Modified');
278         //    exit; // stop processing
279        // }
280         // ie kludge cause it's brain dead..
281         
282        // var_dump($isIE); 
283         $mt =  $this->to;
284         if ($isIE && preg_match('#^application/#i', $this->to)) {
285             // pdfs' break if we add this line?
286             //$mt = 'application/octet-stream' ;
287             $type = $type === false ?  'attachment' : $type;
288         }
289         $type = $type === false ?  'inline' : $type;
290         
291         
292         
293        
294         //if (!preg_match('#^image\/#i', $this->to)) {
295     
296         // a reasonable expiry time - 5 minutes..
297         header("Expires: ". gmdate("D, d M Y H:i:s",  strtotime("NOW + 5 MINUTES")) . " GMT");
298         header("Cache-Control: must-revalidate");
299         header("Pragma: public");     
300         header("Last-Modified: " . $ts_string . " GMT");
301         header("ETag: \"{$etag}\"");
302         
303         //var_dump($mt);
304         require_once 'File/MimeType.php';
305         $fmt = new File_MimeType();
306         $ext = $fmt->toExt($mt);
307         $sfn = basename($fn);
308         $sfn = preg_match('#\.'.$ext.'$#', $sfn) ? $sfn : $sfn. '.' .$ext;
309         //var_dump($sfn);
310         
311         if (empty($filename)) {
312             $filename = $sfn;
313         }
314
315         $filename = preg_replace('/[^\x00-\x7E]/', '', $filename);
316         
317         header('Content-length: '. filesize($fn));
318        // if ($type != 'inline') {
319             header('Content-Disposition: '.$type.'; filename="' . htmlspecialchars($filename).  '"');
320        // }
321        
322         // needs to be removed after debugging - otherwise it logs to error.log
323         //ini_set('display_errors', 0); //trigger_error("Serving: {$this->target} ". filesize($fn));
324         if ($_SERVER["REQUEST_METHOD"] == 'HEAD') {
325             //fclose($fh);
326             exit;
327         }
328         //var_dump($fn, $mt); exit;
329         header('Content-type: '. $mt);
330         
331         // even though we have done a file_exists above - it still errors out here occausionally.
332         $fh = @fopen($fn, 'rb');
333         //fpassthru($fh);
334         
335         // passthrough seems to have problems -- trying fread
336         while($fh && !feof($fh))
337         {
338             echo @fread($fh, 1024*8);
339             @ob_flush();
340             flush();
341         }
342         
343         if ($fh) {
344             fclose($fh);
345         }
346         
347         if ($delete_after) {
348             @unlink($fn);
349         }
350         exit;
351         
352         
353     }
354     /**
355      * 
356      * returned format:
357      *
358      * (
359           from =>
360           to =>
361           cls => instance of class
362      )
363      * 
364      *
365      */
366     
367     function methods()
368     {
369         static $methods = false;
370         if ($methods !== false ) {
371             return $methods;
372         }
373         $methods = array();
374         $base = __DIR__.'/Convert/Solution';
375         $dh = opendir($base);
376         while (false !== ($fn = readdir($dh))) {
377             if (substr($fn,0,1) == '.' ) {
378                 continue;
379             }
380             require_once 'File/Convert/Solution/' . $fn;
381             $cls = 'File_Convert_Solution_'. str_replace('.php', '',$fn);
382             
383             $ref = new ReflectionClass($cls);        
384             $val = $ref->getStaticPropertyValue('rules');
385             
386             foreach($val as $r) {
387                 $r['cls'] = $cls;
388                 $methods[] = $r;
389             }
390             
391             
392         }
393         return $methods;
394         
395         
396         
397     }
398     
399     
400     
401  
402     /**
403      * This recursively calls to find the best match.
404      * First by matching the 'from'
405      *
406      * Then if multiple outputs are available,
407      * It will see if any of those can be used to generate the to, by recurivly calling it'self..
408      *
409      */
410      
411     function getConvMethods($from, $to, $stack = array())
412     {
413             // these source types have to use unoconv....
414         //print_r(array('getConvMethods', func_get_args()));
415         // $pos[converter] => array( list of targets);
416         require_once 'File/Convert/Solution.php';
417
418         if (count($stack) > 4) { // too deepp.. pos. recursion.
419             return false;
420         }
421         $pos = array();
422         // print_r(self::$methods);
423         foreach($this->methods() as $t) {
424             if (!in_array($from, $t['from'])) {
425                 continue;
426             }
427             if (in_array($to,$t['to'])) {
428                 $cls = $t['cls'];
429                 $ret =  new $cls($from, $to);  // found a solid match - returns the method.
430                 //$ret->convert = $this; // recursion?
431                 $this->solutions[] = $ret;
432
433                 //echo "got match?";
434                 return $ret;
435             }
436             // from matches..
437             $pos[$t['cls']] = $t['to']; // list of targets
438             
439         }
440         //echo "got here?";
441         
442         $stack[] = $from;
443         $res = array();
444         foreach($pos as $conv => $ar) {
445             // array contains a list of pos. mimetypes.
446             // 
447             foreach($ar as $targ) {
448                 if ($from == $targ) {
449                     continue; // skip going back...
450                 }
451                 if (in_array($targ, $stack)) {
452                     continue; // going backwards..
453                 }
454                 // we need to build a list here. 
455                 
456                 
457                 
458                 $ns = $stack;
459                 $ns[] = $targ;
460                 $try = $this->getConvMethods($targ, $to, $ns);
461                 // try will be an array of method, from, to (or false)
462                 
463                 if ($try === false) {
464                     continue; // mo way to convert
465                 }
466 //                print_r($conv);exit;
467
468                 $first = new $conv($from, $targ);
469                 //$first->convert = $this;
470                 $sol_list= $first->add($try);
471                 
472                 $res[] = $sol_list;
473                 
474             }
475             
476         }
477         if (empty($res)) {
478              $this->debug("No methods found to convert {$from} to {$to}");
479
480             return false;
481         }
482         // find the shortest..
483         usort  ( $res  , array($this, 'solutionSort'));
484         $best = $res[0];
485         $this->solutions[] = $best;
486         return $best;
487         
488         
489     }
490     function solutionSort($a, $b) {
491         if ($a->count() == $b->count()) {
492             return 0;
493         }
494         return $a->count() < $b->count() ?  -1 : 1;
495     }
496     
497     function debug($str)
498     { 
499         if ($this->debug) {
500             
501             if (is_callable($this->debug)) {
502                 call_user_func($this->debug,$str);
503             } else {
504                 echo $str . "<br/>\n";
505             }
506         }
507         $this->log[] = $str;
508     }
509     
510     static $deleteOnExit = false;
511     /**
512      * generate a tempory file with an extension (dont forget to delete it)
513      */
514     
515     function deleteOnExitAdd($name)
516     {
517         if (self::$deleteOnExit === false) {
518             register_shutdown_function(array('File_Convert','deleteOnExit'));
519             self::$deleteOnExit  = array();
520         }
521         self::$deleteOnExit[] = $name;
522     }
523     
524     static function deleteOnExit()
525     {
526         
527         foreach(self::$deleteOnExit as $fn) {
528             if (file_exists($fn)) {
529                 unlink($fn);
530             }
531         }
532     }
533                 
534           
535            
536 }
537
538
539
540
541
542