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