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