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