typo
[Pman.Core] / Images.php
1 <?php
2 /**
3  * Deal with image delivery and HTML replacement of image links in body text.
4  *
5  *
6  * NOTE THIS WAS NEVER INTENDED FOR PUBLIC IMAGE DISTRIBUTION - we need to create a seperate file for that...
7  *
8  * $str = Pman_Core_Images::replaceImg($str); // < use with HTML
9  *
10  * or
11  *
12  * Deliver image /file etc..
13  * 
14  * Use Cases:
15  * 
16  * args: ontable request
17  *      ontable (req) tablename.
18  *      filename
19  *      (other table args)
20  *      as (serve as a type) = eg. ?as=audio/mpeg 
21  * 
22  * args: generic
23  *     as :(serve as a type) = eg. mimetype.
24  * 
25  * Images/{ID}/fullname.xxxx
26  * 
27  * (valid thumbs 200, 400)...?
28  * Images/Thumb/200/{ID}/fullname.xxxx
29  * Images/Download/{ID}/fullname.xxxx
30  *
31  *
32  *
33  * 
34  * Used to be in Base... now in core..
35  *
36  * 
37  * view permission should be required on the underlying object...
38  * 
39  */
40 require_once  'Pman.php';
41 class Pman_Core_Images extends Pman
42 {
43     
44     // tables that do not need authentication checks before serving.
45     var $public_image_tables = array(
46         'crm_mailing_list_message'   // we know these are ok...
47     );
48     
49     var  $sizes = array(
50                 '100', 
51                 '100x100', 
52                 '150', 
53                 '150x150', 
54                 '200', 
55                 '200x0',
56                 '200x200',  
57                 '400x0',
58                 '300x100',
59                 '500'
60             );
61     function getAuth()
62     {
63         parent::getAuth(); // load company!
64         //return true;
65         $au = $this->getAuthUser();
66         
67         if (!$au) {
68             $this->authUser = false;
69             return true;//die("Access denied");
70         }
71         
72         $this->authUser = $au;
73         
74         return true;
75     }
76     var $thumb = false;
77     var $as_mimetype = false;
78     var $method = 'inline';
79     var $page = false;
80     var $is_local = false;
81     
82     function get($s, $opts=array()) // determin what to serve!!!!
83     {
84         // for testing only.
85         //if (!empty($_GET['_post'])) {
86         //   return $this->post();
87         //}
88
89         $this->is_local = (!empty($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'localhost') ? true : false;
90         
91         $this->as_mimetype = empty($_REQUEST['as']) ? '' : $_REQUEST['as'];
92         
93         $this->page = empty($_REQUEST['page']) ? false : (int) $_REQUEST['page'];
94         
95         $bits= explode('/', $s);
96         $id = 0;
97 //        var_dump($bits);die('in');
98         // without id as first part...
99         if (!empty($bits[0]) && $bits[0] == 'Thumb') {
100             $this->thumb = true;
101             $this->as_mimetype = 'image/jpeg';
102             $this->size = empty($bits[1]) ? '0x0' : $bits[1];
103             $id = empty($bits[2]) ? 0 :   $bits[2];
104             
105         } else if (!empty($bits[0]) && $bits[0] == 'Download') {
106             $this->method = 'attachment';
107             $id = empty($bits[1]) ? 0 :   $bits[1];
108             
109         } else  if (!empty($bits[1]) && $bits[1] == 'Thumb') { // with id as first part.
110             $this->thumb = true;
111             $this->as_mimetype = 'image/jpeg';
112             $this->size = empty($bits[2]) ? '0x0' : $bits[2];
113             $id = empty($bits[3]) ? 0 :   $bits[3];
114             
115         } else if (!empty($bits[0]) && $bits[0] == 'events') {
116             if (!$this->authUser) {
117                 $this->imgErr("no-authentication-events",$s);
118             }
119             $this->downloadEvent($bits);
120             $this->imgErr("unknown file",$s);
121             
122             
123         } else {
124         
125             $id = empty($bits[0]) ? 0 :  $bits[0];
126         }
127         
128         if (strpos($id,':') > 0) {  // id format  tablename:id:-imgtype
129             //DB_DataObject::debugLevel(1);
130             if (!$this->authUser) {
131                 $this->imgErr("not-authenticated-using-colon-format",$s);
132                 
133             }
134             
135             $onbits = explode(':', $id);
136             if ((count($onbits) < 2)   || empty($onbits[1]) || !is_numeric($onbits[1]) || !strlen($onbits[0])) {
137                 $this->imgErr("bad-url",$s);
138                 
139             }
140             //DB_DataObject::debugLevel(1);
141             $img = DB_DataObject::factory('Images');
142             $img->ontable = $onbits[0];
143             $img->onid = $onbits[1];
144             if (empty($_REQUEST['anytype'])) {
145                 $img->whereAdd("mimetype like 'image/%'");
146             }
147             $img->orderBy('title ASC'); /// spurious ordering... (curretnly used by shipping project)
148             if (isset($onbits[2])) {
149                 $img->imgtype = $onbits[2];
150             }
151             $img->limit(1);
152             if (!$img->find(true)) {
153                 $this->imgErr("no images for that item: " . htmlspecialchars($id),$s);
154                 
155             }
156             
157             $id = $img->id;
158             
159             
160         }
161         $id = (int) $id;
162         
163         // depreciated - should use ontable:onid:type here...
164         if (!empty($_REQUEST['ontable'])) {
165             
166             if (!$this->authUser) {
167                 die("authentication required");
168             }
169             
170             //DB_DataObjecT::debugLevel(1);
171             $img = DB_DataObject::factory('Images');
172             $img->setFrom($_REQUEST);
173            
174             
175             
176             $img->limit(1);
177             if (!$img->find(true)) {
178                 $this->imgErr("No file exists",$s);
179             } 
180             $id = $img->id;
181             
182         }
183         
184         $img = DB_DataObjecT::factory('Images');
185          
186         if (!$id || !$img->get($id) || !$img->exists()) {
187            //print_r($img);           die("HERE");
188             $this->imgErr("image has been removed or deleted.",$s);
189         }
190         
191         if($this->is_local) {
192             return $this->serve($img);
193         }
194         
195         if (!$this->authUser && !in_array($img->ontable,$this->public_image_tables)) {
196             
197             if ($img->ontable != 'core_company') {
198                 $this->imgErr("not-authenticated {$img->ontable}",$s);
199             }
200             if ($img->imgtype != 'LOGO') {
201                 $this->imgErr("not-logo",$s);
202             }
203             $comp  = $img->object();
204             if ($comp->comptype != 'OWNER') {
205                 $this->imgErr("not-owner-company",$s);
206             }
207             
208             return $this->serve($img);
209             
210         }
211         
212         if(!$this->hasPermission($img)){
213             $this->imgErr("access to this image/file has been denied.",$s);
214         }
215         
216         $this->serve($img);
217         exit;
218     }
219     
220     function imgErr($reason,$path) {
221         header('Location: ' . $this->rootURL . '/Pman/templates/images/file-broken.png?reason=' . urlencode($reason) );
222         header('X-Error: ' . $reason . ':' . $path);
223         echo $reason . ':' . $path;
224         exit;
225     }
226     
227     function hasPermission($img) 
228     {
229         return true;
230     }
231     
232     function post($v)
233     {
234         if (!empty($_REQUEST['_get'])) {
235             return $this->get($v);
236         }
237         
238         if (!$this->authUser) {
239             $this->jerr("image conversion only allowed by registered users");
240         }
241         // converts a posted string (eg.svg)
242         // into another type..
243         if (empty($_REQUEST['as'])) {
244            $this->jerr("missing target type");
245         }
246         if (empty($_REQUEST['mimetype'])) {
247             $this->jerr("missing mimetype");
248         }
249         if (empty($_REQUEST['data'])) {
250             $this->jerr("missing data");
251         }
252         
253         
254         $this->as_mimetype = $_REQUEST['as'];
255         $this->mimetype = $_REQUEST['mimetype'];
256         require_once 'File/MimeType.php';
257         $y = new File_MimeType();
258         $src_ext = $y->toExt( $this->mimetype );
259         
260         
261         $tmp = $this->tempName($src_ext);
262         file_put_contents($tmp, $_REQUEST['data']);
263         
264         require_once 'File/Convert.php';
265         $cv = new File_Convert($tmp, $this->mimetype);
266         
267         $fn = $cv->convert(
268                 $this->as_mimetype ,
269                 empty($_REQUEST['width']) ? 0 : $_REQUEST['width'],
270                 empty($_REQUEST['height']) ? 0 : $_REQUEST['height']
271         );
272         if (!empty($_REQUEST['as_data'])) {
273             $this->jok(base64_encode(file_get_contents($fn)));
274         }
275         
276         $cv->serve('attachment');
277         exit;
278         
279         
280         
281     }
282     
283     
284  
285     function serve($img)
286     {
287         $this->sessionState(0); // turn off session... - locking...
288         require_once 'File/Convert.php';
289         if (!$img->exists()) {
290             $this->imgErr("serve = missing-image", $img->getStoreName());
291              
292         }
293 //        print_r($img);exit;
294         $x = $img->toFileConvert();
295         if (empty($this->as_mimetype) || $img->mimetype == 'image/gif') {
296             $this->as_mimetype  = $img->mimetype;
297         }
298         if (!$this->thumb) {
299             if ($x->mimetype == $this->as_mimetype) {
300                 $x->serveOnly($this->method, $img->filename);
301                 exit;
302             }
303             $x->convert( $this->as_mimetype);
304             $x->serve($this->method, $img->filename);
305             exit;
306         }
307         //echo "SKALING?  $this->size";
308         // acutally if we generated the image, then we do not need to validate the size..
309         
310         // if the mimetype is not converted..
311         // then the filename should be original.{size}.jpeg
312         $fn = $img->getStoreName() . '.'. $this->size . '.jpeg'; // thumbs are currenly all jpeg.!???
313         
314         if($img->mimetype == 'image/gif'){
315             $fn = $img->getStoreName() . '.'. $this->size . '.gif';
316         }
317         
318         if (!file_exists($fn)) {
319             $fn = $img->getStoreName()  . '.'. $this->size . '.'. $img->fileExt();
320             // if it's an image, convert into the same type for thumbnail..
321             if (preg_match('#^image/#', $img->mimetype)) {
322                $this->as_mimetype = $img->mimetype;
323             }
324         }
325         
326         if (!file_exists($fn)) {    
327             $this->validateSize();
328         }
329         
330         if(!empty($this->page) && !is_nan($this->page * 1)){
331             $x->convert( $this->as_mimetype, $this->size, 0, $this->page);
332         } else {
333             $x->convert( $this->as_mimetype, $this->size);
334         }
335         
336         $x->serve();
337         exit;
338         
339         
340         
341         
342     }
343     function validateSize()
344     {
345         if($this->is_local) {
346             return true;
347         }
348         
349         if (($this->authUser && !empty($this->authUser->company_id) && $this->authUser->company()->comptype=='OWNER')
350             || $_SERVER['SERVER_ADDR'] == $_SERVER['REMOTE_ADDR']) {
351             return true;
352         }
353         
354         
355         $ff = HTML_FlexyFramework::get();
356         
357         $sizes= $this->sizes;
358         
359         $cfg = isset($ff->Pman_Images) ? $ff->Pman_Images :
360                 (isset($ff->Pman_Core_Images) ? $ff->Pman_Core_Images : array());
361         
362         if (!empty($cfg['sizes'])) {
363             $sizes = array_merge($sizes , $cfg['sizes']);
364         }
365         
366         $project = $ff->project;
367         
368         require_once $ff->project . '.php';
369         
370         $project = str_replace('/', '_', $project);
371          
372         $pr_obj = new $project;
373          
374        // var_dump($pr_obj->Pman_Core_Images_Size);
375         if(isset($pr_obj->Pman_Core_Images_Size)){
376             $sizes = $pr_obj->Pman_Core_Images_Size;
377             
378             
379         }
380         
381         if (!in_array($this->size, $sizes)) {
382             die("invalid scale - ".$this->size);
383         }
384     }
385     /**
386      * replace image urls
387      *
388      * The idea of this code was to replace urls for images when you have an admin
389      * and a distribution page. with different urls.
390      *
391      * it may be usefull later if things like embedded images in emails. but
392      * I think it's proably better not to use this.
393      *
394      * The key problem being how to determine if we are replacing 'our' images or some external one..
395      * 
396      *
397      */
398     
399     
400     static function replaceImageURLS($html, $obj = false)
401     {
402         
403         $ff = HTML_FlexyFramework::get();
404         if (!isset($ff->Pman_Images['public_baseURL'])) {
405             return $html;
406         }
407         //var_dump($ff->Pman_Images['public_baseURL']);
408         $baseURL = $ff->Pman_Images['public_baseURL'];
409         
410         libxml_use_internal_errors(true);
411         $dom = new DOMDocument();
412         $dom->loadHTML("<?xml encoding='utf-8'?> <div id='tmp_dom_wrapper'>{$html}</div>");
413         $imgs = $dom->getElementsByTagName('img');
414        
415         
416         foreach($imgs as $img) {
417             $src = $img->getAttribute('src');
418             if (!$src|| !strlen(trim($src))) {
419                 continue;
420             }
421              
422             if (0 === strpos($src, 'data:')) {
423                 if (!$obj) {
424                     HTML_FlexyFramework::get()->page->jerr("no object to attach data url");
425                 }
426                 
427                 self::replaceDataUrl($baseURL, $img, $obj);
428                 continue;
429             }
430             
431             
432             if (false !== strpos($src, '//') && false === strpos($src, $baseURL)) {
433                 // contains an absolute path.. and not our baseURL.
434                 continue;
435             }
436              
437             $img->setAttribute('src', self::domImgUrl($baseURL, $img));
438               
439             // what about mailto or data... - just ignore?? for images...
440             
441             
442         }
443         
444         $anchors = $dom->getElementsByTagName('a');
445         $result = array();
446         preg_match_all('/<a\s+[^>]+>/i',$html, $result); 
447
448         $matches = array_unique($result[0]);
449         foreach($anchors as $anc) {
450             $href = $anc->getAttribute('href');
451             if (!empty($href) || 0 !== strpos($href, $baseURL)) { 
452                 continue;
453             }
454             $anc->setAttribute('href', self::domImgUrl($baseURL, $href));
455         }
456         
457         
458         $inner = $dom->getElementById("tmp_dom_wrapper");
459         $html = '';
460         foreach ($inner->childNodes as $child) {
461             $html .= ($dom->saveHTML($child));
462         }
463         return $html;
464     }
465     
466     static function domImgUrl($baseURL, $dom) 
467     {
468         $url = $dom;
469         if (!is_string($url)) {
470             $url = $dom->getAttribute('src');
471         }
472          $umatch  = false;
473         if(!preg_match('#/(Images|Images/Thumb/[a-z0-9]+|Images/Download)/([0-9]+)/(.*)$#', $url, $umatch))  {
474             return $url;
475         }
476         $id = $umatch[2];
477         $hash = '';
478         
479         if (!empty($umatch[3]) && strpos($umatch[3],'#')) {
480             $hh = explode('#',$umatch[3]);
481             $hash = '#'. array_pop($hh);
482         }
483         
484         
485         $img = DB_DataObject::factory('Images');
486         if (!$img->get($id)) {
487             return $url;
488         }
489         $type = explode('/', $umatch[1]);
490         $thumbsize = -1;
491          
492         if (count($type) > 2 && $type[1] == 'Thumb') {
493             $thumbsize = $type[2];
494             $provider = '/Images/Thumb';
495         } else {
496             $provider = '/'.$umatch[1];
497         }
498         
499         $w =  is_string($dom) ? false : $dom->getAttribute('width');
500         $h =  is_string($dom) ? false : $dom->getAttribute('width');
501         
502         if (!is_string($dom) && (!empty($w) || !empty($h)) )
503         {
504             // no support for %...
505             $thumbsize =
506                 (empty($w) ? '0' : $w * 1) .
507                 'x' .
508                 (empty($h) ? '0' : $h * 1);
509              $provider = '/Images/Thumb';
510             
511         }
512         
513         if ($thumbsize !== -1) {
514             // change in size..
515             // need to regenerate it..
516             
517             $type = array('Images', 'Thumb', $thumbsize);
518                 
519             $fc = $img->toFileConvert();
520             // make sure it's available..
521             $fc->convert($img->mimetype, $thumbsize);
522             
523             
524         } else {
525             $provider = $provider == 'Images/Thumb' ? 'Images' : $provider; 
526         }
527         
528         
529         // finally replace the original TAG with the new version..
530         
531         return $img->URL($thumbsize, $provider, $baseURL) . $hash ;
532         
533          
534     }
535     
536     static function replaceDataUrl($baseURL, $img, $obj)
537     {
538         $d = DB_DataObject::Factory('Images');
539         $d->object($obj);
540         
541         
542         $d->createFromData($img->getAttribute('src'));
543         $img->setAttribute('src', $d->URL(-1, '/Images' , $baseURL));
544     }
545     
546     static function replaceImgUrl($html, $baseURL, $tag, $attr, $attr_name) 
547     {
548         
549         //print_R($attr);
550         // see if it's an image url..
551         // Images/{ID}/fullname.xxxx
552         // Images/Thumb/200/{ID}/fullname.xxxx
553         // Images/Download/{ID}/fullname.xxxx
554         
555         $attr_url = $attr[$attr_name];
556         $umatch  = false;
557         if(!preg_match('#/(Images|Images/Thumb/[a-z0-9]+|Images/Download)/([0-9]+)/(.*)$#', $attr_url, $umatch))  {
558             return $html;
559         }
560         
561         $id = $umatch[2];
562         $hash = '';
563         if (!empty($umatch[3]) && strpos($umatch[3],'#')) {
564             $hh = explode('#',$umatch[3]);
565             $hash = '#'. array_pop($hh);
566         }
567         
568         
569         $img = DB_DataObject::factory('Images');
570         if (!$img->get($id)) {
571             return $html;
572         }
573         $type = explode('/', $umatch[1]);
574         $thumbsize = -1;
575          
576         if (count($type) > 2 && $type[1] == 'Thumb') {
577             $thumbsize = $type[2];
578             $provider = '/Images/Thumb';
579         } else {
580             $provider = '/'.$umatch[1];
581         }
582         
583         if (!empty($attr['width']) || !empty($attr['height']) )
584         {
585             // no support for %...
586             $thumbsize =
587                 (empty($attr['width']) ? '0' : $attr['width'] * 1) .
588                 'x' .
589                 (empty($attr['height']) ? '0' : $attr['height'] * 1);
590              $provider = '/Images/Thumb';
591             
592         }
593         
594         if ($thumbsize !== -1) {
595             // change in size..
596             // need to regenerate it..
597             
598             $type = array('Images', 'Thumb', $thumbsize);
599                 
600             $fc = $img->toFileConvert();
601             // make sure it's available..
602             $fc->convert($img->mimetype, $thumbsize);
603             
604             
605         } else {
606             $provider = $provider == 'Images/Thumb' ? 'Images' : $provider; 
607         }
608         
609         
610         // finally replace the original TAG with the new version..
611         
612         $new_tag = str_replace(
613             $attr_name. '="'. $attr_url . '"',
614             $attr_name .'="'. htmlspecialchars($img->URL($thumbsize, $provider, $baseURL)) . $hash .'"',
615             $tag
616         );
617         
618         
619         return str_replace($tag, $new_tag, $html);
620          
621     }
622     
623     function downloadEvent($bits)
624     {
625         $ev = DB_DAtaObject::Factory('events');
626         if (!$ev->get($bits[1])) {
627             die("could not find event id");
628         }
629         // technically same user only.. -- normally www-data..
630         if (function_exists('posix_getpwuid')) {
631             $uinfo = posix_getpwuid( posix_getuid () ); 
632             $user = $uinfo['name'];
633         } else {
634             $user = getenv('USERNAME'); // windows.
635         }
636         $ff = HTML_FlexyFramework::get();
637         
638         $file = $ev->logDir() . date('/Y/m/d/',strtotime($ev->event_when)). $ev->id . ".json";
639         
640         if(!$file || !file_exists($file)){
641             die("file was not saved");
642         }
643         
644         $filesJ = json_decode(file_get_contents($file));
645
646         foreach($filesJ->FILES as $k=>$f){
647             if ($f->tmp_name != $bits[2]) {
648                 continue;
649             }
650
651             $src = $file = $ev->logDir() . date('/Y/m/d/', strtotime($ev->event_when)).  $f->tmp_name ;
652             
653             if (!$src || !file_exists($src)) {
654                 die("file was not saved");
655             }
656             header ('Content-Type: ' . $f->type);
657
658             header("Content-Disposition: attachment; filename=\"".basename($f->name)."\";" );
659             @ob_clean();
660             flush();
661             readfile($src);
662             exit;
663         }
664     }
665     
666      
667         
668         
669          
670 }