PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/core/cache/class.disk.cache.php

http://buddypress-media.googlecode.com/
PHP | 652 lines | 236 code | 134 blank | 282 comment | 59 complexity | 0c546d527197af599658d2385d1b16f1 MD5 | raw file
Possible License(s): AGPL-1.0, Apache-2.0, GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * BP-MEDIA DISK CACHE
  4. * Handles disk cache operations for media item thumbnails
  5. *
  6. * @version 0.1.9
  7. * @since 0.1.9
  8. * @package BP-Media
  9. * @subpackage Cache
  10. * @license GPL v2.0
  11. * @link http://code.google.com/p/buddypress-media/
  12. *
  13. * ========================================================================================================
  14. */
  15. class BPM_dCache {
  16. /**
  17. * Fetches the filepath and URL info for a given cache level
  18. *
  19. * @version 0.1.9
  20. * @since 0.1.9
  21. *
  22. * @param int $cache_level | Media cache level
  23. * @return string $path | File path for cache folder
  24. * @return string $url | URL for cache folder
  25. */
  26. public function getLevelPath($cache_level) {
  27. global $bpm;
  28. switch ($cache_level) {
  29. case 0: {
  30. if ($bp->bpa->options['cache_mode_L0'] == 'global') {
  31. $path = $bp->bpa->options['cache_path_L0Folder'];
  32. $url = $bp->bpa->options['cache_path_L0URI'];
  33. } else {
  34. $path = bp_media_upload_path() . $bp->bpa->options['cache_offset_L0Folder'];
  35. $url = bp_media_get_url_from_path( bp_media_upload_path() . $bp->bpa->options['cache_offset_L0URI']);
  36. } break;
  37. }
  38. case 1: {
  39. if ($bp->bpa->options['cache_mode_L1'] == 'global') {
  40. $path = $bp->bpa->options['cache_path_L1Folder'];
  41. $url = $bp->bpa->options['cache_path_L1URI'];
  42. } else {
  43. $path = bp_media_upload_path() . $bp->bpa->options['cache_offset_L1Folder'];
  44. $url = bp_media_get_url_from_path( bp_media_upload_path() . $bp->bpa->options['cache_offset_L1URI']);
  45. } break;
  46. }
  47. case 2: {
  48. if ($bp->bpa->options['cache_mode_L2'] == 'global') {
  49. $path = $bp->bpa->options['cache_path_L2Folder'];
  50. $url = $bp->bpa->options['cache_path_L2URI'];
  51. } else {
  52. $path = bp_media_upload_path() . $bp->bpa->options['cache_offset_L2Folder'];
  53. $url = bp_media_get_url_from_path( bp_media_upload_path() . $bp->bpa->options['cache_offset_L2URI']);
  54. } break;
  55. }
  56. case 3: {
  57. if ($bp->bpa->options['cache_mode_L3'] == 'global') {
  58. $path = $bp->bpa->options['cache_path_L3Folder'];
  59. $url = $bp->bpa->options['cache_path_L3URI'];
  60. } else {
  61. $path = bp_media_upload_path() . $bp->bpa->options['cache_offset_L3Folder'];
  62. $url = bp_media_get_url_from_path( bp_media_upload_path() . $bp->bpa->options['cache_offset_L3URI']);
  63. } break;
  64. }
  65. case 4: {
  66. if ($bp->bpa->options['cache_mode_L4'] == 'global') {
  67. $path = $bp->bpa->options['cache_path_L4Folder'];
  68. $url = $bp->bpa->options['cache_path_L4URI'];
  69. } else {
  70. $path = bp_media_upload_path() . $bp->bpa->options['cache_offset_L4Folder'];
  71. $url = bp_media_get_url_from_path( bp_media_upload_path() . $bp->bpa->options['cache_offset_L4URI']);
  72. } break;
  73. }
  74. default : {
  75. return null;
  76. }
  77. }
  78. return compact('path', 'url');
  79. }
  80. /**
  81. * Generates a full set of media item parameters so an item's cache filename can be constructed. Uses a partial
  82. * set of parameters, information stored in the $media_template / $albums_template global variables, and
  83. * the plugin's database tables.
  84. *
  85. * @version 0.1.9
  86. * @since 0.1.9
  87. *
  88. * @param array $args [
  89. * @param int $media_id | GUID for media item.
  90. * @param int $org_w | Original width of media item in pixels
  91. * @param int $org_h | Original height of media item in pixels
  92. * @param string $org_path | Original file path of media item
  93. * @param string $alt | Used as alt attribute of preview image.
  94. * @param string $size | Name of a registered size.
  95. * @param string $format | Image file format of cached image
  96. * @param string $watermark | Name of registered watermark to apply to image
  97. * @param string $process | Process queue strategy for item
  98. * @param int $resize_w | Width in pixels to resize media item to.
  99. * @param int $resize_h | Height in pixels to resize media item to.
  100. * @param bool $resize_crop | Crop when aspect ratio is different, default false
  101. * @param bool $resize_upscale | Upscale when original is smaller, default false
  102. * @param int $resize_quality | JPG image quality [1-100], default 90
  103. * ]
  104. * @return array | processed $args
  105. */
  106. public function fill_resize_args($args) {
  107. global $bpm, $media_template, $albums_template;
  108. $defaults = array(
  109. 'resize_crop' => false,
  110. 'resize_upscale' => false,
  111. 'resize_quality' => 90,
  112. 'alt' => null,
  113. 'is_default_for' => false
  114. );
  115. $r = wp_parse_args($args,$defaults);
  116. // Determine the image processing and cache parameters for the size
  117. // CASE 1: We've been been passed a registered media size
  118. if( isset($r['size'])) {
  119. $size_info = bp_media_imageSize_getRegisteredSize($r['size']);
  120. if($size_info) {
  121. $r['resize_w'] = $size_info->x_pixels;
  122. $r['resize_h'] = $size_info->y_pixels;
  123. $r['format'] = $size_info->format;
  124. $r['resize_quality'] = $size_info->quality;
  125. $r['resize_crop'] = $size_info->crop;
  126. $r['resize_upscale'] = $size_info->scale;
  127. $r['watermark'] = $size_info->watermark;
  128. $r['cache_level'] = $size_info->cache;
  129. $r['process'] = $size_info->process;
  130. return $r;
  131. }
  132. else {
  133. // If the size name isn't in the database, quit
  134. return false;
  135. }
  136. }
  137. // CASE 2: We've been passed a pair of x/y resize parameters
  138. elseif ( isset( $r['resize_w'] ) && isset( $r['resize_h']) ) {
  139. $r['size'] = 'custom';
  140. $size_info = bp_media_imageSize_getRegisteredSize($r['size']);
  141. $r['cache_level'] = $size_info->cache;
  142. return $r;
  143. }
  144. // CASE 3: We've been passed neither, so quit.
  145. else {
  146. return false;
  147. }
  148. }
  149. /**
  150. * Calculates the resize dimensions for a cache image
  151. *
  152. * @version 0.1.9
  153. * @since 0.1.9
  154. *
  155. * @param int $org_w original width
  156. * @param int $org_h original height
  157. * @param int $resize_w desidered width
  158. * @param int $resize_h desidered height
  159. * @param bool $crop if output dimensions ratio will be the same of resize dimensions(true) or original dimensions(false)
  160. * @param bool $upscale if display dimension should be enlarged if original is smaller than desidered size, doesn't affect file dimensions
  161. * @return array [(file_w, file_h, display_w, display_h) actual width/height the file should be cached and width/height to display the file with
  162. */
  163. public function calculate_dimensions($org_w, $org_h, $resize_w, $resize_h, $crop, $upscale ) {
  164. // Args validation
  165. $known_dimensions = (bool)$org_w + (bool)$org_h + (bool)$resize_w + (bool)$resize_h;
  166. if( $known_dimensions < 3 ) {
  167. // Cannot calculate without at least three dimensions
  168. return array(0,0,0,0);
  169. }
  170. elseif( $known_dimensions < 4 ) {
  171. // If only 3 dimensions are provided, guess the missing one.
  172. // This is only a fallback/recovery, the only supported usage is with
  173. // all the 4 dimension filled with non empty values
  174. if ( !$org_w ){
  175. $org_w = round($org_h*$resize_w/$resize_h);
  176. }
  177. elseif ( !$org_h ){
  178. $org_h = round($org_w*$resize_h/$resize_w);
  179. }
  180. elseif ( !$resize_w ){
  181. $resize_w = round($resize_h*$org_w/$org_h);
  182. }
  183. elseif ( !$resize_h ){
  184. $resize_h = round($resize_w*$org_h/$org_w);
  185. }
  186. }
  187. // Calculate size for file stored on disk (file_w, file_h)
  188. // and for displaying it in the browser (display_w, display_h)
  189. // ====================================================================================
  190. if($crop && !$upscale){
  191. $file_w = $display_w = min($resize_w, $org_w);
  192. $file_h = $display_h = min($resize_h, $org_h);
  193. }
  194. else {
  195. $original_ratio = $org_w / $org_h;
  196. $resize_ratio = $resize_w / $resize_h;
  197. $ratio = $crop ? $resize_ratio : $original_ratio;
  198. if( ($original_ratio > $resize_ratio) XOR $crop ){
  199. $file_w = min($org_w, $resize_w);
  200. $file_h = round($file_w / $ratio);
  201. $display_w = $upscale ? $resize_w : $file_w;
  202. $display_h = round($display_w / $ratio);
  203. }
  204. else {
  205. $file_h = min($org_h, $resize_h);
  206. $file_w = round($file_h * $ratio);
  207. $display_h = $upscale ? $resize_h : $file_h;
  208. $display_w = round($display_h * $ratio);
  209. }
  210. }
  211. return apply_filters('bp_media_calculate_size', array((int)$file_w,(int)$file_h,(int)$display_w,(int)$display_h), $org_w, $org_h, $resize_w, $resize_h, $crop, $upscale);
  212. }
  213. /**
  214. * Finds an image in the cache based on supplied info. If a cached version of the image does not exist, it creates one.
  215. *
  216. * @version 0.1.9
  217. * @since 0.1.9
  218. *
  219. * @param array $args [
  220. * @param int $media_id | GUID for media item. [Optional if this item is the active item in $media_template]
  221. * @param int $org_w | Original width of media item in pixels [Optional. If not given and not in $media_template, it will be queried from the db]
  222. * @param int $org_h | Original height of media item in pixels [Optional. If not given and not in $media_template, it will be queried from the db]
  223. * @param string $org_path | Original file path of media item [Optional. If not given and not in $media_template, it will be queried from the db]
  224. * @param string $alt | Used as alt attribute of preview image. If empty and not false, the title of the current item in media loop will be used.
  225. * @param string $size | Name of a registered size. If supplied, settings for the registered size will override all resize_* args
  226. * @param int $resize_w | Width in pixels to resize media item to.
  227. * @param int $resize_h | Height in pixels to resize media item to.
  228. * @param string $format | Image file format of cached image
  229. * @param string $watermark | Name of registered watermark to apply to image
  230. * @param string $process | Process queue strategy for item
  231. * @param bool $resize_crop | Crop when aspect ratio is different, default false
  232. * @param bool $resize_upscale | Upscale when original is smaller, default false
  233. * @param int $resize_quality | JPG image quality [1-100], default 90
  234. * ]
  235. * @return array | url, display_w, display_h, alt, path, file_w, file_h
  236. */
  237. public function getImage($args) {
  238. global $bpm;
  239. if(empty($args['owner_id'])) {
  240. debug_print_backtrace();
  241. // var_dump(debug_backtrace());
  242. var_dump($args); die;
  243. }
  244. extract($args, EXTR_OVERWRITE);
  245. if ( $size == 'original' || ($org_w == $resize_w && $org_h == $resize_h) ) {
  246. $path = $org_path;
  247. $display_w = $file_w = $org_w;
  248. $display_h = $file_h = $org_w;
  249. $url = bp_media_get_url_from_path($path);
  250. return apply_filters('bp_media_find_or_generate_preview_image', compact('url','display_w','display_h','alt','path','file_w','file_h'), $args);
  251. }
  252. list($file_w, $file_h, $display_w, $display_h) = $this->calculate_dimensions( $org_w, $org_h, $resize_w, $resize_h, $resize_crop, $resize_upscale );
  253. if( ($org_w == $file_w) && ($org_h == $file_h) ){
  254. $path = $org_path;
  255. }
  256. else {
  257. $cache = $this->getLevelPath($cache_level);
  258. $base = $cache['path'] .'/'. ( $is_default_for ? 'default' : $owner_id );
  259. list( $path, $glob_search ) = $this->get_cache_file_path( $base, $media_id, $size, $file_w, $file_h, $resize_quality, $org_path );
  260. // Search for previously generated files that have same width, height and quality, no matter what size was requested when they have been generated
  261. $equivalent_files = glob( $glob_search );
  262. if ( is_array($equivalent_files) && count($equivalent_files) ){
  263. $path = $equivalent_files[0];
  264. // If the "Touch Files" option is set, update the timestamp on the file (Required for Windows PC's and web hosts
  265. // with last file access update switched off for performance reasons
  266. if ($bp->bpa->options['cache_server_touchFiles']) {
  267. touch($path);
  268. }
  269. }
  270. else {
  271. @wp_mkdir_p( $base );
  272. $cache_file = image_resize( $org_path, $file_w, $file_h, $resize_crop, null , $base, $resize_quality );
  273. rename($cache_file,$path);
  274. }
  275. }
  276. $url = bp_media_get_url_from_path($path);
  277. return apply_filters('bp_media_find_or_generate_preview_image', compact('url','display_w','display_h','alt','path','file_w','file_h'), $args);
  278. }
  279. public function get_cache_file_path( $dir, $media_id, $size, $file_w, $file_h, $resize_quality, $org_path, $is_default_for=false ) {
  280. $ext = pathinfo($org_path, PATHINFO_EXTENSION);
  281. $basename = basename($org_path, ".$ext");
  282. $resize_quality = ('gif' == $ext || 'png' == $ext) ? 0 : $resize_quality;
  283. if( $is_default_for ) {
  284. $name = sprintf('%s/%s_%s_w%d_h%d_q%03d.%s', $dir, $is_default_for, $size, $file_w, $file_h, $resize_quality, $ext);
  285. $glob_search = sprintf('%s/%s_*_w%d_h%d_q%03d.%s', $dir, $is_default_for, $file_w, $file_h, $resize_quality, $ext);
  286. } else {
  287. // Note: as each media could have more than one original file ( e.g. video and thumb ) is better only prefixing info so it's always possible to understand which file in org dir is this cache of
  288. $name = sprintf('%s/%010d_%s_w%d_h%d_q%03d_%s.%s', $dir, $media_id, $size, $file_w, $file_h, $resize_quality, $basename, $ext);
  289. $glob_search = sprintf('%s/%010d_*_w%d_h%d_q%03d_%s.%s', $dir, $media_id, $file_w, $file_h, $resize_quality, $basename, $ext);
  290. }
  291. return array( $name, $glob_search );
  292. }
  293. /**
  294. * Deletes a single media item from the cache
  295. *
  296. * @version 0.1.9
  297. * @since 0.1.9
  298. *
  299. * @param int $owner_id | The numeric ID of the media item's owner
  300. * @param int $media_id | The numeric ID of the media item
  301. * @return int | Number of files removed from cache (multiple image sizes possible for a single media item)
  302. */
  303. public function deleteSingleImage($owner_id, $media_id) {
  304. global $bpm;
  305. $file_count = 0;
  306. for( $level = 0; $this->getLevelPath($level); $level++ ) {
  307. $result = $this->getLevelPath($level);
  308. // Safety measure
  309. if( empty($result['path'])){
  310. die;
  311. }
  312. foreach( glob( $result['path'] . "/" . $owner_id . "/" . sprintf('%010d_',$media_id) . "*.*") as $filename) {
  313. @unlink($filename);
  314. $file_count++;
  315. }
  316. }
  317. return $file_count;
  318. }
  319. /**
  320. * Deletes all cache files for a registered media size
  321. *
  322. * @version 0.1.9
  323. * @since 0.1.9
  324. *
  325. * @param string $size | Name of registered media size
  326. * @return int | Number of files removed from cache
  327. */
  328. public function deleteImageSize($size) {
  329. global $bpm;
  330. $file_count = 0;
  331. for($level = 0; $this>getLevelPath($level); $level++) {
  332. $result = $this->getLevelPath($level);
  333. // Safety measure
  334. if( empty($result['path'])) {
  335. die;
  336. }
  337. foreach( glob( $result['path'] . "/*") as $owner_dir ) {
  338. if( is_dir($owner_dir) ) {
  339. foreach( glob( $owner_dir . "/*_$size_*.*") as $filename ) {
  340. @unlink($filename);
  341. $file_count++;
  342. }
  343. }
  344. }
  345. }
  346. return $file_count;
  347. }
  348. /**
  349. * Deletes all cache files for a single user
  350. *
  351. * @version 0.1.9
  352. * @since 0.1.9
  353. *
  354. * @param int $user_id | Numeric ID of user
  355. * @return int | Number of files removed from cache
  356. */
  357. public function deleteUser($user_id) {
  358. $file_count = 0;
  359. for($level = 0; $this->getLevelPath($level); $level++) {
  360. $result = $this->getLevelPath($level);
  361. // Safety measure
  362. if( empty($result['path']))
  363. die;
  364. foreach (glob( $result['path'] . "/" . $user_id . "/*") as $filename) {
  365. @unlink($filename);
  366. $file_count++;
  367. }
  368. @rmdir($result['path'] . "/" . $user_id);
  369. }
  370. return $file_count;
  371. }
  372. /**
  373. * Reports the storage space currently in use by a specific cache level
  374. *
  375. * @version 0.1.9
  376. * @since 0.1.9
  377. * @param int $level | Numeric ID of cache level
  378. * @return int $inode_count| Number of inodes currently used by cache level
  379. * @return float $storage_count | Number of bytes currently used by cache level
  380. */
  381. public function checkUsage($level=3) {
  382. $inode_count = 0;
  383. $storage_count = 0;
  384. $path = $this->getLevelPath($level);
  385. if( file_exists($path['path']) && is_dir($path['path']) ){
  386. foreach (glob( $path['path'] . "/*") as $owner_dir) {
  387. if(is_dir($owner_dir)) {
  388. foreach (glob( $owner_dir . "/*") as $filename) {
  389. $inode_count++;
  390. $storage_count = $storage_count + filesize($filename);
  391. }
  392. }
  393. }
  394. }
  395. $result = array();
  396. $result["inode_count"] = $inode_count;
  397. $result["storage_count"] = $storage_count;
  398. return $result;
  399. }
  400. /**
  401. * Removes files and inodes from a specific cache level to meet the specified inode and disk
  402. * space usage limits
  403. *
  404. * @version 0.1.9
  405. * @since 0.1.9
  406. *
  407. * @param int $level | Numeric ID of cache level
  408. * @param int $max_inodes | Number of inodes to purge cache to
  409. * @param float $max_space | Number of bytes to purge cache level to
  410. * @return int $inodes_deleted| Number of inodes deleted by function
  411. * @return float $storage_deleted | Number of bytes deleted by function
  412. */
  413. public function purge($level, $max_inodes, $max_space){
  414. $inode_count = 0;
  415. $storage_count = 0;
  416. // This comparison function determines how to sort the file node array
  417. function node_compare($a, $b) {
  418. if ($a['timestamp'] == $b['timestamp']) {
  419. return 0;
  420. }
  421. return ($a['timestamp'] > $b['timestamp']) ? -1 : 1;
  422. }
  423. $nodes = array();
  424. $result = $this->getLevelPath($level);
  425. // We iterate through all of the user directories inside the cache directory,
  426. // building an array of file info parameters
  427. foreach( glob( $result['path'] . "/*") as $owner_dir ) {
  428. if( is_dir($owner_dir )) {
  429. foreach( glob( $owner_dir . "/*") as $filename ) {
  430. $file_size = filesize($filename);
  431. $nodes[] = array (
  432. 'timestamp' => fileatime($filename), // Timestamp for the file
  433. 'path' => $filename, // Full path to the file
  434. 'size' => $file_size // Size of file in bytes
  435. );
  436. // As we build the array, we keep a running total of the number of files
  437. // and their space usage in bytes
  438. $storage_count = $storage_count + $file_size;
  439. $inode_count++;
  440. }
  441. }
  442. }
  443. // If the total amount of storage space, and/or the total number of files exceeds the
  444. // limit passed to the function, we need to purge the cache
  445. if( (($inode_count > $max_inodes) && ($max_inodes != 0)) || (($storage_count > $max_space) && ($max_space != 0)) ) {
  446. $inode_count = 0;
  447. $storage_count = 0;
  448. // We sort the file array from newest to oldest files, using our
  449. // custom sort function
  450. usort($nodes, "node_compare");
  451. // Then we iterate through the sorted array, starting at the newest file, and keep a
  452. // running total of the number of the number of files and their total space usage
  453. // TODO: less iterations if sorted from oldest and deleting until $storage_deleted >= ( $storage_count - $max_space ), same for inode
  454. foreach ($nodes as $node) {
  455. $storage_count = $storage_count + $node['size'];
  456. $inode_count++;
  457. // Once our total reaches either the maximum number of files or the maximum allowed storage space,
  458. // we delete every file in the array after that point
  459. if( (($inode_count > $max_inodes) && ($max_inodes != 0)) || (($storage_count > $max_space) && ($max_space != 0)) ){
  460. //@unlink($filename);
  461. $storage_deleted = $storage_deleted + $node['size'];
  462. $inodes_deleted++;
  463. //echo "\n KILL:" . $node['timestamp'] . " : " . $node['path'] . "\n";
  464. }
  465. else {
  466. //echo "\n SAVE:" . $node['timestamp'] . " : " . $node['path'] . "\n";
  467. }
  468. }
  469. }
  470. return compact($inodes_deleted, $storage_deleted);
  471. }
  472. } // End of class BPM_dCache
  473. ?>