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