]> git.mxchange.org Git - friendica.git/blob - src/Object/Photo.php
Function names
[friendica.git] / src / Object / Photo.php
1 <?php
2 /**
3  * @file src/Object/Photo.php
4  * @brief This file contains the Photo class for image processing
5  */
6 namespace Friendica\Object;
7
8 use Friendica\App;
9 use Friendica\Core\Cache;
10 use Friendica\Core\Config;
11 use Friendica\Core\System;
12 use Friendica\Database\DBM;
13
14 require_once "include/photos.php";
15
16 /**
17  * Class to handle Photos
18  */
19 class Photo
20 {
21         private $image;
22
23         /*
24          * Put back gd stuff, not everybody have Imagick
25          */
26         private $imagick;
27         private $width;
28         private $height;
29         private $valid;
30         private $type;
31         private $types;
32
33         /**
34          * @brief supported mimetypes and corresponding file extensions
35          */
36         public static function supportedTypes()
37         {
38                 if (class_exists('Imagick')) {
39                         // Imagick::queryFormats won't help us a lot there...
40                         // At least, not yet, other parts of friendica uses this array
41                         $t = array(
42                                 'image/jpeg' => 'jpg',
43                                 'image/png' => 'png',
44                                 'image/gif' => 'gif'
45                         );
46                 } else {
47                         $t = array();
48                         $t['image/jpeg'] ='jpg';
49                         if (imagetypes() & IMG_PNG) {
50                                 $t['image/png'] = 'png';
51                         }
52                 }
53
54                 return $t;
55         }
56
57         /**
58          * @brief Constructor
59          * @param object  $data data
60          * @param boolean $type optional, default null
61          * @return object
62          */
63         public function __construct($data, $type = null)
64         {
65                 $this->imagick = class_exists('Imagick');
66                 $this->types = static::supportedTypes();
67                 if (!array_key_exists($type, $this->types)) {
68                         $type='image/jpeg';
69                 }
70                 $this->type = $type;
71
72                 if ($this->isImagick() && $this->loadData($data)) {
73                         return true;
74                 } else {
75                         // Failed to load with Imagick, fallback
76                         $this->imagick = false;
77                 }
78                 return $this->loadData($data);
79         }
80
81         /**
82          * @brief Destructor
83          * @return void
84          */
85         public function __destruct()
86         {
87                 if ($this->image) {
88                         if ($this->isImagick()) {
89                                 $this->image->clear();
90                                 $this->image->destroy();
91                                 return;
92                         }
93                         if (is_resource($this->image)) {
94                                 imagedestroy($this->image);
95                         }
96                 }
97         }
98
99         /**
100          * @return boolean
101          */
102         public function isImagick()
103         {
104                 return $this->imagick;
105         }
106
107         /**
108          * @brief Maps Mime types to Imagick formats
109          * @return arr With with image formats (mime type as key)
110          */
111         public function getFormatsMap()
112         {
113                 $m = array(
114                         'image/jpeg' => 'JPG',
115                         'image/png' => 'PNG',
116                         'image/gif' => 'GIF'
117                 );
118                 return $m;
119         }
120
121         /**
122          * @param object $data data
123          * @return boolean
124          */
125         private function loadData($data)
126         {
127                 if ($this->isImagick()) {
128                         $this->image = new Imagick();
129                         try {
130                                 $this->image->readImageBlob($data);
131                         } catch (Exception $e) {
132                                 // Imagick couldn't use the data
133                                 return false;
134                         }
135
136                         /*
137                          * Setup the image to the format it will be saved to
138                          */
139                         $map = $this->getFormatsMap();
140                         $format = $map[$type];
141                         $this->image->setFormat($format);
142
143                         // Always coalesce, if it is not a multi-frame image it won't hurt anyway
144                         $this->image = $this->image->coalesceImages();
145
146                         /*
147                          * setup the compression here, so we'll do it only once
148                          */
149                         switch ($this->getType()) {
150                                 case "image/png":
151                                         $quality = Config::get('system', 'png_quality');
152                                         if ((! $quality) || ($quality > 9)) {
153                                                 $quality = PNG_QUALITY;
154                                         }
155                                         /*
156                                          * From http://www.imagemagick.org/script/command-line-options.php#quality:
157                                          *
158                                          * 'For the MNG and PNG image formats, the quality value sets
159                                          * the zlib compression level (quality / 10) and filter-type (quality % 10).
160                                          * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering,
161                                          * unless the image has a color map, in which case it means compression level 7 with no PNG filtering'
162                                          */
163                                         $quality = $quality * 10;
164                                         $this->image->setCompressionQuality($quality);
165                                         break;
166                                 case "image/jpeg":
167                                         $quality = Config::get('system', 'jpeg_quality');
168                                         if ((! $quality) || ($quality > 100)) {
169                                                 $quality = JPEG_QUALITY;
170                                         }
171                                         $this->image->setCompressionQuality($quality);
172                         }
173
174                         // The 'width' and 'height' properties are only used by non-Imagick routines.
175                         $this->width  = $this->image->getImageWidth();
176                         $this->height = $this->image->getImageHeight();
177                         $this->valid  = true;
178
179                         return true;
180                 }
181
182                 $this->valid = false;
183                 $this->image = @imagecreatefromstring($data);
184                 if ($this->image !== false) {
185                         $this->width  = imagesx($this->image);
186                         $this->height = imagesy($this->image);
187                         $this->valid  = true;
188                         imagealphablending($this->image, false);
189                         imagesavealpha($this->image, true);
190
191                         return true;
192                 }
193
194                 return false;
195         }
196
197         /**
198          * @return boolean
199          */
200         public function isValid()
201         {
202                 if ($this->isImagick()) {
203                         return ($this->image !== false);
204                 }
205                 return $this->valid;
206         }
207
208         /**
209          * @return mixed
210          */
211         public function getWidth()
212         {
213                 if (!$this->isValid()) {
214                         return false;
215                 }
216
217                 if ($this->isImagick()) {
218                         return $this->image->getImageWidth();
219                 }
220                 return $this->width;
221         }
222
223         /**
224          * @return mixed
225          */
226         public function getHeight()
227         {
228                 if (!$this->isValid()) {
229                         return false;
230                 }
231
232                 if ($this->isImagick()) {
233                         return $this->image->getImageHeight();
234                 }
235                 return $this->height;
236         }
237
238         /**
239          * @return mixed
240          */
241         public function getImage()
242         {
243                 if (!$this->isValid()) {
244                         return false;
245                 }
246
247                 if ($this->isImagick()) {
248                         /* Clean it */
249                         $this->image = $this->image->deconstructImages();
250                         return $this->image;
251                 }
252                 return $this->image;
253         }
254
255         /**
256          * @return mixed
257          */
258         public function getType()
259         {
260                 if (!$this->isValid()) {
261                         return false;
262                 }
263
264                 return $this->type;
265         }
266
267         /**
268          * @return mixed
269          */
270         public function getExt()
271         {
272                 if (!$this->isValid()) {
273                         return false;
274                 }
275
276                 return $this->types[$this->getType()];
277         }
278
279         /**
280          * @param integer $max max dimension
281          * @return mixed
282          */
283         public function scaleImage($max)
284         {
285                 if (!$this->isValid()) {
286                         return false;
287                 }
288
289                 $width = $this->getWidth();
290                 $height = $this->getHeight();
291
292                 $dest_width = $dest_height = 0;
293
294                 if ((! $width)|| (! $height)) {
295                         return false;
296                 }
297
298                 if ($width > $max && $height > $max) {
299                         // very tall image (greater than 16:9)
300                         // constrain the width - let the height float.
301
302                         if ((($height * 9) / 16) > $width) {
303                                 $dest_width = $max;
304                                 $dest_height = intval(($height * $max) / $width);
305                         } elseif ($width > $height) {
306                                 // else constrain both dimensions
307                                 $dest_width = $max;
308                                 $dest_height = intval(($height * $max) / $width);
309                         } else {
310                                 $dest_width = intval(($width * $max) / $height);
311                                 $dest_height = $max;
312                         }
313                 } else {
314                         if ($width > $max) {
315                                 $dest_width = $max;
316                                 $dest_height = intval(($height * $max) / $width);
317                         } else {
318                                 if ($height > $max) {
319                                         // very tall image (greater than 16:9)
320                                         // but width is OK - don't do anything
321
322                                         if ((($height * 9) / 16) > $width) {
323                                                 $dest_width = $width;
324                                                 $dest_height = $height;
325                                         } else {
326                                                 $dest_width = intval(($width * $max) / $height);
327                                                 $dest_height = $max;
328                                         }
329                                 } else {
330                                         $dest_width = $width;
331                                         $dest_height = $height;
332                                 }
333                         }
334                 }
335
336
337                 if ($this->isImagick()) {
338                         /*
339                          * If it is not animated, there will be only one iteration here,
340                          * so don't bother checking
341                          */
342                         // Don't forget to go back to the first frame
343                         $this->image->setFirstIterator();
344                         do {
345                                 // FIXME - implement horizantal bias for scaling as in followin GD functions
346                                 // to allow very tall images to be constrained only horizontally.
347
348                                 $this->image->scaleImage($dest_width, $dest_height);
349                         } while ($this->image->nextImage());
350
351                         // These may not be necessary any more
352                         $this->width  = $this->image->getImageWidth();
353                         $this->height = $this->image->getImageHeight();
354
355                         return;
356                 }
357
358
359                 $dest = imagecreatetruecolor($dest_width, $dest_height);
360                 imagealphablending($dest, false);
361                 imagesavealpha($dest, true);
362                 if ($this->type=='image/png') {
363                         imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
364                 }
365                 imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
366                 if ($this->image) {
367                         imagedestroy($this->image);
368                 }
369                 $this->image = $dest;
370                 $this->width  = imagesx($this->image);
371                 $this->height = imagesy($this->image);
372         }
373
374         /**
375          * @param integer $degrees degrees to rotate image
376          * @return mixed
377          */
378         public function rotate($degrees)
379         {
380                 if (!$this->isValid()) {
381                         return false;
382                 }
383
384                 if ($this->isImagick()) {
385                         $this->image->setFirstIterator();
386                         do {
387                                 $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate()
388                         } while ($this->image->nextImage());
389                         return;
390                 }
391
392                 // if script dies at this point check memory_limit setting in php.ini
393                 $this->image  = imagerotate($this->image, $degrees, 0);
394                 $this->width  = imagesx($this->image);
395                 $this->height = imagesy($this->image);
396         }
397
398         /**
399          * @param boolean $horiz optional, default true
400          * @param boolean $vert  optional, default false
401          * @return mixed
402          */
403         public function flip($horiz = true, $vert = false)
404         {
405                 if (!$this->isValid()) {
406                         return false;
407                 }
408
409                 if ($this->isImagick()) {
410                         $this->image->setFirstIterator();
411                         do {
412                                 if ($horiz) {
413                                         $this->image->flipImage();
414                                 }
415                                 if ($vert) {
416                                         $this->image->flopImage();
417                                 }
418                         } while ($this->image->nextImage());
419                         return;
420                 }
421
422                 $w = imagesx($this->image);
423                 $h = imagesy($this->image);
424                 $flipped = imagecreate($w, $h);
425                 if ($horiz) {
426                         for ($x = 0; $x < $w; $x++) {
427                                 imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h);
428                         }
429                 }
430                 if ($vert) {
431                         for ($y = 0; $y < $h; $y++) {
432                                 imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1);
433                         }
434                 }
435                 $this->image = $flipped;
436         }
437
438         /**
439          * @param string $filename filename
440          * @return mixed
441          */
442         public function orient($filename)
443         {
444                 if ($this->isImagick()) {
445                         // based off comment on http://php.net/manual/en/imagick.getimageorientation.php
446                         $orientation = $this->image->getImageOrientation();
447                         switch ($orientation) {
448                                 case imagick::ORIENTATION_BOTTOMRIGHT:
449                                         $this->image->rotateimage("#000", 180);
450                                         break;
451                                 case imagick::ORIENTATION_RIGHTTOP:
452                                         $this->image->rotateimage("#000", 90);
453                                         break;
454                                 case imagick::ORIENTATION_LEFTBOTTOM:
455                                         $this->image->rotateimage("#000", -90);
456                                         break;
457                         }
458
459                         $this->image->setImageOrientation(imagick::ORIENTATION_TOPLEFT);
460                         return true;
461                 }
462                 // based off comment on http://php.net/manual/en/function.imagerotate.php
463
464                 if (!$this->isValid()) {
465                         return false;
466                 }
467
468                 if ((!function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) {
469                         return;
470                 }
471
472                 $exif = @exif_read_data($filename, null, true);
473                 if (!$exif) {
474                         return;
475                 }
476
477                 $ort = $exif['IFD0']['Orientation'];
478
479                 switch ($ort) {
480                         case 1: // nothing
481                                 break;
482
483                         case 2: // horizontal flip
484                                 $this->flip();
485                                 break;
486
487                         case 3: // 180 rotate left
488                                 $this->rotate(180);
489                                 break;
490
491                         case 4: // vertical flip
492                                 $this->flip(false, true);
493                                 break;
494
495                         case 5: // vertical flip + 90 rotate right
496                                 $this->flip(false, true);
497                                 $this->rotate(-90);
498                                 break;
499
500                         case 6: // 90 rotate right
501                                 $this->rotate(-90);
502                                 break;
503
504                         case 7: // horizontal flip + 90 rotate right
505                                 $this->flip();
506                                 $this->rotate(-90);
507                                 break;
508
509                         case 8: // 90 rotate left
510                                 $this->rotate(90);
511                                 break;
512                 }
513
514                 //      logger('exif: ' . print_r($exif,true));
515                 return $exif;
516         }
517
518         /**
519          * @param integer $min minimum dimension
520          * @return mixed
521          */
522         public function scaleImageUp($min)
523         {
524                 if (!$this->isValid()) {
525                         return false;
526                 }
527
528                 $width = $this->getWidth();
529                 $height = $this->getHeight();
530
531                 $dest_width = $dest_height = 0;
532
533                 if ((!$width)|| (!$height)) {
534                         return false;
535                 }
536
537                 if ($width < $min && $height < $min) {
538                         if ($width > $height) {
539                                 $dest_width = $min;
540                                 $dest_height = intval(($height * $min) / $width);
541                         } else {
542                                 $dest_width = intval(($width * $min) / $height);
543                                 $dest_height = $min;
544                         }
545                 } else {
546                         if ($width < $min) {
547                                 $dest_width = $min;
548                                 $dest_height = intval(($height * $min) / $width);
549                         } else {
550                                 if ($height < $min) {
551                                         $dest_width = intval(($width * $min) / $height);
552                                         $dest_height = $min;
553                                 } else {
554                                         $dest_width = $width;
555                                         $dest_height = $height;
556                                 }
557                         }
558                 }
559
560                 if ($this->isImagick()) {
561                         return $this->scaleImage($dest_width, $dest_height);
562                 }
563
564                 $dest = imagecreatetruecolor($dest_width, $dest_height);
565                 imagealphablending($dest, false);
566                 imagesavealpha($dest, true);
567                 if ($this->type=='image/png') {
568                         imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
569                 }
570                 imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
571                 if ($this->image) {
572                         imagedestroy($this->image);
573                 }
574                 $this->image = $dest;
575                 $this->width  = imagesx($this->image);
576                 $this->height = imagesy($this->image);
577         }
578
579         /**
580          * @param integer $dim dimension
581          * @return mixed
582          */
583         public function scaleImageSquare($dim)
584         {
585                 if (!$this->isValid()) {
586                         return false;
587                 }
588
589                 if ($this->isImagick()) {
590                         $this->image->setFirstIterator();
591                         do {
592                                 $this->image->scaleImage($dim, $dim);
593                         } while ($this->image->nextImage());
594                         return;
595                 }
596
597                 $dest = imagecreatetruecolor($dim, $dim);
598                 imagealphablending($dest, false);
599                 imagesavealpha($dest, true);
600                 if ($this->type=='image/png') {
601                         imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
602                 }
603                 imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height);
604                 if ($this->image) {
605                         imagedestroy($this->image);
606                 }
607                 $this->image = $dest;
608                 $this->width  = imagesx($this->image);
609                 $this->height = imagesy($this->image);
610         }
611
612         /**
613          * @param integer $max maximum
614          * @param integer $x   x coordinate
615          * @param integer $y   y coordinate
616          * @param integer $w   width
617          * @param integer $h   height
618          * @return mixed
619          */
620         public function cropImage($max, $x, $y, $w, $h)
621         {
622                 if (!$this->isValid()) {
623                         return false;
624                 }
625
626                 if ($this->isImagick()) {
627                         $this->image->setFirstIterator();
628                         do {
629                                 $this->image->cropImage($w, $h, $x, $y);
630                                 /*
631                                  * We need to remove the canva,
632                                  * or the image is not resized to the crop:
633                                  * http://php.net/manual/en/imagick.cropimage.php#97232
634                                  */
635                                 $this->image->setImagePage(0, 0, 0, 0);
636                         } while ($this->image->nextImage());
637                         return $this->scaleImage($max);
638                 }
639
640                 $dest = imagecreatetruecolor($max, $max);
641                 imagealphablending($dest, false);
642                 imagesavealpha($dest, true);
643                 if ($this->type=='image/png') {
644                         imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
645                 }
646                 imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h);
647                 if ($this->image) {
648                         imagedestroy($this->image);
649                 }
650                 $this->image = $dest;
651                 $this->width  = imagesx($this->image);
652                 $this->height = imagesy($this->image);
653         }
654
655         /**
656          * @param string $path file path
657          * @return mixed
658          */
659         public function saveImage($path)
660         {
661                 if (!$this->isValid()) {
662                         return false;
663                 }
664
665                 $string = $this->imageString();
666
667                 $a = get_app();
668
669                 $stamp1 = microtime(true);
670                 file_put_contents($path, $string);
671                 $a->save_timestamp($stamp1, "file");
672         }
673
674         /**
675          * @return mixed
676          */
677         public function imageString()
678         {
679                 if (!$this->isValid()) {
680                         return false;
681                 }
682
683                 if ($this->isImagick()) {
684                         /* Clean it */
685                         $this->image = $this->image->deconstructImages();
686                         $string = $this->image->getImagesBlob();
687                         return $string;
688                 }
689
690                 $quality = false;
691
692                 ob_start();
693
694                 // Enable interlacing
695                 imageinterlace($this->image, true);
696
697                 switch ($this->getType()) {
698                         case "image/png":
699                                 $quality = Config::get('system', 'png_quality');
700                                 if ((!$quality) || ($quality > 9)) {
701                                         $quality = PNG_QUALITY;
702                                 }
703                                 imagepng($this->image, null, $quality);
704                                 break;
705                         case "image/jpeg":
706                                 $quality = Config::get('system', 'jpeg_quality');
707                                 if ((!$quality) || ($quality > 100)) {
708                                         $quality = JPEG_QUALITY;
709                                 }
710                                 imagejpeg($this->image, null, $quality);
711                 }
712                 $string = ob_get_contents();
713                 ob_end_clean();
714
715                 return $string;
716         }
717
718         /**
719          * @param integer $uid       uid
720          * @param integer $cid       cid
721          * @param integer $rid       rid
722          * @param string  $filename  filename
723          * @param string  $album     album name
724          * @param integer $scale     scale
725          * @param integer $profile   optional, default = 0
726          * @param string  $allow_cid optional, default = ''
727          * @param string  $allow_gid optional, default = ''
728          * @param string  $deny_cid  optional, default = ''
729          * @param string  $deny_gid  optional, default = ''
730          * @param string  $desc      optional, default = ''
731          * @return object
732          */
733         public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '')
734         {
735                 $r = dba::select('photo', array('guid'), array("`resource-id` = ? AND `guid` != ?", $rid, ''), array('limit' => 1));
736                 if (DBM::is_result($r)) {
737                         $guid = $r['guid'];
738                 } else {
739                         $guid = get_guid();
740                 }
741
742                 $x = dba::select('photo', array('id'), array('resource-id' => $rid, 'uid' => $uid, 'contact-id' => $cid, 'scale' => $scale), array('limit' => 1));
743
744                 $fields = array('uid' => $uid, 'contact-id' => $cid, 'guid' => $guid, 'resource-id' => $rid, 'created' => datetime_convert(), 'edited' => datetime_convert(),
745                                 'filename' => basename($filename), 'type' => $this->getType(), 'album' => $album, 'height' => $this->getHeight(), 'width' => $this->getWidth(),
746                                 'datasize' => strlen($this->imageString()), 'data' => $this->imageString(), 'scale' => $scale, 'profile' => $profile,
747                                 'allow_cid' => $allow_cid, 'allow_gid' => $allow_gid, 'deny_cid' => $deny_cid, 'deny_gid' => $deny_gid, 'desc' => $desc);
748
749                 if (DBM::is_result($x)) {
750                         $r = dba::update('photo', $fields, array('id' => $x['id']));
751                 } else {
752                         $r = dba::insert('photo', $fields);
753                 }
754
755                 return $r;
756         }
757
758         /**
759          * Guess image mimetype from filename or from Content-Type header
760          *
761          * @param string  $filename Image filename
762          * @param boolean $fromcurl Check Content-Type header from curl request
763          *
764          * @return object
765          */
766         public function guessImageType($filename, $fromcurl = false)
767         {
768                 logger('Photo: guessImageType: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG);
769                 $type = null;
770                 if ($fromcurl) {
771                         $a = get_app();
772                         $headers=array();
773                         $h = explode("\n", $a->get_curl_headers());
774                         foreach ($h as $l) {
775                                 list($k,$v) = array_map("trim", explode(":", trim($l), 2));
776                                 $headers[$k] = $v;
777                         }
778                         if (array_key_exists('Content-Type', $headers))
779                                 $type = $headers['Content-Type'];
780                 }
781                 if (is_null($type)) {
782                         // Guessing from extension? Isn't that... dangerous?
783                         if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) {
784                                 /**
785                                  * Well, this not much better,
786                                  * but at least it comes from the data inside the image,
787                                  * we won't be tricked by a manipulated extension
788                                  */
789                                 $image = new Imagick($filename);
790                                 $type = $image->getImageMimeType();
791                                 $image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
792                         } else {
793                                 $ext = pathinfo($filename, PATHINFO_EXTENSION);
794                                 $types = $this->supportedTypes();
795                                 $type = "image/jpeg";
796                                 foreach ($types as $m => $e) {
797                                         if ($ext == $e) {
798                                                 $type = $m;
799                                         }
800                                 }
801                         }
802                 }
803                 logger('Photo: guessImageType: type='.$type, LOGGER_DEBUG);
804                 return $type;
805         }
806
807         /**
808          * @brief Updates the avatar links in a contact only if needed
809          *
810          * @param string $avatar Link to avatar picture
811          * @param int    $uid    User id of contact owner
812          * @param int    $cid    Contact id
813          * @param bool   $force  force picture update
814          *
815          * @return array Returns array of the different avatar sizes
816          */
817         public function updateContactAvatar($avatar, $uid, $cid, $force = false)
818         {
819                 // Limit = 1 returns the row so no need for dba:inArray()
820                 $r = dba::select('contact', array('avatar', 'photo', 'thumb', 'micro', 'nurl'), array('id' => $cid), array('limit' => 1));
821                 if (!DBM::is_result($r)) {
822                         return false;
823                 } else {
824                         $data = array($r["photo"], $r["thumb"], $r["micro"]);
825                 }
826
827                 if (($r["avatar"] != $avatar) || $force) {
828                         $photos = $this->importProfilePhoto($avatar, $uid, $cid, true);
829
830                         if ($photos) {
831                                 dba::update(
832                                         'contact',
833                                         array('avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => datetime_convert()),
834                                         array('id' => $cid)
835                                 );
836
837                                 // Update the public contact (contact id = 0)
838                                 if ($uid != 0) {
839                                         $pcontact = dba::select('contact', array('id'), array('nurl' => $r[0]['nurl']), array('limit' => 1));
840                                         if (DBM::is_result($pcontact)) {
841                                                 $this->updateContactAvatar($avatar, 0, $pcontact['id'], $force);
842                                         }
843                                 }
844
845                                 return $photos;
846                         }
847                 }
848
849                 return $data;
850         }
851
852         /**
853          * @param string  $photo         photo
854          * @param integer $uid           user id
855          * @param integer $cid           contact id
856          * @param boolean $quit_on_error optional, default false
857          * @return array
858          */
859         private function importProfilePhoto($photo, $uid, $cid, $quit_on_error = false)
860         {
861                 $r = dba::select(
862                         'photo',
863                         array('resource-id'),
864                         array('uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'album' => 'Contact Photos'),
865                         array('limit' => 1)
866                 );
867
868                 if (DBM::is_result($r) && strlen($r['resource-id'])) {
869                         $hash = $r['resource-id'];
870                 } else {
871                         $hash = photo_new_resource();
872                 }
873         
874                 $photo_failure = false;
875         
876                 $filename = basename($photo);
877                 $img_str = fetch_url($photo, true);
878         
879                 if ($quit_on_error && ($img_str == "")) {
880                         return false;
881                 }
882         
883                 $type = $this->guessImageType($photo, true);
884                 $img = new Photo($img_str, $type);
885                 if ($img->isValid()) {
886                         $img->scaleImageSquare(175);
887         
888                         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4);
889         
890                         if ($r === false) {
891                                 $photo_failure = true;
892                         }
893         
894                         $img->scaleImage(80);
895         
896                         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5);
897         
898                         if ($r === false) {
899                                 $photo_failure = true;
900                         }
901         
902                         $img->scaleImage(48);
903         
904                         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6);
905         
906                         if ($r === false) {
907                                 $photo_failure = true;
908                         }
909         
910                         $suffix = '?ts='.time();
911         
912                         $photo = System::baseUrl() . '/photo/' . $hash . '-4.' . $img->getExt() . $suffix;
913                         $thumb = System::baseUrl() . '/photo/' . $hash . '-5.' . $img->getExt() . $suffix;
914                         $micro = System::baseUrl() . '/photo/' . $hash . '-6.' . $img->getExt() . $suffix;
915         
916                         // Remove the cached photo
917                         $a = get_app();
918                         $basepath = $a->get_basepath();
919         
920                         if (is_dir($basepath."/photo")) {
921                                 $filename = $basepath.'/photo/'.$hash.'-4.'.$img->getExt();
922                                 if (file_exists($filename)) {
923                                         unlink($filename);
924                                 }
925                                 $filename = $basepath.'/photo/'.$hash.'-5.'.$img->getExt();
926                                 if (file_exists($filename)) {
927                                         unlink($filename);
928                                 }
929                                 $filename = $basepath.'/photo/'.$hash.'-6.'.$img->getExt();
930                                 if (file_exists($filename)) {
931                                         unlink($filename);
932                                 }
933                         }
934                 } else {
935                         $photo_failure = true;
936                 }
937         
938                 if ($photo_failure && $quit_on_error) {
939                         return false;
940                 }
941         
942                 if ($photo_failure) {
943                         $photo = System::baseUrl() . '/images/person-175.jpg';
944                         $thumb = System::baseUrl() . '/images/person-80.jpg';
945                         $micro = System::baseUrl() . '/images/person-48.jpg';
946                 }
947         
948                 return(array($photo, $thumb, $micro));
949         }
950
951         /**
952          * @param string $url url
953          * @return object
954          */
955         public function getPhotoInfo($url)
956         {
957                 $data = array();
958         
959                 $data = Cache::get($url);
960         
961                 if (is_null($data) || !$data || !is_array($data)) {
962                         $img_str = fetch_url($url, true, $redirects, 4);
963                         $filesize = strlen($img_str);
964         
965                         if (function_exists("getimagesizefromstring")) {
966                                 $data = getimagesizefromstring($img_str);
967                         } else {
968                                 $tempfile = tempnam(get_temppath(), "cache");
969         
970                                 $a = get_app();
971                                 $stamp1 = microtime(true);
972                                 file_put_contents($tempfile, $img_str);
973                                 $a->save_timestamp($stamp1, "file");
974         
975                                 $data = getimagesize($tempfile);
976                                 unlink($tempfile);
977                         }
978         
979                         if ($data) {
980                                 $data["size"] = $filesize;
981                         }
982         
983                         Cache::set($url, $data);
984                 }
985         
986                 return $data;
987         }
988
989         /**
990          * @param integer $width  width
991          * @param integer $height height
992          * @param integer $max    max
993          * @return array
994          */
995         public function scaleImageTo($width, $height, $max)
996         {
997                 $dest_width = $dest_height = 0;
998         
999                 if ((!$width) || (!$height)) {
1000                         return false;
1001                 }
1002         
1003                 if ($width > $max && $height > $max) {
1004                         // very tall image (greater than 16:9)
1005                         // constrain the width - let the height float.
1006         
1007                         if ((($height * 9) / 16) > $width) {
1008                                 $dest_width = $max;
1009                                 $dest_height = intval(($height * $max) / $width);
1010                         } elseif ($width > $height) {
1011                                 // else constrain both dimensions
1012                                 $dest_width = $max;
1013                                 $dest_height = intval(($height * $max) / $width);
1014                         } else {
1015                                 $dest_width = intval(($width * $max) / $height);
1016                                 $dest_height = $max;
1017                         }
1018                 } else {
1019                         if ($width > $max) {
1020                                 $dest_width = $max;
1021                                 $dest_height = intval(($height * $max) / $width);
1022                         } else {
1023                                 if ($height > $max) {
1024                                         // very tall image (greater than 16:9)
1025                                         // but width is OK - don't do anything
1026         
1027                                         if ((($height * 9) / 16) > $width) {
1028                                                 $dest_width = $width;
1029                                                 $dest_height = $height;
1030                                         } else {
1031                                                 $dest_width = intval(($width * $max) / $height);
1032                                                 $dest_height = $max;
1033                                         }
1034                                 } else {
1035                                         $dest_width = $width;
1036                                         $dest_height = $height;
1037                                 }
1038                         }
1039                 }
1040                 return array("width" => $dest_width, "height" => $dest_height);
1041         }
1042
1043         /**
1044          * @brief This function doesn't seem to be used
1045          * @param object  $a         App
1046          * @param integer $uid       user id
1047          * @param string  $imagedata optional, default empty
1048          * @param string  $url       optional, default empty
1049          * @return array
1050          */
1051         private function storePhoto(App $a, $uid, $imagedata = "", $url = "")
1052         {
1053                 $r = q(
1054                         "SELECT `user`.`nickname`, `user`.`page-flags`, `contact`.`id` FROM `user` INNER JOIN `contact` on `user`.`uid` = `contact`.`uid`
1055                         WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 AND `contact`.`self` = 1 LIMIT 1",
1056                         intval($uid)
1057                 );
1058         
1059                 if (!DBM::is_result($r)) {
1060                         logger("Can't detect user data for uid ".$uid, LOGGER_DEBUG);
1061                         return(array());
1062                 }
1063         
1064                 $page_owner_nick  = $r[0]['nickname'];
1065         
1066                 /// @TODO
1067                 /// $default_cid      = $r[0]['id'];
1068                 /// $community_page   = (($r[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1069         
1070                 if ((strlen($imagedata) == 0) && ($url == "")) {
1071                         logger("No image data and no url provided", LOGGER_DEBUG);
1072                         return(array());
1073                 } elseif (strlen($imagedata) == 0) {
1074                         logger("Uploading picture from ".$url, LOGGER_DEBUG);
1075         
1076                         $stamp1 = microtime(true);
1077                         $imagedata = @file_get_contents($url);
1078                         $a->save_timestamp($stamp1, "file");
1079                 }
1080         
1081                 $maximagesize = Config::get('system', 'maximagesize');
1082         
1083                 if (($maximagesize) && (strlen($imagedata) > $maximagesize)) {
1084                         logger("Image exceeds size limit of ".$maximagesize, LOGGER_DEBUG);
1085                         return(array());
1086                 }
1087         
1088                 $tempfile = tempnam(get_temppath(), "cache");
1089         
1090                 $stamp1 = microtime(true);
1091                 file_put_contents($tempfile, $imagedata);
1092                 $a->save_timestamp($stamp1, "file");
1093         
1094                 $data = getimagesize($tempfile);
1095         
1096                 if (!isset($data["mime"])) {
1097                         unlink($tempfile);
1098                         logger("File is no picture", LOGGER_DEBUG);
1099                         return(array());
1100                 }
1101         
1102                 $ph = new Photo($imagedata, $data["mime"]);
1103         
1104                 if (!$ph->isValid()) {
1105                         unlink($tempfile);
1106                         logger("Picture is no valid picture", LOGGER_DEBUG);
1107                         return(array());
1108                 }
1109         
1110                 $ph->orient($tempfile);
1111                 unlink($tempfile);
1112         
1113                 $max_length = Config::get('system', 'max_image_length');
1114                 if (! $max_length) {
1115                         $max_length = MAX_IMAGE_LENGTH;
1116                 }
1117                 if ($max_length > 0) {
1118                         $ph->scaleImage($max_length);
1119                 }
1120         
1121                 $width = $ph->getWidth();
1122                 $height = $ph->getHeight();
1123         
1124                 $hash = photo_new_resource();
1125         
1126                 $smallest = 0;
1127         
1128                 // Pictures are always public by now
1129                 //$defperm = '<'.$default_cid.'>';
1130                 $defperm = "";
1131                 $visitor = 0;
1132         
1133                 $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 0, 0, $defperm);
1134         
1135                 if (!$r) {
1136                         logger("Picture couldn't be stored", LOGGER_DEBUG);
1137                         return(array());
1138                 }
1139         
1140                 $image = array("page" => System::baseUrl().'/photos/'.$page_owner_nick.'/image/'.$hash,
1141                                 "full" => System::baseUrl()."/photo/{$hash}-0.".$ph->getExt());
1142         
1143                 if ($width > 800 || $height > 800) {
1144                         $image["large"] = System::baseUrl()."/photo/{$hash}-0.".$ph->getExt();
1145                 }
1146         
1147                 if ($width > 640 || $height > 640) {
1148                         $ph->scaleImage(640);
1149                         $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 1, 0, $defperm);
1150                         if ($r) {
1151                                 $image["medium"] = System::baseUrl()."/photo/{$hash}-1.".$ph->getExt();
1152                         }
1153                 }
1154         
1155                 if ($width > 320 || $height > 320) {
1156                         $ph->scaleImage(320);
1157                         $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 2, 0, $defperm);
1158                         if ($r) {
1159                                 $image["small"] = System::baseUrl()."/photo/{$hash}-2.".$ph->getExt();
1160                         }
1161                 }
1162         
1163                 if ($width > 160 && $height > 160) {
1164                         $x = 0;
1165                         $y = 0;
1166         
1167                         $min = $ph->getWidth();
1168                         if ($min > 160) {
1169                                 $x = ($min - 160) / 2;
1170                         }
1171         
1172                         if ($ph->getHeight() < $min) {
1173                                 $min = $ph->getHeight();
1174                                 if ($min > 160) {
1175                                         $y = ($min - 160) / 2;
1176                                 }
1177                         }
1178         
1179                         $min = 160;
1180                         $ph->cropImage(160, $x, $y, $min, $min);
1181         
1182                         $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 3, 0, $defperm);
1183                         if ($r) {
1184                                 $image["thumb"] = System::baseUrl()."/photo/{$hash}-3.".$ph->getExt();
1185                         }
1186                 }
1187         
1188                 // Set the full image as preview image. This will be overwritten, if the picture is larger than 640.
1189                 $image["preview"] = $image["full"];
1190         
1191                 // Deactivated, since that would result in a cropped preview, if the picture wasn't larger than 320
1192                 //if (isset($image["thumb"]))
1193                 //      $image["preview"] = $image["thumb"];
1194         
1195                 // Unsure, if this should be activated or deactivated
1196                 //if (isset($image["small"]))
1197                 //      $image["preview"] = $image["small"];
1198         
1199                 if (isset($image["medium"])) {
1200                         $image["preview"] = $image["medium"];
1201                 }
1202         
1203                 return($image);
1204         }
1205 }