Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

1069 wiersze
26 KiB

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