/app/rule/media/class-ms-rule-media-model.php

  1. <?php 
  2. /** 
  3. * Membership Media Rule class. 
  4. * 
  5. * Persisted by Membership class. 
  6. * 
  7. * @since 1.0.0 
  8. * 
  9. * @package Membership2 
  10. * @subpackage Model 
  11. */ 
  12.  
  13. /** 
  14. * Rule model. 
  15. */ 
  16. class MS_Rule_Media_Model extends MS_Rule { 
  17.  
  18. /** 
  19. * Rule type. 
  20. * 
  21. * @since 1.0.0 
  22. * 
  23. * @var string $rule_type 
  24. */ 
  25. protected $rule_type = MS_Rule_Media::RULE_ID; 
  26.  
  27. /** 
  28. * Media protection type constants. 
  29. * 
  30. * @since 1.0.0 
  31. * 
  32. * @var string $protection_type 
  33. */ 
  34. const PROTECTION_TYPE_BASIC = 'protection_type_basic'; 
  35. const PROTECTION_TYPE_COMPLETE = 'protection_type_complete'; 
  36. const PROTECTION_TYPE_HYBRID = 'protection_type_hybrid'; 
  37.  
  38. /** 
  39. * Media protection file change prefix. 
  40. * 
  41. * @since 1.0.0 
  42. * 
  43. * @var string 
  44. */ 
  45. const FILE_PROTECTION_PREFIX = 'ms_'; 
  46.  
  47. /** 
  48. * Media protection file seed/token. 
  49. * 
  50. * @since 1.0.0 
  51. * 
  52. * @var string 
  53. */ 
  54. const FILE_PROTECTION_INCREMENT = 2771; 
  55.  
  56. /** 
  57. * Returns the active flag for a specific rule. 
  58. * State depends on Add-on 
  59. * 
  60. * @since 1.0.0 
  61. * @return bool 
  62. */ 
  63. static public function is_active() { 
  64. return MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_MEDIA ); 
  65.  
  66.  
  67. /** 
  68. * Verify access to the current content. 
  69. * 
  70. * This rule will return NULL (not relevant), because the media-item is 
  71. * protected via the download URL and not by protecting the current page. 
  72. * 
  73. * @since 1.0.0 
  74. * 
  75. * @param string $id The content id to verify access. 
  76. * @param bool $admin_has_access Used for post-meta box to get correct 
  77. * post permission: By default admin has access to all posts, if 
  78. * this flag is FALSE then the admin-check is skipped. 
  79. * @return bool|null True if has access, false otherwise. 
  80. * Null means: Rule not relevant for current page. 
  81. */ 
  82. public function has_access( $id, $admin_has_access = true ) { 
  83. if ( MS_Model_Addon::is_enabled( MS_Addon_Mediafiles::ID ) 
  84. && 'attachment' == get_post_type( $id ) 
  85. ) { 
  86. return parent::has_access( $id, $admin_has_access ); 
  87. } else { 
  88. return null; 
  89.  
  90. /** 
  91. * Get the protection type available. 
  92. * 
  93. * @since 1.0.0 
  94. * 
  95. * @return array { 
  96. * The protection type => Description. 
  97. * @type string $protection_type The media protection type. 
  98. * @type string $description The media protection description. 
  99. * } 
  100. */ 
  101. public static function get_protection_types() { 
  102. $settings = MS_Factory::load( 'MS_Model_Settings' ); 
  103. $mask = $settings->downloads['masked_url']; 
  104. $example1 = MS_Helper_Utility::home_url( $mask . date( '/Y/m/' ) . 'my-image.jpg' ); 
  105. $example2 = MS_Helper_Utility::home_url( $mask . '/ms_12345.jpg' ); 
  106. $example3 = MS_Helper_Utility::home_url( $mask . '/?ms_file=ms_12345.jpg' ); 
  107. $example1 = '<br /><small>' . __( 'Example:', 'membership2' ) . ' ' . $example1 . '</small>'; 
  108. $example2 = '<br /><small>' . __( 'Example:', 'membership2' ) . ' ' . $example2 . '</small>'; 
  109. $example3 = '<br /><small>' . __( 'Example:', 'membership2' ) . ' ' . $example3 . '</small>'; 
  110.  
  111. $protection_types = array( 
  112. self::PROTECTION_TYPE_BASIC => __( 'Basic protection (default)', 'membership2' ) . $example1,  
  113. self::PROTECTION_TYPE_COMPLETE => __( 'Complete protection', 'membership2' ) . $example2,  
  114. self::PROTECTION_TYPE_HYBRID => __( 'Hybrid protection', 'membership2' ) . $example3,  
  115. ); 
  116.  
  117. return apply_filters( 
  118. 'ms_rule_media_model_get_protection_types',  
  119. $protection_types 
  120. ); 
  121.  
  122. /** 
  123. * Validate protection type. 
  124. * 
  125. * @since 1.0.0 
  126. * 
  127. * @param string $type The protection type to validate. 
  128. * @return boolean True if is valid. 
  129. */ 
  130. public static function is_valid_protection_type( $type ) { 
  131. $valid = false; 
  132. $types = self::get_protection_types(); 
  133.  
  134. if ( array_key_exists( $type, $types ) ) { 
  135. $valid = true; 
  136.  
  137. return apply_filters( 
  138. 'ms_rule_media_model_is_valid_protection_type',  
  139. $valid 
  140. ); 
  141.  
  142. /** 
  143. * Starts the output buffering to replace all links in the final HTML 
  144. * document. 
  145. * 
  146. * Related filter: 
  147. * - init 
  148. * 
  149. * @since 1.0.0 
  150. */ 
  151. public function buffer_start() { 
  152. ob_start( array( $this, 'protect_download_content' ) ); 
  153.  
  154. /** 
  155. * Ends the output buffering and calls the output_callback function. 
  156. * 
  157. * Related filter: 
  158. * - shutdown 
  159. * 
  160. * @since 1.0.0 
  161. */ 
  162. public function buffer_end() { 
  163. if ( ob_get_level() ) { 
  164. ob_end_flush(); 
  165.  
  166. /** 
  167. * Set initial protection. 
  168. * 
  169. * @since 1.0.0 
  170. */ 
  171. public function initialize() { 
  172. parent::protect_content(); 
  173.  
  174. if ( MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_MEDIA ) && ( ! is_admin() && ! is_customize_preview() ) ) { 
  175. // Start buffering during init action, though output should only 
  176. // happen a lot later... This way we're safe. 
  177. $this->add_action( 'init', 'buffer_start', 9999 ); 
  178.  
  179. // Process the buffer right in the end. 
  180. $this->add_action( 'shutdown', 'buffer_end' ); 
  181.  
  182. $this->add_action( 'parse_request', 'handle_download_protection', 3 ); 
  183.  
  184. /** 
  185. * Protect media file. 
  186. * 
  187. * Search content and mask media filename and path. 
  188. * 
  189. * This function is called as output_callback for ob_start() - as a result 
  190. * we cannot echo/output anything in this function! The return value of the 
  191. * function will be displayed to the user. 
  192. * 
  193. * @since 1.0.0 
  194. * 
  195. * @param string $the_content The content before filter. 
  196. * @return string The content with masked media url. 
  197. */ 
  198. public function protect_download_content( $the_content ) { 
  199. do_action( 
  200. 'ms_rule_media_model_protect_download_content_before',  
  201. $the_content,  
  202. $this 
  203. ); 
  204.  
  205. $download_settings = MS_Plugin::instance()->settings->downloads; 
  206.  
  207. if ( ! MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_MEDIA ) ) { 
  208. return $the_content; 
  209.  
  210. $upload_dir = wp_upload_dir(); 
  211. $original_url = trailingslashit( $upload_dir['baseurl'] ); 
  212. $new_path = trailingslashit( 
  213. trailingslashit( get_option( 'home' ) ) . 
  214. $download_settings['masked_url'] 
  215. ); 
  216.  
  217. /** 
  218. * Find all the urls in the post and then we'll check if they are protected 
  219. * Regex from http://blog.mattheworiordan.com/post/13174566389/url-regular-expression-for-links-with-or-without-the 
  220. */ 
  221. $url_exp = implode( 
  222. '',  
  223. array( 
  224. '/((([A-Za-z]{3, 9}:(?:\/\/)?)',  
  225. '(?:[-;:&=\+\$, \w]+@)?',  
  226. '[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$, \w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?',  
  227. '\??(?:[-\+=&;%@.\w_]*)#?(?:[.\!\/\\w]*))?)/',  
  228. ); 
  229.  
  230. $matches = array(); 
  231. if ( preg_match_all( $url_exp, $the_content, $matches ) ) { 
  232. $home = untrailingslashit( get_option( 'home' ) ); 
  233.  
  234. /** 
  235. * $matches[0] .. Full link 'http://example.com/blog/img.png?ver=1' 
  236. * $matches[1] .. Full link 'http://example.com/blog/img.png?ver=1' 
  237. * $matches[2] .. Domain only 'http://example.com' 
  238. * $matches[3] .. Protocol 'http://' 
  239. * $matches[4] .. File path '/blog/img.png?ver=1' 
  240. */ 
  241. if ( ! empty( $matches ) && ! empty( $matches[2] ) ) { 
  242. $links = (array) $matches[0]; 
  243. $paths = (array) $matches[4]; 
  244.  
  245. foreach ( $links as $key => $link ) { 
  246. // Ignore all external links. 
  247. if ( 0 !== strpos( $link, $home ) ) { continue; } 
  248.  
  249. // The file is on local site - is it a valid attachment? 
  250. $file = basename( $paths[ $key ] ); 
  251. $post_id = $this->get_attachment_id( $link ); 
  252.  
  253. // Ignore links that have no relevant wp_posts entry. 
  254. if ( empty( $post_id ) ) { continue; } 
  255. $f_info = $this->extract_file_info( $file ); 
  256.  
  257. // We have a protected file - so we'll mask it! 
  258. switch ( $download_settings['protection_type'] ) { 
  259. case self::PROTECTION_TYPE_COMPLETE: 
  260. $protected_filename = self::FILE_PROTECTION_PREFIX . 
  261. ( $post_id + (int) self::FILE_PROTECTION_INCREMENT ) . 
  262. $f_info->size_extension . 
  263. '.' . pathinfo( $f_info->filename, PATHINFO_EXTENSION ); 
  264.  
  265. $the_content = str_replace( 
  266. $link,  
  267. $new_path . $protected_filename,  
  268. $the_content 
  269. ); 
  270. break; 
  271.  
  272. case self::PROTECTION_TYPE_HYBRID: 
  273. $protected_filename = self::FILE_PROTECTION_PREFIX . 
  274. ($post_id + (int) self::FILE_PROTECTION_INCREMENT ) . 
  275. $f_info->size_extension . 
  276. '.' . pathinfo( $f_info->filename, PATHINFO_EXTENSION ); 
  277.  
  278. $the_content = str_replace( 
  279. $link,  
  280. $new_path . '?ms_file=' . $protected_filename,  
  281. $the_content 
  282. ); 
  283. break; 
  284.  
  285. case self::PROTECTION_TYPE_BASIC: 
  286. default: 
  287. $the_content = str_replace( 
  288. $link,  
  289. str_replace( 
  290. $original_url,  
  291. $new_path,  
  292. $link 
  293. ),  
  294. $the_content 
  295. ); 
  296. break; 
  297.  
  298. return apply_filters( 
  299. 'ms_rule_media_model_protect_download_content',  
  300. $the_content,  
  301. $this 
  302. ); 
  303.  
  304. /** 
  305. * Extract filename and size extension info. 
  306. * 
  307. * @since 1.0.0 
  308. * 
  309. * @param string $file The filename to extract info from. 
  310. * @return array { 
  311. * @type string $filename The filename. 
  312. * @type string $size_extension The wordpress thumbnail size extension. Default empty. 
  313. * } 
  314. */ 
  315. public function extract_file_info( $file ) { 
  316. // See if the filename has a size extension and if so, strip it out. 
  317. $filename_exp_full = '/(.+)\-(\d+[x]\d+)\.(.+)$/'; 
  318. $filename_exp_min = '/(.+)\.(.+)$/'; 
  319. $filematch = array(); 
  320.  
  321. if ( preg_match( $filename_exp_full, $file, $filematch ) ) { 
  322. // Image with an image size attached. 
  323. $type = strtolower( $filematch[3] ); 
  324. $filename = $filematch[1] . '.' . $type; 
  325. $size_extension = '-' . $filematch[2]; 
  326. } elseif ( preg_match( $filename_exp_min, $file, $filematch ) ) { 
  327. // Image without an image size definition. 
  328. $type = strtolower( $filematch[2] ); 
  329. $filename = $filematch[1] . '.' . $type; 
  330. $size_extension = ''; 
  331. } else { 
  332. // Image without an extension. 
  333. $type = ''; 
  334. $filename = $file; 
  335. $size_extension = ''; 
  336.  
  337. $info = (object) array( 
  338. 'filename' => $filename,  
  339. 'size_extension' => $size_extension,  
  340. 'type' => $type,  
  341. ); 
  342.  
  343. return apply_filters( 
  344. 'ms_rule_media_model_extract_file_info',  
  345. $info,  
  346. $file,  
  347. $this 
  348. ); 
  349.  
  350. /** 
  351. * Get attachment post_id using the filename. 
  352. * 
  353. * @since 1.0.0 
  354. * 
  355. * @param string $url The URL to obtain the post_id. 
  356. * @return int The post ID or 0 if not found. 
  357. */ 
  358. public function get_attachment_id( $url ) { 
  359. static $Uploads_Url = null; 
  360. static $Uploads_Url_Len = 0; 
  361. global $wpdb; 
  362.  
  363. // First let WordPress try to find the Attachment ID. 
  364. $id = $this->thumbnail_url_to_id( $url ); 
  365.  
  366. if ( $id ) { 
  367. // Make sure the result ID is a valid attachment ID. 
  368. if ( 'attachment' != get_post_type( $id ) ) { 
  369. $id = 0; 
  370. } else { 
  371. // Manual attempt: Get the filename from the URL and use a custom query. 
  372. if ( null === $Uploads_Url ) { 
  373. $uploads = wp_upload_dir(); 
  374. $Uploads_Url = trailingslashit( $uploads['baseurl'] ); 
  375. $Uploads_Url_Len = strlen( $Uploads_Url ); 
  376.  
  377. if ( false !== strpos( $url, $Uploads_Url ) ) { 
  378. $url = substr( $url, $Uploads_Url_Len ); 
  379.  
  380. // See if we cached that URL already. 
  381. $id = wp_cache_get( $url, 'ms_attachment_id' ); 
  382.  
  383. if ( empty( $id ) ) { 
  384. $sql = " 
  385. SELECT wposts.ID 
  386. FROM $wpdb->posts as wposts 
  387. INNER JOIN $wpdb->postmeta as wpostmeta ON wposts.ID = wpostmeta.post_id 
  388. WHERE 
  389. wposts.post_type = 'attachment' 
  390. AND wpostmeta.meta_key = '_wp_attached_file' 
  391. AND wpostmeta.meta_value = %s 
  392. "; 
  393. $sql = $wpdb->prepare( $sql, $url ); 
  394. $id = $wpdb->get_var( $sql ); 
  395.  
  396. wp_cache_set( $url, $id, 'ms_attachment_id' ); 
  397.  
  398. return apply_filters( 
  399. 'ms_rule_get_attachment_id',  
  400. absint( $id ),  
  401. $url,  
  402. $this 
  403. ); 
  404.  
  405. /** 
  406. * Fetch ID of an image from the image URL. 
  407. * The URL can contain the size 
  408. * 
  409. * @see http://stackoverflow.com/questions/10990808/wordpress-unique-scenario-get-attachment-id-by-url 
  410. * @since 1.0.2.6 
  411. * @param string $file_url URL of an attachment. 
  412. * @return int The post-ID or 0 if no post found. 
  413. */ 
  414. public function thumbnail_url_to_id( $file_url ) { 
  415. global $wpdb; 
  416. $filename = basename( $file_url ); 
  417.  
  418. // TODO: This is the same code as used in function `get_attachment_id` above?? 
  419. $rows = $wpdb->get_results( 
  420. $wpdb->prepare( " 
  421. SELECT p.ID, m.meta_value 
  422. FROM {$wpdb->posts} p 
  423. INNER JOIN {$wpdb->postmeta} m ON p.ID = m.post_id 
  424. AND m.meta_key = '_wp_attachment_metadata' 
  425. AND m.meta_value LIKE %s 
  426. ",  
  427. '%"' . $filename . '"%' 
  428. ); 
  429.  
  430. foreach ( $rows as $row ) { 
  431. $row->meta_value = maybe_unserialize( $row->meta_value ); 
  432. return $row->ID; 
  433.  
  434. return 0; 
  435.  
  436. /** 
  437. * Handle protected media access. 
  438. * 
  439. * Search for masked file and show the proper content, or no access image if don't have access. 
  440. * 
  441. * Realted Action Hooks: 
  442. * - parse_request 
  443. * 
  444. * @since 1.0.0 
  445. * 
  446. * @param WP_Query $query The WP_Query object to filter. 
  447. */ 
  448. public function handle_download_protection( $query ) { 
  449. do_action( 
  450. 'ms_rule_media_model_handle_download_protection_before',  
  451. $query,  
  452. $this 
  453. ); 
  454.  
  455. $the_file = false; 
  456. $requested_item = false; 
  457. $download_settings = MS_Plugin::instance()->settings->downloads; 
  458. $protection_type = $download_settings['protection_type']; 
  459.  
  460. if ( ! MS_Model_Addon::is_enabled( MS_Model_Addon::ADDON_MEDIA ) ) { 
  461. return; 
  462.  
  463. if ( ! empty( $query->query_vars['protectedfile'] ) && self::PROTECTION_TYPE_COMPLETE == $protection_type ) { 
  464. $requested_item = explode( '/', $query->query_vars['protectedfile'] ); 
  465. $requested_item = array_pop( $requested_item ); 
  466. } elseif ( ! empty( $_GET['ms_file'] ) 
  467. && self::PROTECTION_TYPE_HYBRID == $protection_type 
  468. ) { 
  469. $requested_item = $_GET['ms_file']; 
  470. } else { 
  471. $requested_item = MS_Helper_Utility::get_current_url(); 
  472.  
  473. if ( ! empty( $requested_item ) ) { 
  474. // At this point we know that the requested post is an attachment. 
  475. $f_info = $this->extract_file_info( $requested_item ); 
  476.  
  477. switch ( $protection_type ) { 
  478. case self::PROTECTION_TYPE_COMPLETE: 
  479. case self::PROTECTION_TYPE_HYBRID: 
  480. // Work out the post_id again. 
  481. $attachment_id = preg_replace( 
  482. '/^' . self::FILE_PROTECTION_PREFIX . '/',  
  483. '',  
  484. $f_info->filename 
  485. ); 
  486.  
  487. $request_name = basename($attachment_id); //Get the name of the requested file 
  488. $request_name = pathinfo($request_name); //Get the info the of the requested file 
  489. $attachment_id = str_replace("ms_", "", $request_name['filename']); //Remove the prefix since we always have ms_ and get the attachment id 
  490.  
  491. $attachment_id = $attachment_id - (int) self::FILE_PROTECTION_INCREMENT; //I dont know why this was here 
  492.  
  493. $the_file = $this->restore_filename( $attachment_id, $f_info->size_extension ); 
  494. break; 
  495.  
  496. default: 
  497. case self::PROTECTION_TYPE_BASIC: 
  498. $upload_dir = wp_upload_dir(); 
  499. $original_url = $upload_dir['baseurl']; 
  500. $home = get_option( 'home' ); 
  501. $original_url = explode( $home, $original_url ); 
  502.  
  503. $furl = untrailingslashit( 
  504. str_replace( 
  505. '/' . $download_settings['masked_url'],  
  506. $original_url[1],  
  507. $requested_item 
  508. ); 
  509.  
  510. $home = untrailingslashit( get_option( 'home' ) ); 
  511. $attachment_id = $this->get_attachment_id( $furl ); 
  512. $the_file = $this->restore_filename( $attachment_id, $f_info->size_extension ); 
  513. break; 
  514.  
  515. if ( ! empty( $the_file ) 
  516. && ! empty( $attachment_id ) 
  517. && is_numeric( $attachment_id ) 
  518. ) { 
  519. if ( $this->can_access_file( $attachment_id ) ) { 
  520. $upload_dir = wp_upload_dir(); 
  521. $file = trailingslashit( $upload_dir['basedir'] ) . $the_file; 
  522. $this->output_file( $file ); 
  523. } else { 
  524. $this->show_no_access_image(); 
  525.  
  526. do_action( 
  527. 'ms_rule_media_model_handle_download_protection_after',  
  528. $query,  
  529. $this 
  530. ); 
  531.  
  532. /** 
  533. * Checks if the current user can access the specified attachment. 
  534. * 
  535. * @since 1.0.0 
  536. * @param int $attachment_id The attachment post-ID. 
  537. * @return bool 
  538. */ 
  539. public function can_access_file( $attachment_id ) { 
  540. $access = false; 
  541.  
  542. if ( MS_Model_Member::is_normal_admin() ) { 
  543. return true; 
  544.  
  545. if ( ! MS_Model_Addon::is_enabled( MS_Addon_Mediafiles::ID ) ) { 
  546. /** 
  547. * Default protection mode: 
  548. * Protect Attachments based on the parent post. 
  549. */ 
  550. $parent_id = get_post_field( 'post_parent', $attachment_id ); 
  551.  
  552. if ( ! $parent_id ) { 
  553. $access = true; 
  554. } else { 
  555. $member = MS_Model_Member::get_current_member(); 
  556. foreach ( $member->subscriptions as $subscription ) { 
  557. $membership = $subscription->get_membership(); 
  558. $access = $membership->has_access_to_post( $parent_id ); 
  559. if ( $access ) { break; } 
  560. } else { 
  561. /** 
  562. * Advanced protection mode (via Add-on): 
  563. * Each Attachment can be protected individually. 
  564. */ 
  565. $member = MS_Model_Member::get_current_member(); 
  566. foreach ( $member->subscriptions as $subscription ) { 
  567. $rule = $subscription->get_membership()->get_rule( MS_Rule_Media::RULE_ID ); 
  568. $access = $rule->has_access( $attachment_id ); 
  569. if ( $access ) { break; } 
  570.  
  571. return apply_filters( 
  572. 'ms_rule_media_can_access_file',  
  573. $access,  
  574. $attachment_id 
  575. ); 
  576.  
  577. /** 
  578. * Restore filename from post_id. 
  579. * 
  580. * @since 1.0.0 
  581. * 
  582. * @todo refactory hack to get extension. 
  583. * 
  584. * @param int $post_id The attachment post_id. 
  585. * @param string $size_extension The image size extension. 
  586. * @return string The attachment filename. 
  587. */ 
  588. public function restore_filename( $post_id, $size_extension ) { 
  589. $img_filename = null; 
  590.  
  591. if ( ! empty( $post_id ) && is_numeric( $post_id ) ) { 
  592. $img_filename = get_post_meta( $post_id, '_wp_attached_file', true ); 
  593. if ( ! empty( $size_extension ) ) { 
  594. // Add back in a size extension if we need to. 
  595. $img_filename = str_replace( 
  596. '.' . pathinfo( $img_filename, PATHINFO_EXTENSION ),  
  597. $size_extension . '.' . pathinfo( $img_filename, PATHINFO_EXTENSION ),  
  598. $img_filename 
  599. ); 
  600.  
  601. // Hack to remove any double extensions :/ need to change when work out a neater way. 
  602. $img_filename = str_replace( 
  603. $size_extension . $size_extension,  
  604. $size_extension,  
  605. $img_filename 
  606. ); 
  607.  
  608. return apply_filters( 
  609. 'ms_rule_restore_filename',  
  610. $img_filename,  
  611. $post_id,  
  612. $this 
  613. ); 
  614.  
  615. /** 
  616. * Output file to the browser. 
  617. * 
  618. * @since 1.0.0 
  619. * 
  620. * @param string $file The complete path to the file. 
  621. */ 
  622. private function output_file( $file ) { 
  623. do_action( 'ms_rule_media_model_output_file_before', $file, $this ); 
  624.  
  625. if ( ! is_file( $file ) ) { 
  626. status_header( 404 ); 
  627. die( '404 — File not found.' ); 
  628.  
  629. $mime = wp_check_filetype( $file ); 
  630. if ( empty( $mime['type'] ) && function_exists( 'mime_content_type' ) ) { 
  631. $mime['type'] = mime_content_type( $file ); 
  632.  
  633. if ( $mime['type'] ) { 
  634. $mimetype = $mime['type']; 
  635. } else { 
  636. $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 ); 
  637.  
  638. header( 'Content-type: ' . $mimetype ); 
  639. if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) ) { 
  640. header( 'Content-Length: ' . filesize( $file ) ); 
  641.  
  642. if( ! defined( 'M2_MEDIA_ETAG_DISABLED' ) ) 
  643. if( ! defined( 'M2_MEDIA_ETAG' ) ) define( 'M2_MEDIA_ETAG', 'm2_media_addon_etag' ); 
  644.  
  645. $last_modified = date_i18n( 'D, d M Y H:i:s', filemtime( $file ) ); 
  646. $etag = '"' . md5( $last_modified ) . '"'; 
  647. header( "Last-Modified: $last_modified GMT" ); 
  648. header( 'ETag: ' . $etag ); 
  649. header( 'Expires: ' . date_i18n( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' ); 
  650.  
  651. // Support for Conditional GET. 
  652. if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { 
  653. $client_etag = stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ); 
  654. } else { 
  655. $client_etag = false; 
  656.  
  657. if ( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { 
  658. $_SERVER['HTTP_IF_MODIFIED_SINCE'] = false; 
  659.  
  660. $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); 
  661. // If string is empty, return 0. If not, attempt to parse into a timestamp. 
  662. $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0; 
  663.  
  664. // Make a timestamp for our most recent modification... 
  665. $modified_timestamp = strtotime( $last_modified ); 
  666.  
  667. if ( $client_last_modified && $client_etag ) { 
  668. $valid_etag = ( $client_modified_timestamp >= $modified_timestamp ) 
  669. && ( $client_etag === $etag ); 
  670. } else { 
  671. $valid_etag = ( $client_modified_timestamp >= $modified_timestamp ) 
  672. || ( $client_etag === $etag ); 
  673.  
  674. /**if ( $valid_etag ) { 
  675. status_header( 304 ); 
  676. exit; 
  677. }*/ 
  678.  
  679. // If we made it this far, just serve the file. 
  680. readfile( $file ); 
  681.  
  682. do_action( 'ms_rule_media_model_output_file_after', $file, $this ); 
  683.  
  684. die(); 
  685.  
  686. /** 
  687. * Show no access image. 
  688. * 
  689. * @since 1.0.0 
  690. */ 
  691. private function show_no_access_image() { 
  692. $no_access_file = apply_filters( 
  693. 'ms_rule_media_model_show_no_access_image_path',  
  694. MS_Plugin::instance()->dir . 'app/assets/images/no-access.png' 
  695. ); 
  696.  
  697. $this->output_file( $no_access_file ); 
  698.  
  699. do_action( 'ms_rule_show_no_access_image_after', $this ); 
  700.  
  701. /** 
  702. * Get content to protect. 
  703. * 
  704. * @since 1.0.0 
  705. * @param array $args The query post args. 
  706. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  707. * @return array The contents array. 
  708. */ 
  709. public function get_contents( $args = null ) { 
  710. $args = self::get_query_args( $args ); 
  711. $posts = get_posts( $args ); 
  712.  
  713. $contents = array(); 
  714. foreach ( $posts as $content ) { 
  715. $content->id = $content->ID; 
  716. $content->type = MS_Rule_Media::RULE_ID; 
  717. $content->name = $content->post_name; 
  718. $content->access = $this->can_access_file( $content->id ); 
  719.  
  720. $contents[ $content->id ] = $content; 
  721.  
  722. return apply_filters( 
  723. 'ms_rule_media_model_get_contents',  
  724. $contents,  
  725. $args,  
  726. $this 
  727. ); 
  728.  
  729. /** 
  730. * Get the total content count. 
  731. * 
  732. * @since 1.0.0 
  733. * 
  734. * @param array $args The query post args. 
  735. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  736. * @return int The total content count. 
  737. */ 
  738. public function get_content_count( $args = null ) { 
  739. $args = self::get_query_args( $args ); 
  740. $query = new WP_Query( $args ); 
  741.  
  742. $count = $query->found_posts; 
  743.  
  744. return apply_filters( 
  745. 'ms_rule_media_model_get_content_count',  
  746. $count,  
  747. $args 
  748. ); 
  749.  
  750. /** 
  751. * Get the default query args. 
  752. * 
  753. * @since 1.0.0 
  754. * 
  755. * @param string $args The query post args. 
  756. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  757. * @return array The parsed args. 
  758. */ 
  759. public function get_query_args( $args = null ) { 
  760. $defaults = array( 
  761. 'orderby' => 'post_date',  
  762. 'order' => 'DESC',  
  763. 'post_type' => 'attachment',  
  764. 'post_status' => 'any',  
  765. ); 
  766. $args = wp_parse_args( $args, $defaults ); 
  767.  
  768. return parent::prepare_query_args( $args, 'get_posts' ); 
.