Jetpack_Media

Class to handle different actions related to media.

Defined (1)

The class is defined in the following location(s).

/_inc/lib/class.media.php  
  1. class Jetpack_Media { 
  2. public static $WP_ORIGINAL_MEDIA = '_wp_original_post_media'; 
  3. public static $WP_REVISION_HISTORY = '_wp_revision_history'; 
  4. public static $REVISION_HISTORY_MAXIMUM_AMOUNT = 0; 
  5. public static $WP_ATTACHMENT_IMAGE_ALT = '_wp_attachment_image_alt'; 
  6.  
  7. /** 
  8. * Generate a filename in function of the original filename of the media. 
  9. * The returned name has the `{basename}-{hash}-{random-number}.{ext}` shape. 
  10. * The hash is built according to the filename trying to avoid name collisions 
  11. * with other media files. 
  12. *  
  13. * @param number $media_id - media post ID 
  14. * @param string $new_filename - the new filename 
  15. * @return string A random filename. 
  16. */ 
  17. public static function generate_new_filename( $media_id, $new_filename ) { 
  18. // get the right filename extension 
  19. $new_filename_paths = pathinfo( $new_filename ); 
  20. $new_file_ext = $new_filename_paths['extension']; 
  21.  
  22. // take out filename from the original file or from the current attachment 
  23. $original_media = (array) self::get_original_media( $media_id ); 
  24.  
  25. if ( ! empty( $original_media ) ) { 
  26. $original_file_parts = pathinfo( $original_media['file'] ); 
  27. $filename_base = $original_file_parts['filename']; 
  28. } else { 
  29. $current_file = get_attached_file( $media_id ); 
  30. $current_file_parts = pathinfo( $current_file ); 
  31. $current_file_ext = $current_file_parts['filename']; 
  32. $filename_base = $current_file_parts['filename']; 
  33.  
  34. // add unique seed based on the filename 
  35. $filename_base .= '-' . crc32( $filename_base ) . '-'; 
  36.  
  37. $number_suffix = time() . rand( 100, 999 ); 
  38.  
  39. do { 
  40. $filename = $filename_base; 
  41. $filename .= $number_suffix; 
  42. $file_ext = $new_file_ext ? $new_file_ext : $current_file_ext; 
  43.  
  44. $new_filename = "{$filename}.{$file_ext}"; 
  45. $new_path = "{$current_file_parts['dirname']}/$new_filename"; 
  46. $number_suffix++; 
  47. } while( file_exists( $new_path ) ); 
  48.  
  49. return $new_filename; 
  50.  
  51. /** 
  52. * File urls use the post (image item) date to generate a folder path. 
  53. * Post dates can change, so we use the original date used in the `guid` 
  54. * url so edits can remain in the same folder. In the following function 
  55. * we capture a string in the format of `YYYY/MM` from the guid. 
  56. * For example with a guid of 
  57. * "http://test.files.wordpress.com/2016/10/test.png" the resulting string 
  58. * would be: "2016/10" 
  59. * @param number $media_id 
  60. * @return string 
  61. */ 
  62. private function get_time_string_from_guid( $media_id ) { 
  63. $time = date( "Y/m", strtotime( current_time( 'mysql' ) ) ); 
  64.  
  65. if ( $media = get_post( $media_id ) ) { 
  66. $pattern = '/\/(\d{4}\/\d{2})\//'; 
  67. preg_match( $pattern, $media->guid, $matches ); 
  68. if ( count( $matches ) > 1 ) { 
  69. $time = $matches[1]; 
  70. return $time; 
  71.  
  72. /** 
  73. * Return an array of allowed mime_type items used to upload a media file. 
  74. *  
  75. * @return array mime_type array 
  76. */ 
  77. static function get_allowed_mime_types( $default_mime_types ) { 
  78. return array_unique( array_merge( $default_mime_types, array( 
  79. 'application/msword', // .doc 
  80. 'application/vnd.ms-powerpoint', // .ppt, .pps 
  81. 'application/vnd.ms-excel', // .xls 
  82. 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx 
  83. 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', // .ppsx 
  84. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 
  85. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx 
  86. 'application/vnd.oasis.opendocument.text', // .odt 
  87. 'application/pdf', // .pdf 
  88. ) ) ); 
  89.  
  90. /** 
  91. * Checks that the mime type of the file 
  92. * is among those in a filterable list of mime types. 
  93. * @param string $file Path to file to get its mime type. 
  94. * @return bool 
  95. */ 
  96. protected static function is_file_supported_for_sideloading( $file ) { 
  97. if ( class_exists( 'finfo' ) ) { // php 5.3+ 
  98. $finfo = new finfo( FILEINFO_MIME ); 
  99. $mime = explode( '; ', $finfo->file( $file ) ); 
  100. $type = $mime[0]; 
  101.  
  102. } elseif ( function_exists( 'mime_content_type' ) ) { // PHP 5.2 
  103. $type = mime_content_type( $file ); 
  104.  
  105. } else { 
  106. return false; 
  107.  
  108. /** 
  109. * Filter the list of supported mime types for media sideloading. 
  110. * @since 4.0 
  111. * @module json-api 
  112. * @param array $supported_mime_types Array of the supported mime types for media sideloading. 
  113. */ 
  114. $supported_mime_types = apply_filters( 'jetpack_supported_media_sideload_types', array( 
  115. 'image/png',  
  116. 'image/jpeg',  
  117. 'image/gif',  
  118. 'image/bmp',  
  119. 'video/quicktime',  
  120. 'video/mp4',  
  121. 'video/mpeg',  
  122. 'video/ogg',  
  123. 'video/3gpp',  
  124. 'video/3gpp2',  
  125. 'video/h261',  
  126. 'video/h262',  
  127. 'video/h264',  
  128. 'video/x-msvideo',  
  129. 'video/x-ms-wmv',  
  130. 'video/x-ms-asf',  
  131. ) ); 
  132.  
  133. // If the type returned was not an array as expected, then we know we don't have a match. 
  134. if ( ! is_array( $supported_mime_types ) ) { 
  135. return false; 
  136.  
  137. return in_array( $type, $supported_mime_types ); 
  138.  
  139. /** 
  140. * Try to remove the temporal file from the given file array. 
  141. *  
  142. * @param array $file_array Array with data about the temporal file 
  143. * @return bool `true` if the file has been removed. `false` either the file doesn't exist or it couldn't be removed. 
  144. */ 
  145. private static function remove_tmp_file( $file_array ) { 
  146. if ( ! file_exists ( $file_array['tmp_name'] ) ) { 
  147. return false; 
  148. return @unlink( $file_array['tmp_name'] ); 
  149.  
  150. /** 
  151. * Save the given temporal file considering file type,  
  152. * correct location according to the original file path, etc. 
  153. * The file type control is done through of `jetpack_supported_media_sideload_types` filter,  
  154. * which allows define to the users their own file types list. 
  155. *  
  156. * @param array $file_array file to save 
  157. * @param number $media_id 
  158. * @return array|WP_Error an array with information about the new file saved or a WP_Error is something went wrong. 
  159. */ 
  160. public static function save_temporary_file( $file_array, $media_id ) { 
  161. $tmp_filename = $file_array['tmp_name']; 
  162.  
  163. if ( ! file_exists( $tmp_filename ) ) { 
  164. return new WP_Error( 'invalid_input', 'No media provided in input.' ); 
  165.  
  166. // add additional mime_types through of the `jetpack_supported_media_sideload_types` filter 
  167. $mime_type_static_filter = array( 
  168. 'Jetpack_Media',  
  169. 'get_allowed_mime_types' 
  170. ); 
  171.  
  172. add_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter ); 
  173. if ( 
  174. ! self::is_file_supported_for_sideloading( $tmp_filename ) && 
  175. ! file_is_displayable_image( $tmp_filename ) 
  176. ) { 
  177. @unlink( $tmp_filename ); 
  178. return new WP_Error( 'invalid_input', 'Invalid file type.', 403 ); 
  179. remove_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter ); 
  180.  
  181. // generate a new file name 
  182. $tmp_new_filename = self::generate_new_filename( $media_id, $file_array[ 'name' ] ); 
  183.  
  184. // start to create the parameters to move the temporal file 
  185. $overrides = array( 'test_form' => false ); 
  186.  
  187. // get time according to the original filaname 
  188. $time = self::get_time_string_from_guid( $media_id ); 
  189.  
  190. $file_array['name'] = $tmp_new_filename; 
  191. $file = wp_handle_sideload( $file_array, $overrides, $time ); 
  192.  
  193. self::remove_tmp_file( $file_array ); 
  194.  
  195. if ( isset( $file['error'] ) ) { 
  196. return new WP_Error( 'upload_error', $file['error'] ); 
  197.  
  198. return $file; 
  199.  
  200. /** 
  201. * Return an object with an snapshot of a revision item. 
  202. *  
  203. * @param object $media_item - media post object 
  204. * @return object a revision item  
  205. */ 
  206. public static function get_snapshot( $media_item ) { 
  207. $current_file = get_attached_file( $media_item->ID ); 
  208. $file_paths = pathinfo( $current_file ); 
  209.  
  210. $snapshot = array( 
  211. 'date' => (string) WPCOM_JSON_API_Date::format_date( $media_item->post_modified_gmt, $media_item->post_modified ),  
  212. 'URL' => (string) wp_get_attachment_url( $media_item->ID ),  
  213. 'file' => (string) $file_paths['basename'],  
  214. 'extension' => (string) $file_paths['extension'],  
  215. 'mime_type' => (string) $media_item->post_mime_type,  
  216. 'size' => (int) filesize( $current_file )  
  217. ); 
  218.  
  219. return (object) $snapshot; 
  220.  
  221. /** 
  222. * Add a new item into revision_history array. 
  223. *  
  224. * @param object $media_item - media post object 
  225. * @param file $file - file recently added 
  226. * @param bool $has_original_media - condition is the original media has been already added 
  227. * @return bool `true` if the item has been added. Otherwise `false`. 
  228. */ 
  229. public static function register_revision( $media_item, $file, $has_original_media ) { 
  230. if ( is_wp_error( $file ) || ! $has_original_media ) { 
  231. return false; 
  232.  
  233. add_post_meta( $media_item->ID, self::$WP_REVISION_HISTORY, self::get_snapshot( $media_item ) ); 
  234. /** 
  235. * Return the `revision_history` of the given media. 
  236. *  
  237. * @param number $media_id - media post ID 
  238. * @return array `revision_history` array 
  239. */ 
  240. public static function get_revision_history( $media_id ) { 
  241. return array_reverse( get_post_meta( $media_id, self::$WP_REVISION_HISTORY ) ); 
  242.  
  243. /** 
  244. * Return the original media data 
  245. */ 
  246. public static function get_original_media( $media_id ) { 
  247. $original = get_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA, true ); 
  248. $original = $original ? $original : array(); 
  249. return $original; 
  250.  
  251. public static function delete_file( $pathname ) { 
  252. if ( ! file_exists( $pathname ) || ! is_file( $pathname ) ) { 
  253. // let's touch a fake file to try to `really` remove the media file 
  254. touch( $pathname ); 
  255.  
  256. return wp_delete_file( $pathname ); 
  257.  
  258. /** 
  259. * Try to delete a file according to the dirname of 
  260. * the media attached file and the filename. 
  261. *  
  262. * @param number $media_id - media post ID 
  263. * @param string $filename - basename of the file ( name-of-file.ext ) 
  264. * @return bool `true` is the file has been removed, `false` if not. 
  265. */ 
  266. private static function delete_media_history_file( $media_id, $filename ) { 
  267. $attached_path = get_attached_file( $media_id ); 
  268. $attached_parts = pathinfo( $attached_path ); 
  269. $dirname = $attached_parts['dirname']; 
  270.  
  271. $pathname = $dirname . '/' . $filename; 
  272.  
  273. // remove thumbnails 
  274. $metadata = wp_generate_attachment_metadata( $media_id, $pathname ); 
  275.  
  276. if ( isset( $metadata ) && isset( $metadata['sizes'] ) ) { 
  277. foreach ( $metadata['sizes'] as $size => $properties ) { 
  278. self::delete_file( $dirname . '/' . $properties['file'] ); 
  279.  
  280. // remove primary file 
  281. self::delete_file( $pathname ); 
  282.  
  283. /** 
  284. * Remove specific items from the `revision history` array 
  285. * depending on the given criteria: array( 
  286. * 'from' => (int) <from>,  
  287. * 'to' => (int) <to>,  
  288. * ) 
  289. *  
  290. * Also, it removes the file defined in each item. 
  291. * @param number $media_id - media post ID 
  292. * @param object $criteria - criteria to remove the items 
  293. * @param array [$revision_history] - revision history array 
  294. * @return array `revision_history` array updated. 
  295. */ 
  296. public static function remove_items_from_revision_history( $media_id, $criteria = array(), $revision_history ) { 
  297. if ( ! isset ( $revision_history ) ) { 
  298. $revision_history = self::get_revision_history( $media_id ); 
  299.  
  300. $from = $criteria['from']; 
  301. $to = $criteria['to'] ? $criteria['to'] : ( $from + 1 ); 
  302.  
  303. for ( $i = $from; $i < $to; $i++ ) { 
  304. $removed_item = array_slice( $revision_history, $from, 1 ); 
  305. if ( ! $removed_item ) { 
  306. break; 
  307.  
  308. array_splice( $revision_history, $from, 1 ); 
  309. self::delete_media_history_file( $media_id, $removed_item[0]->file ); 
  310.  
  311. // override all history items 
  312. delete_post_meta( $media_id, self::$WP_REVISION_HISTORY ); 
  313. $revision_history = array_reverse( $revision_history ); 
  314. foreach ( $revision_history as &$item ) { 
  315. add_post_meta( $media_id, self::$WP_REVISION_HISTORY, $item ); 
  316.  
  317. return $revision_history; 
  318.  
  319. /** 
  320. * Limit the number of items of the `revision_history` array. 
  321. * When the stack is overflowing the oldest item is remove from there (FIFO). 
  322. *  
  323. * @param number $media_id - media post ID 
  324. * @param number [$limit] - maximun amount of items. 20 as default. 
  325. * @return array items removed from `revision_history` 
  326. */ 
  327. public static function limit_revision_history( $media_id, $limit = null) { 
  328. if ( is_null( $limit ) ) { 
  329. $limit = self::$REVISION_HISTORY_MAXIMUM_AMOUNT; 
  330.  
  331. $revision_history = self::get_revision_history( $media_id ); 
  332.  
  333. $total = count( $revision_history ); 
  334.  
  335. if ( $total < $limit ) { 
  336. return array(); 
  337.  
  338. self::remove_items_from_revision_history( 
  339. $media_id,  
  340. array( 'from' => $limit, 'to' => $total ),  
  341. $revision_history 
  342. ); 
  343.  
  344. return self::get_revision_history( $media_id ); 
  345.  
  346. /** 
  347. * Remove the original file and clean the post metadata. 
  348. *  
  349. * @param number $media_id - media post ID 
  350. */ 
  351. public static function clean_original_media( $media_id ) { 
  352. $original_file = self::get_original_media( $media_id ); 
  353.  
  354. if ( ! $original_file ) { 
  355. return null; 
  356.  
  357. self::delete_media_history_file( $media_id, $original_file->file ); 
  358. return delete_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA ); 
  359.  
  360. /** 
  361. * Clean `revision_history` of the given $media_id. it means: 
  362. * - remove all media files tied to the `revision_history` items. 
  363. * - clean `revision_history` meta data. 
  364. * - remove and clean the `original_media` 
  365. *  
  366. * @param number $media_id - media post ID 
  367. * @return array results of removing these files 
  368. */ 
  369. public static function clean_revision_history( $media_id ) { 
  370. self::clean_original_media( $media_id ); 
  371.  
  372. $revision_history = self::get_revision_history( $media_id ); 
  373. $total = count( $revision_history ); 
  374. $updated_history = array(); 
  375.  
  376. if ( $total < 1 ) { 
  377. return $updated_history; 
  378.  
  379. $updated_history = self::remove_items_from_revision_history( 
  380. $media_id,  
  381. array( 'from' => 0, 'to' => $total ),  
  382. $revision_history 
  383. ); 
  384.  
  385. return $updated_history; 
  386.  
  387. /** 
  388. * Edit media item process: 
  389. * - update attachment file 
  390. * - preserve original media file 
  391. * - trace revision history 
  392. *  
  393. * @param number $media_id - media post ID 
  394. * @param array $file_array - temporal file 
  395. * @return {Post|WP_Error} Updated media item or a WP_Error is something went wrong. 
  396. */ 
  397. public static function edit_media_file( $media_id, $file_array ) { 
  398. $media_item = get_post( $media_id ); 
  399. $has_original_media = self::get_original_media( $media_id ); 
  400.  
  401. if ( ! $has_original_media ) { 
  402. // The first time that the media is updated 
  403. // the original media is stored into the revision_history 
  404. $snapshot = self::get_snapshot( $media_item ); 
  405. add_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA, $snapshot, true ); 
  406.  
  407. // save temporary file in the correct location 
  408. $uploaded_file = self::save_temporary_file( $file_array, $media_id ); 
  409.  
  410. if ( is_wp_error( $uploaded_file ) ) { 
  411. self::remove_tmp_file( $file_array ); 
  412. return $uploaded_file; 
  413.  
  414. // revision_history control 
  415. self::register_revision( $media_item, $uploaded_file, $has_original_media ); 
  416.  
  417. $uploaded_path = $uploaded_file['file']; 
  418. $udpated_mime_type = $uploaded_file['type']; 
  419. $was_updated = update_attached_file( $media_id, $uploaded_path ); 
  420.  
  421. if ( ! $was_updated ) { 
  422. return WP_Error( 'update_error', 'Media update error' ); 
  423.  
  424. $new_metadata = wp_generate_attachment_metadata( $media_id, $uploaded_path ); 
  425. wp_update_attachment_metadata( $media_id, $new_metadata ); 
  426.  
  427. // check maximum amount of revision_history 
  428. self::limit_revision_history( $media_id ); 
  429.  
  430. $edited_action = wp_update_post( (object) array( 
  431. 'ID' => $media_id,  
  432. 'post_mime_type' => $udpated_mime_type 
  433. ), true ); 
  434.  
  435. if ( is_wp_error( $edited_action ) ) { 
  436. return $edited_action; 
  437.  
  438. return $media_item;