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