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