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