]> git.mxchange.org Git - friendica.git/blob - include/Photo.php
f02ee130a0c865ce878eac2788011933bbeed5a8
[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         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 = $this->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 = q("SELECT `guid` FROM `photo` WHERE `resource-id` = '%s' AND `guid` != '' LIMIT 1",
632                         dbesc($rid)
633                 );
634                 if (dbm::is_result($r)) {
635                         $guid = $r[0]['guid'];
636                 } else {
637                         $guid = get_guid();
638                 }
639
640                 $x = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d AND `contact-id` = %d AND `scale` = %d LIMIT 1",
641                         dbesc($rid),
642                         intval($uid),
643                         intval($cid),
644                         intval($scale)
645                 );
646                 if (dbm::is_result($x)) {
647                         $r = q("UPDATE `photo`
648                                 SET `uid` = %d,
649                                 `contact-id` = %d,
650                                 `guid` = '%s',
651                                 `resource-id` = '%s',
652                                 `created` = '%s',
653                                 `edited` = '%s',
654                                 `filename` = '%s',
655                                 `type` = '%s',
656                                 `album` = '%s',
657                                 `height` = %d,
658                                 `width` = %d,
659                                 `datasize` = %d,
660                                 `data` = '%s',
661                                 `scale` = %d,
662                                 `profile` = %d,
663                                 `allow_cid` = '%s',
664                                 `allow_gid` = '%s',
665                                 `deny_cid` = '%s',
666                                 `deny_gid` = '%s',
667                                 `desc` = '%s'
668                                 WHERE `id` = %d",
669
670                                 intval($uid),
671                                 intval($cid),
672                                 dbesc($guid),
673                                 dbesc($rid),
674                                 dbesc(datetime_convert()),
675                                 dbesc(datetime_convert()),
676                                 dbesc(basename($filename)),
677                                 dbesc($this->getType()),
678                                 dbesc($album),
679                                 intval($this->getHeight()),
680                                 intval($this->getWidth()),
681                                 dbesc(strlen($this->imageString())),
682                                 dbesc($this->imageString()),
683                                 intval($scale),
684                                 intval($profile),
685                                 dbesc($allow_cid),
686                                 dbesc($allow_gid),
687                                 dbesc($deny_cid),
688                                 dbesc($deny_gid),
689                                 dbesc($desc),
690                                 intval($x[0]['id'])
691                         );
692                 } else {
693                         $r = q("INSERT INTO `photo`
694                                 (`uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid`, `desc`)
695                                 VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s')",
696                                 intval($uid),
697                                 intval($cid),
698                                 dbesc($guid),
699                                 dbesc($rid),
700                                 dbesc(datetime_convert()),
701                                 dbesc(datetime_convert()),
702                                 dbesc(basename($filename)),
703                                 dbesc($this->getType()),
704                                 dbesc($album),
705                                 intval($this->getHeight()),
706                                 intval($this->getWidth()),
707                                 dbesc(strlen($this->imageString())),
708                                 dbesc($this->imageString()),
709                                 intval($scale),
710                                 intval($profile),
711                                 dbesc($allow_cid),
712                                 dbesc($allow_gid),
713                                 dbesc($deny_cid),
714                                 dbesc($deny_gid),
715                                 dbesc($desc)
716                         );
717                 }
718
719                 return $r;
720         }
721 }
722
723
724 /**
725  * Guess image mimetype from filename or from Content-Type header
726  *
727  * @arg $filename string Image filename
728  * @arg $fromcurl boolean Check Content-Type header from curl request
729  */
730 function guess_image_type($filename, $fromcurl=false) {
731         logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG);
732         $type = null;
733         if ($fromcurl) {
734                 $a = get_app();
735                 $headers=array();
736                 $h = explode("\n",$a->get_curl_headers());
737                 foreach ($h as $l) {
738                         list($k,$v) = array_map("trim", explode(":", trim($l), 2));
739                         $headers[$k] = $v;
740                 }
741                 if (array_key_exists('Content-Type', $headers))
742                         $type = $headers['Content-Type'];
743         }
744         if (is_null($type)){
745                 // Guessing from extension? Isn't that... dangerous?
746                 if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) {
747                         /**
748                          * Well, this not much better,
749                          * but at least it comes from the data inside the image,
750                          * we won't be tricked by a manipulated extension
751                          */
752                         $image = new Imagick($filename);
753                         $type = $image->getImageMimeType();
754                         $image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
755                 } else {
756                         $ext = pathinfo($filename, PATHINFO_EXTENSION);
757                         $types = Photo::supportedTypes();
758                         $type = "image/jpeg";
759                         foreach ($types as $m => $e){
760                                 if ($ext == $e) {
761                                         $type = $m;
762                                 }
763                         }
764                 }
765         }
766         logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG);
767         return $type;
768
769 }
770
771 /**
772  * @brief Updates the avatar links in a contact only if needed
773  *
774  * @param string $avatar Link to avatar picture
775  * @param int $uid User id of contact owner
776  * @param int $cid Contact id
777  * @param bool $force force picture update
778  *
779  * @return array Returns array of the different avatar sizes
780  */
781 function update_contact_avatar($avatar, $uid, $cid, $force = false) {
782         $r = q("SELECT `avatar`, `photo`, `thumb`, `micro`, `nurl` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid));
783         if (!dbm::is_result($r)) {
784                 return false;
785         } else {
786                 $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]);
787         }
788
789         if (($r[0]["avatar"] != $avatar) || $force) {
790                 $photos = import_profile_photo($avatar, $uid, $cid, true);
791
792                 if ($photos) {
793                         q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d",
794                                 dbesc($avatar), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]),
795                                 dbesc(datetime_convert()), intval($cid));
796
797                         // Update the public contact (contact id = 0)
798                         if ($uid != 0) {
799                                 $pcontact = dba::select('contact', array('id'), array('nurl' => $r[0]['nurl']), array('limit' => 1));
800                                 if (dbm::is_result($pcontact)) {
801                                         update_contact_avatar($avatar, 0, $pcontact['id'], $force);
802                                 }
803                         }
804
805                         return $photos;
806                 }
807         }
808
809         return $data;
810 }
811
812 function import_profile_photo($photo, $uid, $cid, $quit_on_error = false) {
813
814         $r = q("SELECT `resource-id` FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `scale` = 4 AND `album` = 'Contact Photos' LIMIT 1",
815                 intval($uid),
816                 intval($cid)
817         );
818         if (dbm::is_result($r) && strlen($r[0]['resource-id'])) {
819                 $hash = $r[0]['resource-id'];
820         } else {
821                 $hash = photo_new_resource();
822         }
823
824         $photo_failure = false;
825
826         $filename = basename($photo);
827         $img_str = fetch_url($photo, true);
828
829         if ($quit_on_error && ($img_str == "")) {
830                 return false;
831         }
832
833         $type = guess_image_type($photo, true);
834         $img = new Photo($img_str, $type);
835         if ($img->is_valid()) {
836
837                 $img->scaleImageSquare(175);
838
839                 $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4);
840
841                 if ($r === false)
842                         $photo_failure = true;
843
844                 $img->scaleImage(80);
845
846                 $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5);
847
848                 if ($r === false)
849                         $photo_failure = true;
850
851                 $img->scaleImage(48);
852
853                 $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6);
854
855                 if ($r === false) {
856                         $photo_failure = true;
857                 }
858
859                 $suffix = '?ts='.time();
860
861                 $photo = System::baseUrl() . '/photo/' . $hash . '-4.' . $img->getExt() . $suffix;
862                 $thumb = System::baseUrl() . '/photo/' . $hash . '-5.' . $img->getExt() . $suffix;
863                 $micro = System::baseUrl() . '/photo/' . $hash . '-6.' . $img->getExt() . $suffix;
864
865                 // Remove the cached photo
866                 $a = get_app();
867                 $basepath = $a->get_basepath();
868
869                 if (is_dir($basepath."/photo")) {
870                         $filename = $basepath.'/photo/'.$hash.'-4.'.$img->getExt();
871                         if (file_exists($filename)) {
872                                 unlink($filename);
873                         }
874                         $filename = $basepath.'/photo/'.$hash.'-5.'.$img->getExt();
875                         if (file_exists($filename)) {
876                                 unlink($filename);
877                         }
878                         $filename = $basepath.'/photo/'.$hash.'-6.'.$img->getExt();
879                         if (file_exists($filename)) {
880                                 unlink($filename);
881                         }
882                 }
883         } else {
884                 $photo_failure = true;
885         }
886
887         if ($photo_failure && $quit_on_error) {
888                 return false;
889         }
890
891         if ($photo_failure) {
892                 $photo = System::baseUrl() . '/images/person-175.jpg';
893                 $thumb = System::baseUrl() . '/images/person-80.jpg';
894                 $micro = System::baseUrl() . '/images/person-48.jpg';
895         }
896
897         return(array($photo,$thumb,$micro));
898
899 }
900
901 function get_photo_info($url) {
902         $data = array();
903
904         $data = Cache::get($url);
905
906         if (is_null($data) || !$data || !is_array($data)) {
907                 $img_str = fetch_url($url, true, $redirects, 4);
908                 $filesize = strlen($img_str);
909
910                 if (function_exists("getimagesizefromstring")) {
911                         $data = getimagesizefromstring($img_str);
912                 } else {
913                         $tempfile = tempnam(get_temppath(), "cache");
914
915                         $a = get_app();
916                         $stamp1 = microtime(true);
917                         file_put_contents($tempfile, $img_str);
918                         $a->save_timestamp($stamp1, "file");
919
920                         $data = getimagesize($tempfile);
921                         unlink($tempfile);
922                 }
923
924                 if ($data) {
925                         $data["size"] = $filesize;
926                 }
927
928                 Cache::set($url, $data);
929         }
930
931         return $data;
932 }
933
934 function scale_image($width, $height, $max) {
935
936         $dest_width = $dest_height = 0;
937
938         if ((!$width) || (!$height)) {
939                 return false;
940         }
941
942         if ($width > $max && $height > $max) {
943
944                 // very tall image (greater than 16:9)
945                 // constrain the width - let the height float.
946
947                 if ((($height * 9) / 16) > $width) {
948                         $dest_width = $max;
949                         $dest_height = intval(($height * $max) / $width);
950                 } elseif ($width > $height) {
951                         // else constrain both dimensions
952                         $dest_width = $max;
953                         $dest_height = intval(($height * $max) / $width);
954                 } else {
955                         $dest_width = intval(($width * $max) / $height);
956                         $dest_height = $max;
957                 }
958         } else {
959                 if ($width > $max) {
960                         $dest_width = $max;
961                         $dest_height = intval(($height * $max) / $width);
962                 } else {
963                         if ($height > $max) {
964
965                                 // very tall image (greater than 16:9)
966                                 // but width is OK - don't do anything
967
968                                 if ((($height * 9) / 16) > $width) {
969                                         $dest_width = $width;
970                                         $dest_height = $height;
971                                 } else {
972                                         $dest_width = intval(($width * $max) / $height);
973                                         $dest_height = $max;
974                                 }
975                         } else {
976                                 $dest_width = $width;
977                                 $dest_height = $height;
978                         }
979                 }
980         }
981         return array("width" => $dest_width, "height" => $dest_height);
982 }
983
984 function store_photo(App $a, $uid, $imagedata = "", $url = "") {
985         $r = q("SELECT `user`.`nickname`, `user`.`page-flags`, `contact`.`id` FROM `user` INNER JOIN `contact` on `user`.`uid` = `contact`.`uid`
986                 WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 AND `contact`.`self` = 1 LIMIT 1",
987                 intval($uid));
988
989         if (!dbm::is_result($r)) {
990                 logger("Can't detect user data for uid ".$uid, LOGGER_DEBUG);
991                 return(array());
992         }
993
994         $page_owner_nick  = $r[0]['nickname'];
995
996         /// @TODO
997         /// $default_cid      = $r[0]['id'];
998         /// $community_page   = (($r[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
999
1000         if ((strlen($imagedata) == 0) && ($url == "")) {
1001                 logger("No image data and no url provided", LOGGER_DEBUG);
1002                 return(array());
1003         } elseif (strlen($imagedata) == 0) {
1004                 logger("Uploading picture from ".$url, LOGGER_DEBUG);
1005
1006                 $stamp1 = microtime(true);
1007                 $imagedata = @file_get_contents($url);
1008                 $a->save_timestamp($stamp1, "file");
1009         }
1010
1011         $maximagesize = get_config('system', 'maximagesize');
1012
1013         if (($maximagesize) && (strlen($imagedata) > $maximagesize)) {
1014                 logger("Image exceeds size limit of ".$maximagesize, LOGGER_DEBUG);
1015                 return(array());
1016         }
1017
1018 /*
1019         $r = q("select sum(octet_length(data)) as total from photo where uid = %d and scale = 0 and album != 'Contact Photos' ",
1020                 intval($uid)
1021         );
1022
1023         $limit = service_class_fetch($uid,'photo_upload_limit');
1024
1025         if (($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) {
1026                 logger("Image exceeds personal limit of uid ".$uid, LOGGER_DEBUG);
1027                 return(array());
1028         }
1029 */
1030
1031         $tempfile = tempnam(get_temppath(), "cache");
1032
1033         $stamp1 = microtime(true);
1034         file_put_contents($tempfile, $imagedata);
1035         $a->save_timestamp($stamp1, "file");
1036
1037         $data = getimagesize($tempfile);
1038
1039         if (!isset($data["mime"])) {
1040                 unlink($tempfile);
1041                 logger("File is no picture", LOGGER_DEBUG);
1042                 return(array());
1043         }
1044
1045         $ph = new Photo($imagedata, $data["mime"]);
1046
1047         if (!$ph->is_valid()) {
1048                 unlink($tempfile);
1049                 logger("Picture is no valid picture", LOGGER_DEBUG);
1050                 return(array());
1051         }
1052
1053         $ph->orient($tempfile);
1054         unlink($tempfile);
1055
1056         $max_length = get_config('system', 'max_image_length');
1057         if (! $max_length) {
1058                 $max_length = MAX_IMAGE_LENGTH;
1059         }
1060         if ($max_length > 0) {
1061                 $ph->scaleImage($max_length);
1062         }
1063
1064         $width = $ph->getWidth();
1065         $height = $ph->getHeight();
1066
1067         $hash = photo_new_resource();
1068
1069         $smallest = 0;
1070
1071         // Pictures are always public by now
1072         //$defperm = '<'.$default_cid.'>';
1073         $defperm = "";
1074         $visitor = 0;
1075
1076         $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 0, 0, $defperm);
1077
1078         if (!$r) {
1079                 logger("Picture couldn't be stored", LOGGER_DEBUG);
1080                 return(array());
1081         }
1082
1083         $image = array("page" => System::baseUrl().'/photos/'.$page_owner_nick.'/image/'.$hash,
1084                         "full" => System::baseUrl()."/photo/{$hash}-0.".$ph->getExt());
1085
1086         if ($width > 800 || $height > 800) {
1087                 $image["large"] = System::baseUrl()."/photo/{$hash}-0.".$ph->getExt();
1088         }
1089
1090         if ($width > 640 || $height > 640) {
1091                 $ph->scaleImage(640);
1092                 $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 1, 0, $defperm);
1093                 if ($r) {
1094                         $image["medium"] = System::baseUrl()."/photo/{$hash}-1.".$ph->getExt();
1095                 }
1096         }
1097
1098         if ($width > 320 || $height > 320) {
1099                 $ph->scaleImage(320);
1100                 $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 2, 0, $defperm);
1101                 if ($r) {
1102                         $image["small"] = System::baseUrl()."/photo/{$hash}-2.".$ph->getExt();
1103                 }
1104         }
1105
1106         if ($width > 160 && $height > 160) {
1107                 $x = 0;
1108                 $y = 0;
1109
1110                 $min = $ph->getWidth();
1111                 if ($min > 160) {
1112                         $x = ($min - 160) / 2;
1113                 }
1114
1115                 if ($ph->getHeight() < $min) {
1116                         $min = $ph->getHeight();
1117                         if ($min > 160) {
1118                                 $y = ($min - 160) / 2;
1119                         }
1120                 }
1121
1122                 $min = 160;
1123                 $ph->cropImage(160, $x, $y, $min, $min);
1124
1125                 $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 3, 0, $defperm);
1126                 if ($r) {
1127                         $image["thumb"] = System::baseUrl()."/photo/{$hash}-3.".$ph->getExt();
1128                 }
1129         }
1130
1131         // Set the full image as preview image. This will be overwritten, if the picture is larger than 640.
1132         $image["preview"] = $image["full"];
1133
1134         // Deactivated, since that would result in a cropped preview, if the picture wasn't larger than 320
1135         //if (isset($image["thumb"]))
1136         //      $image["preview"] = $image["thumb"];
1137
1138         // Unsure, if this should be activated or deactivated
1139         //if (isset($image["small"]))
1140         //      $image["preview"] = $image["small"];
1141
1142         if (isset($image["medium"])) {
1143                 $image["preview"] = $image["medium"];
1144         }
1145
1146         return($image);
1147 }