Photo.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. <?php
  2. ###
  3. # @name Photo Module
  4. # @author Tobias Reich
  5. # @copyright 2014 by Tobias Reich
  6. ###
  7. if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
  8. class Photo extends Module {
  9. private $database = null;
  10. private $settings = null;
  11. private $photoIDs = null;
  12. private $uploadsBig = null;
  13. private $uploadsThumb = null;
  14. public function __construct($database, $plugins, $settings, $photoIDs) {
  15. # Init vars
  16. $this->database = $database;
  17. $this->plugins = $plugins;
  18. $this->settings = $settings;
  19. $this->photoIDs = $photoIDs;
  20. # Set upload dirs
  21. $this->uploadsBig = __DIR__ . '/../../uploads/big/';
  22. $this->uploadsThumb = __DIR__ . '/../../uploads/thumb/';
  23. return true;
  24. }
  25. public function add($files, $albumID, $description = '', $tags = '') {
  26. if (!isset($this->database)) return false;
  27. # Call plugins
  28. $this->plugins(__METHOD__, 0, func_get_args());
  29. switch($albumID) {
  30. case 's':
  31. # s for public (share)
  32. $public = 1;
  33. $star = 0;
  34. $albumID = 0;
  35. break;
  36. case 'f':
  37. # f for starred (fav)
  38. $star = 1;
  39. $public = 0;
  40. $albumID = 0;
  41. break;
  42. default:
  43. $star = 0;
  44. $public = 0;
  45. break;
  46. }
  47. foreach ($files as $file) {
  48. if ($file['type']!=='image/jpeg'&&
  49. $file['type']!=='image/png'&&
  50. $file['type']!=='image/gif')
  51. return false;
  52. $id = str_replace('.', '', microtime(true));
  53. while(strlen($id)<14) $id .= 0;
  54. $tmp_name = $file['tmp_name'];
  55. $extension = array_reverse(explode('.', $file['name']));
  56. $extension = $extension[0];
  57. $photo_name = md5($id) . ".$extension";
  58. $path = $this->uploadsBig . $photo_name;
  59. # Import if not uploaded via web
  60. if (!is_uploaded_file($tmp_name)) {
  61. if (copy($tmp_name, $path)) { @unlink($tmp_name); }
  62. } else {
  63. move_uploaded_file($tmp_name, $path);
  64. }
  65. # Read infos
  66. $info = $this->getInfo($path);
  67. # Use title of file if IPTC title missing
  68. if ($info['title']==='') $info['title'] = mysqli_real_escape_string($this->database, substr(basename($file['name'], ".$extension"), 0, 30));
  69. # Use description parameter if set
  70. if ($description==='') $description = $info['description'];
  71. # Set orientation based on EXIF data
  72. if ($file['type']==='image/jpeg'&&isset($info['orientation'])&&$info['orientation']!==''&&isset($info['width'])&&isset($info['height'])) {
  73. if (!$this->adjustFile($path, $info)) return false;
  74. }
  75. # Create Thumb
  76. if (!$this->createThumb($path, $photo_name)) return false;
  77. # Save to DB
  78. $query = "INSERT INTO lychee_photos (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star)
  79. VALUES (
  80. '" . $id . "',
  81. '" . $info['title'] . "',
  82. '" . $photo_name . "',
  83. '" . $description . "',
  84. '" . $tags . "',
  85. '" . $info['type'] . "',
  86. '" . $info['width'] . "',
  87. '" . $info['height'] . "',
  88. '" . $info['size'] . "',
  89. '" . $info['iso'] . "',
  90. '" . $info['aperture'] . "',
  91. '" . $info['make'] . "',
  92. '" . $info['model'] . "',
  93. '" . $info['shutter'] . "',
  94. '" . $info['focal'] . "',
  95. '" . $info['takestamp'] . "',
  96. '" . md5($id) . ".jpeg',
  97. '" . $albumID . "',
  98. '" . $public . "',
  99. '" . $star . "');";
  100. $result = $this->database->query($query);
  101. if (!$result) return false;
  102. }
  103. # Call plugins
  104. $this->plugins(__METHOD__, 1, func_get_args());
  105. return true;
  106. }
  107. private function createThumb($url, $filename, $width = 200, $height = 200) {
  108. if (!isset($this->settings, $url, $filename)) return false;
  109. # Call plugins
  110. $this->plugins(__METHOD__, 0, func_get_args());
  111. $info = getimagesize($url);
  112. $photoName = explode(".", $filename);
  113. $newUrl = $this->uploadsThumb . $photoName[0] . '.jpeg';
  114. $newUrl2x = $this->uploadsThumb . $photoName[0] . '@2x.jpeg';
  115. # create thumbnails with Imagick
  116. if(extension_loaded('imagick')) {
  117. # Read image
  118. $thumb = new Imagick();
  119. $thumb->readImage($url);
  120. $thumb->setImageCompressionQuality($this->settings['thumbQuality']);
  121. $thumb->setImageFormat('jpeg');
  122. # Copy image for 2nd thumb version
  123. $thumb2x = clone $thumb;
  124. # Create 1st version
  125. $thumb->cropThumbnailImage($width, $height);
  126. $thumb->writeImage($newUrl);
  127. # Create 2nd version
  128. $thumb2x->cropThumbnailImage($width*2, $height*2);
  129. $thumb2x->writeImage($newUrl2x);
  130. # Close thumb
  131. $thumb->clear();
  132. $thumb->destroy();
  133. # Close thumb2
  134. $thumb2x->clear();
  135. $thumb2x->destroy();
  136. } else {
  137. # Set position and size
  138. $thumb = imagecreatetruecolor($width, $height);
  139. $thumb2x = imagecreatetruecolor($width*2, $height*2);
  140. if ($info[0]<$info[1]) {
  141. $newSize = $info[0];
  142. $startWidth = 0;
  143. $startHeight = $info[1]/2 - $info[0]/2;
  144. } else {
  145. $newSize = $info[1];
  146. $startWidth = $info[0]/2 - $info[1]/2;
  147. $startHeight = 0;
  148. }
  149. # Fallback for older version
  150. if ($info['mime']==='image/webp'&&floatval(phpversion())<5.5) return false;
  151. # Create new image
  152. switch($info['mime']) {
  153. case 'image/jpeg': $sourceImg = imagecreatefromjpeg($url); break;
  154. case 'image/png': $sourceImg = imagecreatefrompng($url); break;
  155. case 'image/gif': $sourceImg = imagecreatefromgif($url); break;
  156. case 'image/webp': $sourceImg = imagecreatefromwebp($url); break;
  157. default: return false;
  158. }
  159. imagecopyresampled($thumb, $sourceImg, 0, 0, $startWidth, $startHeight, $width, $height, $newSize, $newSize);
  160. imagecopyresampled($thumb2x, $sourceImg, 0, 0, $startWidth, $startHeight, $width*2, $height*2, $newSize, $newSize);
  161. imagejpeg($thumb, $newUrl, $this->settings['thumbQuality']);
  162. imagejpeg($thumb2x, $newUrl2x, $this->settings['thumbQuality']);
  163. }
  164. # Call plugins
  165. $this->plugins(__METHOD__, 1, func_get_args());
  166. return true;
  167. }
  168. private function adjustFile($path, $info) {
  169. if (!isset($path, $info)) return false;
  170. # Call plugins
  171. $this->plugins(__METHOD__, 0, func_get_args());
  172. if (extension_loaded('imagick')) {
  173. $rotateImage = 0;
  174. switch ($info['orientation']) {
  175. case 3:
  176. $rotateImage = 180;
  177. $imageOrientation = 1;
  178. break;
  179. case 6:
  180. $rotateImage = 90;
  181. $imageOrientation = 1;
  182. break;
  183. case 8:
  184. $rotateImage = 270;
  185. $imageOrientation = 1;
  186. break;
  187. }
  188. if ($rotateImage) {
  189. $image = new Imagick();
  190. $image->readImage($path);
  191. $image->rotateImage(new ImagickPixel(), $rotateImage);
  192. $image->setImageOrientation($imageOrientation);
  193. $image->writeImage($path);
  194. $image->clear();
  195. $image->destroy();
  196. }
  197. } else {
  198. $newWidth = $info['width'];
  199. $newHeight = $info['height'];
  200. $sourceImg = imagecreatefromjpeg($path);
  201. switch ($info['orientation']) {
  202. case 2:
  203. # mirror
  204. # not yet implemented
  205. break;
  206. case 3:
  207. $sourceImg = imagerotate($sourceImg, -180, 0);
  208. break;
  209. case 4:
  210. # rotate 180 and mirror
  211. # not yet implemented
  212. break;
  213. case 5:
  214. # rotate 90 and mirror
  215. # not yet implemented
  216. break;
  217. case 6:
  218. $sourceImg = imagerotate($sourceImg, -90, 0);
  219. $newWidth = $info['height'];
  220. $newHeight = $info['width'];
  221. break;
  222. case 7:
  223. # rotate -90 and mirror
  224. # not yet implemented
  225. break;
  226. case 8:
  227. $sourceImg = imagerotate($sourceImg, 90, 0);
  228. $newWidth = $info['height'];
  229. $newHeight = $info['width'];
  230. break;
  231. }
  232. $newSourceImg = imagecreatetruecolor($newWidth, $newHeight);
  233. imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight);
  234. imagejpeg($newSourceImg, $path, 100);
  235. }
  236. # Call plugins
  237. $this->plugins(__METHOD__, 1, func_get_args());
  238. return true;
  239. }
  240. public function get($albumID) {
  241. if (!isset($this->database, $this->photoIDs)) return false;
  242. # Call plugins
  243. $this->plugins(__METHOD__, 0, func_get_args());
  244. # Get photo
  245. $photos = $this->database->query("SELECT * FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
  246. $photo = $photos->fetch_assoc();
  247. # Parse photo
  248. $photo['sysdate'] = date('d M. Y', substr($photo['id'], 0, -4));
  249. if (strlen($photo['takestamp'])>0) $photo['takedate'] = date('d M. Y', $photo['takestamp']);
  250. if ($albumID!='false') {
  251. if ($photo['album']!=0) {
  252. # Get album
  253. $albums = $this->database->query("SELECT public FROM lychee_albums WHERE id = '" . $photo['album'] . " LIMIT 1';");
  254. $album = $albums->fetch_assoc();
  255. # Parse album
  256. $photo['public'] = ($album['public']=='1' ? '2' : $photo['public']);
  257. }
  258. $photo['original_album'] = $photo['album'];
  259. $photo['album'] = $albumID;
  260. }
  261. # Call plugins
  262. $this->plugins(__METHOD__, 1, func_get_args());
  263. return $photo;
  264. }
  265. private function getInfo($url) {
  266. if (!isset($this->database, $url)) return false;
  267. # Call plugins
  268. $this->plugins(__METHOD__, 0, func_get_args());
  269. $iptcArray = array();
  270. $info = getimagesize($url, $iptcArray);
  271. # General information
  272. $return['type'] = $info['mime'];
  273. $return['width'] = $info[0];
  274. $return['height'] = $info[1];
  275. # Size
  276. $size = filesize($url)/1024;
  277. if ($size>=1024) $return['size'] = round($size/1024, 1) . ' MB';
  278. else $return['size'] = round($size, 1) . ' KB';
  279. # IPTC Metadata Fallback
  280. $return['title'] = '';
  281. $return['description'] = '';
  282. # IPTC Metadata
  283. if(isset($iptcArray['APP13'])) {
  284. $iptcInfo = iptcparse($iptcArray['APP13']);
  285. if (is_array($iptcInfo)) {
  286. $temp = @$iptcInfo['2#105'][0];
  287. if (isset($temp)&&strlen($temp)>0) $return['title'] = $temp;
  288. $temp = @$iptcInfo['2#120'][0];
  289. if (isset($temp)&&strlen($temp)>0) $return['description'] = $temp;
  290. }
  291. }
  292. # EXIF Metadata Fallback
  293. $return['orientation'] = '';
  294. $return['iso'] = '';
  295. $return['aperture'] = '';
  296. $return['make'] = '';
  297. $return['model'] = '';
  298. $return['shutter'] = '';
  299. $return['focal'] = '';
  300. $return['takestamp'] = '';
  301. # Read EXIF
  302. if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0);
  303. else $exif = false;
  304. # EXIF Metadata
  305. if ($exif!==false) {
  306. if (isset($exif['Orientation'])) $return['orientation'] = $exif['Orientation'];
  307. else if (isset($exif['IFD0']['Orientation'])) $return['orientation'] = $exif['IFD0']['Orientation'];
  308. $temp = @$exif['ISOSpeedRatings'];
  309. if (isset($temp)) $return['iso'] = $temp;
  310. $temp = @$exif['COMPUTED']['ApertureFNumber'];
  311. if (isset($temp)) $return['aperture'] = $temp;
  312. $temp = @$exif['Make'];
  313. if (isset($temp)) $return['make'] = $exif['Make'];
  314. $temp = @$exif['Model'];
  315. if (isset($temp)) $return['model'] = $temp;
  316. $temp = @$exif['ExposureTime'];
  317. if (isset($temp)) $return['shutter'] = $exif['ExposureTime'] . ' Sec.';
  318. $temp = @$exif['FocalLength'];
  319. if (isset($temp)) $return['focal'] = ($temp/1) . ' mm';
  320. $temp = @$exif['DateTimeOriginal'];
  321. if (isset($temp)) $return['takestamp'] = strtotime($temp);
  322. }
  323. # Security
  324. foreach(array_keys($return) as $key) $return[$key] = mysqli_real_escape_string($this->database, $return[$key]);
  325. # Call plugins
  326. $this->plugins(__METHOD__, 1, func_get_args());
  327. return $return;
  328. }
  329. public function getArchive() {
  330. if (!isset($this->database, $this->photoIDs)) return false;
  331. # Call plugins
  332. $this->plugins(__METHOD__, 0, func_get_args());
  333. # Get photo
  334. $photos = $this->database->query("SELECT title, url FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
  335. $photo = $photos->fetch_object();
  336. # Get extension
  337. $extension = array_reverse(explode('.', $photo->url));
  338. # Parse title
  339. if ($photo->title=='') $photo->title = 'Untitled';
  340. # Set headers
  341. header("Content-Type: application/octet-stream");
  342. header("Content-Disposition: attachment; filename=\"$photo->title.$extension[0]\"");
  343. header("Content-Length: " . filesize($this->uploadsBig . $photo->url));
  344. # Send file
  345. readfile($this->uploadsBig . $photo->url);
  346. # Call plugins
  347. $this->plugins(__METHOD__, 1, func_get_args());
  348. return true;
  349. }
  350. function setTitle($title) {
  351. if (!isset($this->database, $this->photoIDs)) return false;
  352. # Call plugins
  353. $this->plugins(__METHOD__, 0, func_get_args());
  354. # Parse
  355. if (strlen($title)>50) $title = substr($title, 0, 50);
  356. # Set title
  357. $result = $this->database->query("UPDATE lychee_photos SET title = '$title' WHERE id IN ($this->photoIDs);");
  358. # Call plugins
  359. $this->plugins(__METHOD__, 1, func_get_args());
  360. if (!$result) return false;
  361. return true;
  362. }
  363. function setDescription($description) {
  364. if (!isset($this->database, $this->photoIDs)) return false;
  365. # Call plugins
  366. $this->plugins(__METHOD__, 0, func_get_args());
  367. # Parse
  368. $description = htmlentities($description);
  369. if (strlen($description)>1000) $description = substr($description, 0, 1000);
  370. # Set description
  371. $result = $this->database->query("UPDATE lychee_photos SET description = '$description' WHERE id IN ('$this->photoIDs');");
  372. # Call plugins
  373. $this->plugins(__METHOD__, 1, func_get_args());
  374. if (!$result) return false;
  375. return true;
  376. }
  377. public function setStar() {
  378. if (!isset($this->database, $this->photoIDs)) return false;
  379. # Call plugins
  380. $this->plugins(__METHOD__, 0, func_get_args());
  381. # Init vars
  382. $error = false;
  383. # Get photos
  384. $photos = $this->database->query("SELECT id, star FROM lychee_photos WHERE id IN ($this->photoIDs);");
  385. # For each photo
  386. while ($photo = $photos->fetch_object()) {
  387. # Invert star
  388. $star = ($photo->star==0 ? 1 : 0);
  389. # Set star
  390. $star = $this->database->query("UPDATE lychee_photos SET star = '$star' WHERE id = '$photo->id';");
  391. if (!$star) $error = true;
  392. }
  393. # Call plugins
  394. $this->plugins(__METHOD__, 1, func_get_args());
  395. if ($error) return false;
  396. return true;
  397. }
  398. function getPublic($password) {
  399. if (!isset($this->database, $this->photoIDs)) return false;
  400. # Call plugins
  401. $this->plugins(__METHOD__, 0, func_get_args());
  402. # Get photo
  403. $photos = $this->database->query("SELECT public, album FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
  404. $photo = $photos->fetch_object();
  405. # Check if public
  406. if ($photo->public==1) return true;
  407. else {
  408. $album = new Album($this->database, null, null, $photo->album);
  409. $acP = $album->checkPassword($password);
  410. $agP = $album->getPublic();
  411. if ($acP===true&&$agP===true) return true;
  412. }
  413. # Call plugins
  414. $this->plugins(__METHOD__, 1, func_get_args());
  415. return false;
  416. }
  417. public function setPublic() {
  418. if (!isset($this->database, $this->photoIDs)) return false;
  419. # Call plugins
  420. $this->plugins(__METHOD__, 0, func_get_args());
  421. # Get public
  422. $photos = $this->database->query("SELECT public FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
  423. $photo = $photos->fetch_object();
  424. # Invert public
  425. $public = ($photo->public==0 ? 1 : 0);
  426. # Set public
  427. $result = $this->database->query("UPDATE lychee_photos SET public = '$public' WHERE id = '$this->photoIDs';");
  428. # Call plugins
  429. $this->plugins(__METHOD__, 1, func_get_args());
  430. if (!$result) return false;
  431. return true;
  432. }
  433. function setAlbum($albumID) {
  434. if (!isset($this->database, $this->photoIDs)) return false;
  435. # Call plugins
  436. $this->plugins(__METHOD__, 0, func_get_args());
  437. # Set album
  438. $result = $this->database->query("UPDATE lychee_photos SET album = '$albumID' WHERE id IN ($this->photoIDs);");
  439. # Call plugins
  440. $this->plugins(__METHOD__, 1, func_get_args());
  441. if (!$result) return false;
  442. return true;
  443. }
  444. public function setTags($tags) {
  445. if (!isset($this->database, $this->photoIDs)) return false;
  446. # Call plugins
  447. $this->plugins(__METHOD__, 0, func_get_args());
  448. # Parse tags
  449. $tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags);
  450. $tags = preg_replace('/,$|^,/', ',', $tags);
  451. if (strlen($tags)>1000) return false;
  452. # Set tags
  453. $result = $this->database->query("UPDATE lychee_photos SET tags = '$tags' WHERE id IN ($this->photoIDs);");
  454. # Call plugins
  455. $this->plugins(__METHOD__, 1, func_get_args());
  456. if (!$result) return false;
  457. return true;
  458. }
  459. public function delete() {
  460. if (!isset($this->database, $this->photoIDs)) return false;
  461. # Call plugins
  462. $this->plugins(__METHOD__, 0, func_get_args());
  463. # Get photos
  464. $photos = $this->database->query("SELECT id, url, thumbUrl FROM lychee_photos WHERE id IN ($this->photoIDs);");
  465. # For each photo
  466. while ($photo = $photos->fetch_object()) {
  467. # Get retina thumb url
  468. $thumbUrl2x = explode(".", $photo->thumbUrl);
  469. $thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1];
  470. # Delete files
  471. if (file_exists($this->uploadsBig . $photo->url)&&!unlink($this->uploadsBig . $photo->url)) return false;
  472. if (file_exists($this->uploadsThumb . $photo->thumbUrl)&&!unlink($this->uploadsThumb . $photo->thumbUrl)) return false;
  473. if (file_exists($this->uploadsThumb . $thumbUrl2x)&&!unlink($this->uploadsThumb . $thumbUrl2x)) return false;
  474. # Delete db entry
  475. $delete = $this->database->query("DELETE FROM lychee_photos WHERE id = '$photo->id';");
  476. if (!$delete) return false;
  477. }
  478. # Call plugins
  479. $this->plugins(__METHOD__, 1, func_get_args());
  480. if (!$photos) return false;
  481. return true;
  482. }
  483. }
  484. ?>