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