PageRenderTime 27ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/core/components/phpthumbof/model/phpthumbof/phpthumbof.class.php

http://github.com/splittingred/phpThumbOf
PHP | 508 lines | 341 code | 42 blank | 125 comment | 69 complexity | dd6b816ff1d44d392e1fecaa1dec4520 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * phpThumbOf
  4. *
  5. * Copyright 2009-2012 by Shaun McCormick <shaun@modx.com>
  6. *
  7. * phpThumbOf is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the Free
  9. * Software Foundation; either version 2 of the License, or (at your option) any
  10. * later version.
  11. *
  12. * phpThumbOf is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * phpThumbOf; if not, write to the Free Software Foundation, Inc., 59 Temple
  18. * Place, Suite 330, Boston, MA 02111-1307 USA
  19. *
  20. * @package phpthumbof
  21. */
  22. /**
  23. * @package phpThumbOf
  24. */
  25. class phpThumbOf {
  26. /** @var modX $modx */
  27. public $modx;
  28. /** @var modAws $aws */
  29. public $aws;
  30. /** @var modPhpThumb $phpThumb */
  31. public $phpThumb;
  32. /** @var array $config */
  33. public $config = array();
  34. /** @var int $debugTimeStart */
  35. public $debugTimeStart = 0;
  36. /** @var string $oldLogTarget */
  37. public $oldLogTarget = 'FILE';
  38. /** @var int $oldLogLevel */
  39. public $oldLogLevel = 1;
  40. function __construct(modX $modx,array $config = array()) {
  41. $this->modx =& $modx;
  42. $corePath = $this->modx->getOption('phpthumbof.core_path',$this->config,$this->modx->getOption('core_path').'components/phpthumbof/');
  43. $assetsPath = $this->modx->getOption('phpthumbof.assets_path',$this->config,$this->modx->getOption('assets_path').'components/phpthumbof/');
  44. $assetsUrl = $this->modx->getOption('phpthumbof.assets_url',$this->config,$this->modx->getOption('assets_url').'components/phpthumbof/');
  45. $this->config = array_merge(array(
  46. 'debug' => false,
  47. 'options' => false,
  48. 'corePath' => $corePath,
  49. 'modelPath' => $corePath.'model/',
  50. 'assetsPath' => $assetsPath,
  51. 'assetsUrl' => $assetsUrl,
  52. 'cachePath' => $modx->context->getOption('phpthumbof.cache_path','',$this->config),
  53. 'cachePathUrl' => $modx->context->getOption('phpthumbof.cache_url',$assetsUrl.'cache/',$this->config),
  54. 'checkRemotelyIfNotFound' => $modx->context->getOption('phpthumbof.check_remotely_if_not_found',false,$this->config),
  55. ),$config);
  56. if (empty($this->config['cachePathUrl'])) $this->config['cachePathUrl'] = $assetsUrl.'cache/';
  57. }
  58. /**
  59. * Get the parsed cachePath
  60. * @return mixed
  61. */
  62. public function getCacheDirectory() {
  63. if (empty($this->config['cachePath'])) {
  64. $this->config['cachePath'] = $this->config['assetsPath'].'cache/';
  65. } else {
  66. $this->config['cachePath'] = str_replace(array(
  67. '[[+core_path]]',
  68. '[[+assets_path]]',
  69. '[[+base_path]]',
  70. '[[+manager_path]]',
  71. ),array(
  72. $this->modx->getOption('core_path',null,MODX_CORE_PATH),
  73. $this->modx->getOption('assets_path',null,MODX_ASSETS_PATH),
  74. $this->modx->getOption('base_path',null,MODX_BASE_PATH),
  75. $this->modx->getOption('manager_path',null,MODX_MANAGER_PATH),
  76. ),$this->config['cachePath']);
  77. }
  78. return $this->config['cachePath'];
  79. }
  80. /**
  81. * Check to make sure cache path is writable
  82. * @return boolean
  83. */
  84. public function ensureCacheDirectoryIsWritable() {
  85. $writable = true;
  86. if (!is_writable($this->config['cachePath'])) {
  87. if (!$this->modx->cacheManager->writeTree($this->config['cachePath'])) {
  88. $this->modx->log(modX::LOG_LEVEL_ERROR,'[phpThumbOf] Cache path not writable: '.$this->config['cachePath']);
  89. $writable = false;
  90. }
  91. }
  92. return $writable;
  93. }
  94. /**
  95. * Create a Thumbnail object for this source
  96. *
  97. * @return ptThumbnail
  98. */
  99. public function createThumbnailObject() {
  100. return new ptThumbnail($this,$this->config);
  101. }
  102. /**
  103. * Start the debug trail
  104. */
  105. public function startDebug() {
  106. if ($this->modx->getOption('debug',$this->config,false)) {
  107. $mtime = microtime();
  108. $mtime = explode(" ", $mtime);
  109. $mtime = $mtime[1] + $mtime[0];
  110. $this->debugTimeStart = $mtime;
  111. set_time_limit(0);
  112. $this->oldLogTarget = $this->modx->getLogTarget();
  113. $this->oldLogLevel = $this->modx->getLogLevel();
  114. $this->modx->setLogLevel(modX::LOG_LEVEL_DEBUG);
  115. $logTarget = $this->modx->getOption('debugTarget',$this->config,'');
  116. if (!empty($logTarget)) {
  117. $this->modx->setLogTarget();
  118. }
  119. }
  120. }
  121. /**
  122. * End the debug trail
  123. */
  124. public function endDebug() {
  125. if ($this->modx->getOption('debug',$this->config,false)) {
  126. $mtime= microtime();
  127. $mtime= explode(" ", $mtime);
  128. $mtime= $mtime[1] + $mtime[0];
  129. $tend= $mtime;
  130. $totalTime= ($tend - $this->debugTimeStart);
  131. $totalTime= sprintf("%2.4f s", $totalTime);
  132. $this->modx->log(modX::LOG_LEVEL_DEBUG,"\n<br />Execution time: {$totalTime}\n<br />");
  133. $this->modx->setLogLevel($this->oldLogLevel);
  134. $this->modx->setLogTarget($this->oldLogTarget);
  135. }
  136. }
  137. }
  138. /**
  139. * Thumbnail class for generating a thumbnail from an input source
  140. *
  141. * @package phpthumbof
  142. */
  143. class ptThumbnail {
  144. /** @var modX $modx */
  145. public $modx;
  146. /** @var phpThumbOf $phpThumbOf */
  147. public $phpThumbOf;
  148. /** @var modPhpThumb $phpThumb */
  149. public $phpThumb;
  150. /** @var modAws $aws */
  151. public $aws;
  152. /** @var array $config */
  153. public $config = array();
  154. /** @var string $input The file to make a thumbnail from */
  155. public $input = '';
  156. /** @var array $options */
  157. public $options = array();
  158. public $cacheKey = '';
  159. public $cacheUrl = '';
  160. public $cacheFilename = '';
  161. public $expired = false;
  162. function __construct(phpThumbOf $phpThumbOf,array $config = array()) {
  163. $this->phpThumbOf =& $phpThumbOf;
  164. $this->modx =& $phpThumbOf->modx;
  165. $this->config = array_merge(array(
  166. 'cache' => true,
  167. 'useS3' => $this->modx->context->getOption('phpthumbof.use_s3',false,$this->config),
  168. 's3path' => $this->modx->context->getOption('phpthumbof.s3_path','phpthumbof/',$this->config),
  169. 's3bucket' => $this->modx->context->getOption('phpthumbof.s3_bucket','',$this->config),
  170. 's3hostAlias' => $this->modx->context->getOption('phpthumbof.s3_host_alias','',$this->config),
  171. 's3headersCheck' => $this->modx->context->getOption('phpthumbof.s3_headers_check',false,$this->config),
  172. ),$config);
  173. $this->config = array_merge(array(
  174. 's3hostDefault' => $this->config['s3bucket'].'.s3.amazonaws.com/',
  175. ),$this->config);
  176. $this->phpThumb = new modPhpThumb($this->modx);
  177. if (!empty($this->config['useS3'])) {
  178. $this->aws = $this->modx->getService('modaws','modAws',$this->config['modelPath'].'aws/',$this->config);
  179. }
  180. }
  181. /**
  182. * Startup the phpThumb service. Must run setInput and setOptions first.
  183. */
  184. public function initializeService() {
  185. $this->phpThumb->config = array_merge($this->phpThumb->config,$this->options);
  186. $this->phpThumb->initialize();
  187. $this->phpThumb->setParameter('config_cache_directory',$this->config['cachePath']);
  188. $this->phpThumb->setParameter('config_allow_src_above_phpthumb',true);
  189. $this->phpThumb->setParameter('allow_local_http_src',true);
  190. $this->phpThumb->setParameter('config_document_root',$this->modx->context->getOption('base_path',MODX_BASE_PATH,$this->config));
  191. $this->phpThumb->setCacheDirectory();
  192. $this->phpThumb->set($this->input);
  193. }
  194. /**
  195. * Set the input source
  196. * @param string $input
  197. * @return string
  198. */
  199. public function setInput($input) {
  200. /* get absolute url of image */
  201. if (strpos($input,'/') != 0 && strpos($input,'http') != 0) {
  202. $input = $this->modx->context->getOption('base_url').$input;
  203. } else {
  204. $input = urldecode($input);
  205. }
  206. $hasQuery = strpos($input,'?');
  207. if ($hasQuery !== false) {
  208. $this->options['queryString'] = substr($input,$hasQuery+1);
  209. $input = substr($input,0,$hasQuery);
  210. }
  211. if (!file_exists($input) && !empty($this->config['checkRemotelyIfNotFound'])) {
  212. $input = $this->modx->context->getOption('url_scheme',MODX_URL_SCHEME).$this->modx->context->getOption('http_host',MODX_HTTP_HOST).urlencode($input);
  213. }
  214. $this->input = $input;
  215. return $this->input;
  216. }
  217. /**
  218. * Set the options for the thumbnail
  219. * @param array|string $options
  220. */
  221. public function setOptions($options) {
  222. /* explode tag options */
  223. $ptOptions = array();
  224. $eoptions = is_array($options) ? $options : explode('&',$options);
  225. foreach ($eoptions as $opt) {
  226. $opt = explode('=',$opt);
  227. $key = str_replace('[]','',$opt[0]);
  228. if (!empty($key)) {
  229. /* allow arrays of options */
  230. if (isset($ptOptions[$key])) {
  231. if (is_string($ptOptions[$key])) {
  232. $ptOptions[$key] = array($ptOptions[$key]);
  233. }
  234. $ptOptions[$key][] = $opt[1];
  235. } else { /* otherwise pass in as string */
  236. $ptOptions[$key] = $opt[1];
  237. }
  238. }
  239. }
  240. if (empty($ptOptions['f'])){
  241. $ext = pathinfo($this->input, PATHINFO_EXTENSION);
  242. $ext = strtolower($ext);
  243. switch ($ext) {
  244. case 'jpg':
  245. case 'jpeg':
  246. case 'png':
  247. case 'gif':
  248. case 'bmp':
  249. $ptOptions['f'] = $ext;
  250. break;
  251. default:
  252. $ptOptions['f'] = 'jpeg';
  253. break;
  254. }
  255. }
  256. $this->options = array_merge($this->options,$ptOptions);
  257. }
  258. /**
  259. * Set up a cache filename that is unique to the tag parsed
  260. * @return string
  261. */
  262. public function getCacheFilename() {
  263. /* either hash the filename */
  264. if ($this->modx->context->getOption('phpthumbof.hash_thumbnail_names',false,$this->config)) {
  265. $inputSanitized = str_replace(array(':','/'),'_',$this->input);
  266. $this->cacheFilename = md5($inputSanitized);
  267. $this->cacheFilename .= '.'.md5(serialize($this->options));
  268. $this->cacheFilename .= '.' . (!empty($this->options['f']) ? $this->options['f'] : 'png');
  269. } else { /* or attempt to preserve the filename */
  270. $inputSanitized = str_replace(array('http://','https://','ftp://','sftp://'),'',$this->input);
  271. $inputSanitized = str_replace(array(':'),'_',$inputSanitized);
  272. $this->cacheFilename = basename($inputSanitized);
  273. if ($this->modx->context->getOption('phpthumbof.postfix_property_hash',true,$this->config)) {
  274. if (!empty($this->options['f'])) { /* get rid of the middle extension and put it at the end */
  275. $length = strlen($this->cacheFilename);
  276. $extLength = strlen($this->options['f']);
  277. $cut = $length-$extLength-1;
  278. if (strlen($this->cacheFilename) > $cut) {
  279. $this->cacheFilename = substr($this->cacheFilename,0,$cut);
  280. }
  281. }
  282. $this->cacheFilename .= '.'.md5(serialize($this->options)).$this->modx->resource->get('id');
  283. $this->cacheFilename .= '.' . (!empty($this->options['f']) ? $this->options['f'] : 'png');
  284. }
  285. }
  286. $this->cacheKey = $this->config['cachePath'].$this->cacheFilename;
  287. return $this->cacheKey;
  288. }
  289. /**
  290. * Get the cache file URL
  291. * @return string
  292. */
  293. public function getCacheUrl() {
  294. $this->cacheUrl = $this->config['cachePathUrl'].str_replace($this->config['cachePath'],'',$this->cacheKey);
  295. $this->cacheUrl = $this->stripDoubleSlashes($this->cacheUrl);
  296. return $this->cacheUrl;
  297. }
  298. /**
  299. * Properly strip double slashes in a string, protecting :// prefixes (such as http://)
  300. * @param string $string
  301. * @return string
  302. */
  303. protected function stripDoubleSlashes($string) {
  304. $string = str_replace('//','/',$string);
  305. if (strpos($string,':/') !== false) {
  306. $string = str_replace(array(
  307. ':/'
  308. ),array(
  309. '://',
  310. ),$string);
  311. }
  312. return $string;
  313. }
  314. /**
  315. * Render the thumbnail
  316. * @return mixed|string
  317. */
  318. public function render() {
  319. $this->getCacheFilename();
  320. $this->getCacheUrl();
  321. $this->cleanCache();
  322. $this->phpThumbOf->startDebug();
  323. /* if using s3, check for file there */
  324. $useS3 = $this->modx->getOption('useS3',$this->config,false);
  325. if ($useS3) {
  326. $expired = $this->checkForS3Cache();
  327. if ($expired !== true) {
  328. return $expired;
  329. }
  330. }
  331. $this->checkCacheFilePermissions();
  332. $this->phpThumbOf->endDebug();
  333. if ($cacheUrl = $this->checkForCachedFile()) {
  334. return $cacheUrl;
  335. }
  336. /* actually make the thumbnail */
  337. if ($this->phpThumb->GenerateThumbnail()) { // this line is VERY important, do not remove it!
  338. if ($this->phpThumb->RenderToFile($this->cacheKey)) {
  339. $this->checkCacheFilePermissions();
  340. if ($useS3) {
  341. $this->cacheUrl = $this->pushToS3();
  342. }
  343. return str_replace(' ','%20',$this->cacheUrl);
  344. } else {
  345. $this->modx->log(modX::LOG_LEVEL_ERROR,'[phpThumbOf] Could not cache thumb "'.$this->input.'" to file at: '.$this->cacheKey.' - Debug: '.print_r($this->phpThumb->debugmessages,true));
  346. }
  347. } else {
  348. $this->modx->log(modX::LOG_LEVEL_ERROR,'[phpThumbOf] Could not generate thumbnail: '.$this->input.' - Debug: '.print_r($this->phpThumb->debugmessages,true));
  349. }
  350. return '';
  351. }
  352. /**
  353. * See if the file is cached on S3.
  354. * @return mixed
  355. */
  356. public function checkForS3Cache() {
  357. /* if using a CNAME alias, set here (ensure is postfixed with /) */
  358. $s3hostAliasLen = strlen($this->config['s3hostAlias']);
  359. if (!empty($this->config['s3hostAlias'])) {
  360. $this->config['s3hostAlias'] = str_replace(array('http://','https://'),'',$this->config['s3hostAlias']);
  361. if (substr($this->config['s3hostAlias'],$s3hostAliasLen-1,$s3hostAliasLen) != '/') {
  362. $this->config['s3hostAlias'] .= '/';
  363. }
  364. }
  365. $s3host = !empty($this->config['s3hostAlias']) ? $this->config['s3hostAlias'] : $this->config['s3hostDefault'];
  366. /* calc relative path of image in s3 bucket */
  367. $path = str_replace('//','/',$this->config['s3path'].$this->cacheFilename);
  368. $this->expired = true;
  369. $lastModified = 0;
  370. $s3imageUrl = '';
  371. /* check with php's get_headers (slower) */
  372. if ($this->config['s3headersCheck']) {
  373. $this->modx->log(modX::LOG_LEVEL_DEBUG,'[phpthumbof] Using get_headers to check modified.');
  374. $s3imageUrl = 'http://'.str_replace('//','/',$s3host.urlencode($path));
  375. $headers = get_headers($s3imageUrl,1);
  376. if (!empty($headers) && !empty($headers[0]) && $headers[0] == 'HTTP/1.1 200 OK') {
  377. if (empty($headers['Last-Modified'])) {
  378. $this->expired = true;
  379. } else {
  380. $this->expired = false;
  381. $lastModified = $headers['Last-Modified'];
  382. $lastModified = strtotime(trim($lastModified[1]));
  383. }
  384. } else {
  385. $this->expired = true;
  386. }
  387. } else { /* otherwise use amazon's (faster) get object info */
  388. $this->modx->log(modX::LOG_LEVEL_DEBUG,'[phpthumbof] Using get_object_url to check modified.');
  389. $s3response = $this->aws->getFileUrl($path);
  390. if (!empty($s3response) && is_object($s3response) && !empty($s3response->body) && !empty($s3response->status) && $s3response->status == 200) {
  391. /* check expiry for image */
  392. $this->expired = false;
  393. $lastModified = strtotime($s3response->header['last-modified']);
  394. $s3imageUrl = $s3response->header['_info']['url'];
  395. if (!empty($this->config['s3hostAlias'])) {
  396. $s3imageUrl = str_replace($this->config['s3hostDefault'],$this->config['s3hostAlias'],$s3imageUrl);
  397. }
  398. }
  399. }
  400. /* check to see if expired */
  401. if (!empty($lastModified)) {
  402. /* use last-modified to determine age */
  403. $maxAge = (int)$this->modx->getOption('phpthumbof.s3_cache_time',null,24) * 60 * 60;
  404. $now = time();
  405. if (($now - $lastModified) > $maxAge) {
  406. $this->expired = true;
  407. }
  408. }
  409. /* if not expired past the cache time, use that url. otherwise, delete from S3 */
  410. if (!$this->expired) {
  411. $this->phpThumbOf->endDebug();
  412. return $s3imageUrl;
  413. }
  414. $this->aws->deleteObject($path);
  415. return true;
  416. }
  417. /**
  418. * Push the cached file to S3
  419. *
  420. * @return string The URL returned by S3
  421. */
  422. public function pushToS3() {
  423. $response = $this->aws->upload($this->cacheKey,$this->config['s3path']);
  424. if (!empty($response)) {
  425. if (!empty($this->config['s3hostAlias'])) {
  426. $this->cacheUrl = str_replace($this->config['s3hostDefault'],$this->config['s3hostAlias'],$response);
  427. } else {
  428. $this->cacheUrl = $response;
  429. }
  430. @unlink($this->cacheKey);
  431. }
  432. return $this->cacheUrl;
  433. }
  434. /**
  435. * Ensure the cache file permissions are correct
  436. */
  437. public function checkCacheFilePermissions() {
  438. if (!empty($this->cacheKey)) {
  439. $filePerm = (int)$this->modx->context->getOption('new_file_permissions','0664',$this->config);
  440. $permissions = @fileperms($this->cacheKey);
  441. if ($permissions != $filePerm) {
  442. @chmod($this->cacheKey, octdec($filePerm));
  443. }
  444. }
  445. }
  446. /**
  447. * Check to see if there's a cached file of this thumbnail already
  448. *
  449. * @return boolean|string
  450. */
  451. public function checkForCachedFile() {
  452. if (file_exists($this->cacheKey) && !$this->modx->getOption('useS3',$this->config,false) && !$this->expired && $this->modx->getOption('cache',$this->config,true)) {
  453. $this->modx->log(modX::LOG_LEVEL_DEBUG,'[phpThumbOf] Using cached file found for thumb: '.$this->cacheKey);
  454. return str_replace(' ','%20',$this->cacheUrl);
  455. }
  456. return false;
  457. }
  458. /**
  459. * Keep the cache directory nice and clean
  460. *
  461. * @return boolean
  462. */
  463. public function cleanCache() {
  464. return $this->phpThumb->CleanUpCacheDirectory();
  465. }
  466. }