PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/phob.class.php

http://phob.googlecode.com/
PHP | 540 lines | 441 code | 41 blank | 58 comment | 9 complexity | c1ec71654cc0541ed64523a784dc6306 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * PHOB - photo browser
  4. *
  5. * @author Jan Skrasek <hrach.cz@gmail.com>
  6. * @copyright Copyright (c) 2008 - 2009, Jan Skrasek
  7. * @version 0.8 $Id: phob.class.php 42 2010-01-16 19:01:17Z skrasek.jan@gmail.com $
  8. * @link http://phob.skrasek.com
  9. * @package Phob
  10. */
  11. class Phob
  12. {
  13. /**
  14. * Returns exp from exif string
  15. * @param string $value
  16. * @return float
  17. */
  18. public static function getExifExp($value)
  19. {
  20. $pos = strpos($value, '/');
  21. if ($pos === false)
  22. return (float) $value;
  23. $a = (float) substr($value, 0, $pos);
  24. $b = (float) substr($value, $pos + 1);
  25. return ($a == 0) ? ($a) : ($b / $a);
  26. }
  27. /**
  28. * Returns float from exif string
  29. * @param string value
  30. * @return float
  31. */
  32. public static function getExifFloat($value)
  33. {
  34. $pos = strpos($value, '/');
  35. if ($pos === false)
  36. return (float) $value;
  37. $a = (float) substr($value, 0, $pos);
  38. $b = (float) substr($value, $pos + 1);
  39. return ($b == 0) ? ($a) : ($a / $b);
  40. }
  41. /**
  42. * Removes utf BOM
  43. * @param string $str
  44. * @return string
  45. */
  46. public static function removeBOM($str)
  47. {
  48. if (substr($str, 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf))
  49. return substr($str, 3);
  50. return $str;
  51. }
  52. /** @var array */
  53. public $lang = array(
  54. 'dirup' => 'Nahoru [..]',
  55. 'root_dir' => 'Ko?enový adresá?',
  56. 'no_photo' => 'Fotografie neexistuje!',
  57. 'exif_Model' => 'Fotoaparát',
  58. 'exif_ExposureTime' => 'Expozice',
  59. 'exif_FNumber' => 'Clona',
  60. 'exif_ISOSpeedRatings' => 'Citlivost',
  61. 'exif_FocalLength' => 'Ohnisková vzdálenost',
  62. 'exif_DateTime' => 'Datum',
  63. );
  64. /** @var string */
  65. public $skins;
  66. /** @var string */
  67. public $photos;
  68. /** @var string */
  69. public $thumbs;
  70. /** @var bool */
  71. public $renderCommentsFile = false;
  72. /** @var bool */
  73. public $updateCommentsFile = false;
  74. /** @var array */
  75. public $config = array(
  76. 'siteName' => 'PhotoBrowser',
  77. 'siteSkin' => 'default',
  78. 'showDirup' => true,
  79. 'showExif' => true,
  80. );
  81. /** @var string */
  82. protected $root;
  83. /** @var array */
  84. protected $router = array(
  85. 'action' => 'list',
  86. 'path' => '',
  87. 'name' => ''
  88. );
  89. /** @var array */
  90. protected $items = array();
  91. /**
  92. * Constructor
  93. * @return void
  94. */
  95. public function __construct()
  96. {
  97. $root = trim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
  98. if (empty($root))
  99. $this->root = '/';
  100. else
  101. $this->root = "/$root/";
  102. }
  103. /**
  104. * Returns gallery output
  105. * @return string
  106. */
  107. public function render()
  108. {
  109. $this->photos = trim($this->photos, '/');
  110. $action = $this->route();
  111. if ($action == 'preview') {
  112. $this->preview();
  113. } else {
  114. $this->scan();
  115. if ($action == 'list')
  116. return $this->index();
  117. else
  118. return $this->view();
  119. }
  120. }
  121. /**
  122. * Routes applicaiton
  123. * @return string action
  124. */
  125. private function route()
  126. {
  127. $key = 'PATH_INFO';
  128. if (!isset($_SERVER['PATH_INFO']) && isset($_SERVER['ORIG_PATH_INFO']))
  129. $key = 'ORIG_PATH_INFO';
  130. $url = (!empty($_SERVER[$key]) && $_SERVER[$key] != '/') ? $_SERVER[$key] : 'list';
  131. $url = explode('/', urldecode(trim($url, '/')));
  132. if (in_array($url[0], array('view', 'preview', 'list')))
  133. $this->router['action'] = array_shift($url);
  134. else
  135. return $this->error('Directory doesn\'t exists.');
  136. if (in_array($this->router['action'], array('view', 'preview')))
  137. $this->router['name'] = array_pop($url);
  138. $this->router['path'] = $url;
  139. $this->router['path_full'] = implode('/', $url);
  140. return $this->router['action'];
  141. }
  142. /**
  143. * Scans direcotry
  144. * @return void
  145. */
  146. private function scan()
  147. {
  148. $scan = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->photos}/{$this->router['path_full']}";
  149. if (!is_dir($scan))
  150. return $this->items = false;
  151. $dirs = array();
  152. $photos = array();
  153. $folder = new DirectoryIterator($scan);
  154. $alias = array();
  155. if ($this->router['action'] == 'list' && is_file($scan . '/alias.txt'))
  156. $alias = $this->readData($scan . '/alias.txt');
  157. foreach ($folder as $file) {
  158. $name = $file->getFileName();
  159. if ($name == '.' || ($name === '..' && (empty($this->router['path']) || !$this->config['showDirup'])))
  160. continue;
  161. if ($file->isDir()) {
  162. if ($name == '..') {
  163. $upPath = $this->router['path'];
  164. array_pop($upPath);
  165. $upPath = implode('/', $upPath);
  166. $dirs[$name] = array(
  167. 'type' => 'dir',
  168. 'name' => $this->__('dirup'),
  169. 'path' => "{$this->root}list/$upPath"
  170. );
  171. } else {
  172. $dirs[$name] = array(
  173. 'type' => 'dir',
  174. 'name' => !empty($alias[$name]) ? $alias[$name] : $name,
  175. 'path' => "{$this->root}list/" . trim("{$this->router['path_full']}/$name", '/')
  176. );
  177. }
  178. } elseif (preg_match("#\.jpe?g$#i", $name)) {
  179. $photos[$name] = array(
  180. 'type' => 'photo',
  181. 'name' => $name,
  182. 'path' => "{$this->root}view/" . trim("{$this->router['path_full']}/$name", '/'),
  183. 'thumb' => "{$this->root}preview/" . trim("{$this->router['path_full']}/$name", '/')
  184. );
  185. }
  186. }
  187. ksort($dirs);
  188. ksort($photos);
  189. if (isset($this->config['reverseOrder']) && $this->config['reverseOrder'] === true) {
  190. $dirs = array_reverse($dirs);
  191. $photos = array_reverse($photos);
  192. }
  193. $this->items = array_merge($dirs, $photos);
  194. if ($this->renderCommentsFile) {
  195. $file = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->photos}/{$this->router['path_full']}/comments.txt";
  196. if (file_exists($file)) {
  197. if ($this->updateCommentsFile) {
  198. $content = array();
  199. $data = $this->readData($file);
  200. foreach ($photos as $photo) {
  201. if (isset($data[$photo['name']]))
  202. $content[] = $photo['name'] . ': ' . $data[$photo['name']];
  203. else
  204. $content[] = $photo['name'] . ': ';
  205. }
  206. file_put_contents($file, implode("\n", $content));
  207. }
  208. } else {
  209. $content = array();
  210. foreach ($photos as $photo)
  211. $content[] = $photo['name'] . ':';
  212. file_put_contents($file, implode("\n", $content));
  213. }
  214. }
  215. }
  216. /**
  217. * Renders list template
  218. * @return string
  219. */
  220. private function index()
  221. {
  222. $this->setTree();
  223. if ($this->items !== false)
  224. $this->set('photos', $this->items);
  225. else
  226. return $this->error('Directory doesn\'t exists.');
  227. return $this->renderTemplate('list');
  228. }
  229. /**
  230. * Renders photo template
  231. * @return string
  232. */
  233. private function view()
  234. {
  235. $this->setTree();
  236. $image = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->photos}/{$this->router['path_full']}/{$this->router['name']}";
  237. if (!file_exists($image))
  238. return $this->error('Photo doesn\'t exists.');
  239. $photoUrl = "{$this->root}{$this->photos}/" . trim("{$this->router['path_full']}/{$this->router['name']}", '/');
  240. $this->set('photoUrl', $photoUrl);
  241. # label
  242. $comment = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->photos}/{$this->router['path_full']}/comments.txt";
  243. if (file_exists($comment))
  244. $data = $this->readData($comment);
  245. else
  246. $data = array();
  247. $this->set('data', $data);
  248. if (isset($data[$this->router['name']]))
  249. $this->set('label', $data[$this->router['name']]);
  250. # exif
  251. if (isset($this->config['showExif']) && $this->config['showExif'] && function_exists('exif_read_data')) {
  252. $exif = array();
  253. $data = exif_read_data($image);
  254. if (!empty($data['Model'])) {
  255. if (!empty($data['Manufacturer']))
  256. $exif[$this->__('exif_Model')] = $data['Manufacturer'] . ' ' . $data['Model'];
  257. else
  258. $exif[$this->__('exif_Model')] = (!empty($data['Make']) ? $data['Make'] . ' ' : '') . $data['Model'];
  259. }
  260. if (!empty($data['ExposureTime']))
  261. $exif[$this->__('exif_ExposureTime')] = '1/' . self::getExifExp($data['ExposureTime']) . ' s';
  262. if (!empty($data['FNumber']))
  263. $exif[$this->__('exif_FNumber')] = 'F ' . self::getExifFloat($data['FNumber']);
  264. if (!empty($data['FocalLength']))
  265. $exif[$this->__('exif_FocalLength')] = self::getExifFloat($data['FocalLength']) . ' mm';
  266. if (!empty($data['ISOSpeedRatings']))
  267. $exif[$this->__('exif_ISOSpeedRatings')] = $data['ISOSpeedRatings'] . ' ISO';
  268. if (!empty($data['DateTime']))
  269. $exif[$this->__('exif_DateTime')] = preg_replace('#(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})#', '$3.$2.$1 $4:$5:$6', $data['DateTime']);
  270. $this->set('exif', $exif);
  271. }
  272. $this->set('next', $this->getPhoto());
  273. $this->set('prev', $this->getPhoto(false));
  274. return $this->renderTemplate('view');
  275. }
  276. /**
  277. * Sends redirect header
  278. * @return string
  279. */
  280. private function error($var)
  281. {
  282. header('HTTP/1.1 404 Not Found');
  283. die("<h1>Error: Page wasn't found</h1>$var");
  284. exit;
  285. }
  286. /**
  287. * Returs next/previou photo
  288. * @param bool next
  289. * @return mixed
  290. */
  291. private function getPhoto($next = true)
  292. {
  293. $item = null;
  294. reset($this->items);
  295. while ($this->router['name'] != key($this->items))
  296. next($this->items);
  297. if ($next) {
  298. do {
  299. $item = next($this->items);
  300. } while ($item['type'] == 'dir' && is_array($item));
  301. } else {
  302. do {
  303. $item = prev($this->items);
  304. } while ($item['type'] == 'dir' && is_array($item));
  305. }
  306. if (is_array($item))
  307. return $item;
  308. else
  309. return false;
  310. }
  311. /**
  312. * Saves dir tree for template
  313. * @return void
  314. */
  315. private function setTree()
  316. {
  317. $dirTree = array(array(
  318. 'name' => $this->__('root_dir'),
  319. 'path' => "{$this->root}list"
  320. ));
  321. $path = '';
  322. foreach ($this->router['path'] as $dir) {
  323. $alias = array();
  324. $alias_file = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->photos}/$path/alias.txt";
  325. if (is_file($alias_file))
  326. $alias = $this->readData($alias_file);
  327. $path .= "$dir/";
  328. $dirTree[] = array(
  329. 'name' => !empty($alias[$dir]) ? $alias[$dir] : $dir,
  330. 'path' => "{$this->root}list/$path"
  331. );
  332. }
  333. $this->set('dirTree', $dirTree);
  334. }
  335. /**
  336. * Renders photo thumbnail
  337. * @return void
  338. */
  339. private function preview()
  340. {
  341. $thumb = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->thumbs}/" . md5($this->router['path_full'] . $this->router['name']) . ".jpeg";
  342. if (file_exists($thumb)) {
  343. header('Content-type: image/jpeg');
  344. readfile($thumb);
  345. exit;
  346. }
  347. $img = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->photos}/{$this->router['path_full']}/{$this->router['name']}";
  348. if (!file_exists($img))
  349. die($this->__('no_photo'));
  350. $thumbnail = false;
  351. if (function_exists('exif_thumbnail')) {
  352. $thumbnail = exif_thumbnail($img);
  353. if ($thumbnail !== false) {
  354. file_put_contents($thumb, $thumbnail);
  355. header('Content-type: image/jpeg');
  356. echo $thumbnail;
  357. exit;
  358. }
  359. }
  360. if (class_exists('Imagick')) {
  361. $im = new Imagick($img);
  362. $thumbnail = $im->clone();
  363. $thumbnail->thumbnailImage(160, 120, true);
  364. $thumbnail->writeImage($thumb);
  365. } elseif (function_exists('imagecreatefromjpeg')) {
  366. $old = imagecreatefromjpeg($img);
  367. $old_x = imagesx($old);
  368. $old_y = imagesy($old);
  369. if ($old_y > $old_x) {
  370. $k = $old_y / 120;
  371. $new_y = 120;
  372. $new_x = floor($old_x / $k);
  373. } else {
  374. $k = $old_x / 160;
  375. $new_x = 160;
  376. $new_y = floor($old_y / $k);
  377. }
  378. $nahled = imagecreatetruecolor($new_x, $new_y);
  379. imagecopyresized($nahled, $old, 0, 0, 0, 0, $new_x, $new_y, $old_x, $old_y);
  380. imagejpeg($nahled, $thumb);
  381. imagedestroy($nahled);
  382. } else {
  383. die('There is no required library for thumbnail generation. (GD, Imagick, Exif)');
  384. }
  385. header('Content-type: image/jpeg');
  386. readfile($thumb);
  387. exit;
  388. }
  389. /**
  390. * Parses configs files
  391. * @param string file path
  392. * @return array
  393. */
  394. public static function readData($file)
  395. {
  396. $array = array();
  397. $data = self::removeBOM(file_get_contents($file));
  398. foreach (explode("\n", $data) as $line) {
  399. if (preg_match('#^(.+)(?::\s|\t)(.+)$#U', $line, $match))
  400. $array[$match[1]] = $match[2];
  401. }
  402. return $array;
  403. }
  404. /**
  405. * Saves param for template
  406. * @param string var name
  407. * @param mixed value
  408. * @return void
  409. */
  410. private function set($var, $val)
  411. {
  412. $this->vars[$var] = $val;
  413. }
  414. /**
  415. * Renders template
  416. * @param string template name
  417. * @return string
  418. */
  419. private function renderTemplate($name)
  420. {
  421. $this->set('siteName', $this->config['siteName']);
  422. $this->set('css', "{$this->root}{$this->skins}/{$this->config['skinName']}/");
  423. extract($this->vars);
  424. $template = dirname($_SERVER['SCRIPT_FILENAME']) . "/{$this->skins}/{$this->config['skinName']}/$name.phtml";
  425. if (file_exists($template))
  426. require $template;
  427. else
  428. echo "Missing template '$template'!";
  429. return ob_get_clean();
  430. }
  431. /**
  432. * Translate key
  433. * @param string key
  434. * @return string
  435. */
  436. private function __($key)
  437. {
  438. if (!empty($this->lang[$key]))
  439. return $this->lang[$key];
  440. else
  441. return $key;
  442. }
  443. }