BP_Docs_Attachments

The BuddyPress Docs BP Docs Attachments class.

Defined (1)

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

/includes/attachments.php  
  1. class BP_Docs_Attachments { 
  2. protected $doc_id; 
  3. protected $override_doc_id; 
  4.  
  5. protected $is_private; 
  6. protected $htaccess_path; 
  7.  
  8. function __construct() { 
  9. if ( ! bp_docs_enable_attachments() ) { 
  10. return; 
  11.  
  12. add_action( 'template_redirect', array( $this, 'catch_attachment_request' ), 20 ); 
  13. add_filter( 'redirect_canonical', array( $this, 'redirect_canonical' ), 10, 2 ); 
  14. add_filter( 'upload_dir', array( $this, 'filter_upload_dir' ) ); 
  15. add_action( 'bp_docs_doc_saved', array( $this, 'check_privacy' ) ); 
  16. add_filter( 'wp_handle_upload_prefilter', array( $this, 'maybe_create_rewrites' ) ); 
  17. add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ), 20 ); 
  18.  
  19. add_action( 'pre_get_posts', array( $this, 'filter_gallery_posts' ) ); 
  20. add_action( 'pre_get_posts', array( $this, 'filter_directory_posts' ), 48 ); 
  21.  
  22. // Add the tags filter markup 
  23. add_filter( 'bp_docs_filter_types', array( $this, 'filter_type' ) ); 
  24. add_filter( 'bp_docs_filter_sections', array( $this, 'filter_markup' ) ); 
  25.  
  26. // Determine whether the directory view is filtered by 'has-attachment' status. 
  27. add_filter( 'bp_docs_is_directory_view_filtered', array( $this, 'is_directory_view_filtered' ), 10, 2 ); 
  28.  
  29. // Icon display 
  30. add_filter( 'icon_dir', 'BP_Docs_Attachments::icon_dir' ); 
  31. add_filter( 'icon_dir_uri', 'BP_Docs_Attachments::icon_dir_uri' ); 
  32.  
  33. // Catch delete request 
  34. add_action( 'bp_actions', array( $this, 'catch_delete_request' ) ); 
  35.  
  36. // Ensure that all logged-in users have the 'upload_files' cap 
  37. add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_cap' ), 10, 4 ); 
  38.  
  39. // Admin notice about directory accessibility 
  40. add_action( 'admin_init', array( $this, 'admin_notice_init' ) ); 
  41.  
  42. require( dirname( __FILE__ ) . '/attachments-ajax.php' ); 
  43.  
  44. /** 
  45. * @todo 
  46. * - Must have script for recreating .htaccess files when: 
  47. * - top-level Docs slug changes 
  48. * - single Doc slug changes 
  49. */ 
  50.  
  51. /** 
  52. * Catches bp-attachment requests and serves attachmens if appropriate 
  53. * @since 1.4 
  54. */ 
  55. function catch_attachment_request() { 
  56. if ( ! empty( $_GET['bp-attachment'] ) ) { 
  57.  
  58. $fn = basename( $_GET['bp-attachment'] ); 
  59.  
  60. // Sanity check - don't do anything if this is not a Doc 
  61. if ( ! bp_docs_is_existing_doc() ) { 
  62. return; 
  63.  
  64. if ( ! $this->filename_is_safe( $fn ) ) { 
  65. wp_die( __( 'File not found.', 'bp-docs' ) ); 
  66.  
  67. $uploads = wp_upload_dir(); 
  68. $filepath = $uploads['path'] . DIRECTORY_SEPARATOR . $fn; 
  69.  
  70. if ( ! file_exists( $filepath ) ) { 
  71. wp_die( __( 'File not found.', 'bp-docs' ) ); 
  72.  
  73. $headers = $this->generate_headers( $filepath ); 
  74.  
  75. // @todo Support xsendfile? 
  76. // @todo Better to send header('Location') instead? 
  77. // Generate symlinks like Drupal. Needs FollowSymLinks 
  78. foreach( $headers as $name => $field_value ) { 
  79. @header("{$name}: {$field_value}"); 
  80.  
  81. ob_end_clean(); 
  82. readfile( $filepath ); 
  83. exit(); 
  84.  
  85. /** 
  86. * If redirecting from a 'p' URL to a rewritten URL, retain 'bp-attachment' param 
  87. * @since 1.6.0 
  88. * @param string $redirect_url URL as calculated by redirect_canonical 
  89. * @param string $requested_url Originally requested URL. 
  90. * @return string 
  91. */ 
  92. public function redirect_canonical( $redirect_url, $requested_url ) { 
  93. if ( isset( $_GET['p'] ) && isset( $_GET['bp-attachment'] ) && false !== strpos( $requested_url, 'bp-attachments' ) ) { 
  94. $redirect_url = add_query_arg( 'bp-attachment', $_GET['bp-attachment'], $redirect_url ); 
  95.  
  96. return $redirect_url; 
  97.  
  98. /** 
  99. * Attempts to customize upload_dir with our attachment paths 
  100. * @todo There's a quirk in wp_upload_dir() that will create the 
  101. * attachment directory if it doesn't already exist. This normally 
  102. * isn't a problem, because wp_upload_dir() is generally only called 
  103. * when a credentialed user is logged in and viewing the admin side. 
  104. * But the current code will force a folder to be created the first 
  105. * time the doc is viewed at all. Not sure whether this merits fixing 
  106. * @since 1.4 
  107. */ 
  108. function filter_upload_dir( $uploads ) { 
  109. if ( ! $this->get_doc_id() ) { 
  110. return $uploads; 
  111.  
  112. $uploads = $this->mod_upload_dir( $uploads ); 
  113.  
  114. return $uploads; 
  115.  
  116. /** 
  117. * After a Doc is saved, check to see whether any action is necessary 
  118. * @since 1.4 
  119. */ 
  120. public function check_privacy( $query ) { 
  121. if ( empty( $query->doc_id ) ) { 
  122. return; 
  123.  
  124. $this->set_doc_id( $query->doc_id ); 
  125.  
  126. if ( $this->get_is_private() ) { 
  127. $this->create_htaccess(); 
  128. } else { 
  129. $this->delete_htaccess(); 
  130.  
  131. /** 
  132. * Delete an htaccess file for the current Doc 
  133. * @since 1.4 
  134. * @return bool 
  135. */ 
  136. public function delete_htaccess() { 
  137. if ( ! $this->get_doc_id() ) { 
  138. return false; 
  139.  
  140. $path = $this->get_htaccess_path(); 
  141. if ( file_exists( $path ) ) { 
  142. unlink( $path ); 
  143.  
  144. return true; 
  145.  
  146. /** 
  147. * Create rewrite rules for upload directory, if appropriate. 
  148. * As a hack, we've hooked to wp_handle_upload_prefilter. We don't 
  149. * actually do anything with the passed value; we just need a place 
  150. * to hook in reliably before the file is written. 
  151. * @since 1.6.0 
  152. * @param $file 
  153. * @return $file 
  154. */ 
  155. public function maybe_create_rewrites( $file ) { 
  156. global $is_apache; 
  157.  
  158. if ( ! $this->get_doc_id() ) { 
  159. return $file; 
  160.  
  161. if ( ! $this->get_is_private() ) { 
  162. return $file; 
  163.  
  164. if ( $is_apache ) { 
  165. $this->create_htaccess(); 
  166.  
  167. return $file; 
  168.  
  169. /** 
  170. * Creates an .htaccess file in the appropriate upload dir, if appropriate 
  171. * @since 1.4 
  172. * @param $file 
  173. * @return $file 
  174. */ 
  175. public function maybe_create_htaccess( $file ) { 
  176. if ( ! $this->get_doc_id() ) { 
  177. return $file; 
  178.  
  179. if ( ! $this->get_is_private() ) { 
  180. return $file; 
  181.  
  182. $this->create_htaccess(); 
  183.  
  184. return $file; 
  185.  
  186. /** 
  187. * Creates an .htaccess for a Doc upload directory 
  188. * No check happens here to see whether an .htaccess is necessary. Make 
  189. * sure you check $this->get_is_private() before running. 
  190. * @since 1.4 
  191. */ 
  192. public function create_htaccess() { 
  193. $htaccess_path = $this->get_htaccess_path(); 
  194.  
  195. $rules = $this->generate_rewrite_rules(); 
  196.  
  197. if ( ! empty( $rules ) ) { 
  198. if ( ! function_exists( 'insert_with_markers' ) ) { 
  199. require_once( ABSPATH . 'wp-admin/includes/misc.php' ); 
  200. insert_with_markers( $htaccess_path, 'BuddyPress Docs', $rules ); 
  201.  
  202. /** 
  203. * Generates a path to htaccess for the Doc in question 
  204. * @since 1.4 
  205. * @return string 
  206. */ 
  207. public function get_htaccess_path() { 
  208. $upload_dir = wp_upload_dir( null, true, true ); 
  209. $this->htaccess_path = $upload_dir['path'] . DIRECTORY_SEPARATOR . '.htaccess'; 
  210.  
  211. return $this->htaccess_path; 
  212.  
  213. /** 
  214. * Check whether the current Doc is private ('read' != 'anyone') 
  215. * @since 1.4 
  216. * @return bool 
  217. */ 
  218. public function get_is_private() { 
  219. // if ( is_null( $this->is_private ) ) { 
  220. $doc_id = $this->get_doc_id(); 
  221. $doc_settings = bp_docs_get_doc_settings( $doc_id ); 
  222. $this->is_private = isset( $doc_settings['read'] ) && 'anyone' !== $doc_settings['read']; 
  223. // } 
  224.  
  225. return $this->is_private; 
  226.  
  227. /** 
  228. * Set a doc id for this object 
  229. * This is a hack that lets you manually bypass the doc sniffing in 
  230. * get_doc_id() 
  231. */ 
  232. public function set_doc_id( $doc_id ) { 
  233. $this->override_doc_id = intval( $doc_id ); 
  234.  
  235. /** 
  236. * Attempt to auto-detect a doc ID 
  237. */ 
  238. public function get_doc_id() { 
  239.  
  240. if ( ! empty( $this->override_doc_id ) ) { 
  241.  
  242. $this->doc_id = $this->override_doc_id; 
  243.  
  244. } else { 
  245.  
  246. if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { 
  247. if ( bp_docs_is_existing_doc() ) { 
  248. $this->doc_id = get_queried_object_id(); 
  249. } else { 
  250. // AJAX 
  251. if ( isset( $_REQUEST['query']['auto_draft_id'] ) ) { 
  252.  
  253. $this->doc_id = (int) $_REQUEST['query']['auto_draft_id']; 
  254.  
  255. } else if ( isset( $_REQUEST['action'] ) && 'upload-attachment' == $_REQUEST['action'] && isset( $_REQUEST['post_id'] ) ) { 
  256.  
  257. $maybe_doc = get_post( $_REQUEST['post_id'] ); 
  258. if ( bp_docs_get_post_type_name() == $maybe_doc->post_type ) { 
  259. $this->doc_id = $maybe_doc->ID; 
  260.  
  261. } else { 
  262. // In order to check if this is a doc, must check ajax referer 
  263. $this->doc_id = $this->get_doc_id_from_url( wp_get_referer() ); 
  264.  
  265. return $this->doc_id; 
  266.  
  267. public function catch_delete_request() { 
  268. if ( ! bp_docs_is_existing_doc() ) { 
  269. return; 
  270.  
  271. if ( ! isset( $_GET['delete_attachment'] ) ) { 
  272. return; 
  273.  
  274. if ( ! current_user_can( 'bp_docs_edit' ) ) { 
  275. return; 
  276.  
  277. $attachment_id = intval( $_GET['delete_attachment'] ); 
  278.  
  279. check_admin_referer( 'bp_docs_delete_attachment_' . $attachment_id ); 
  280.  
  281. if ( wp_delete_attachment( $attachment_id ) ) { 
  282. bp_core_add_message( __( 'Attachment deleted', 'bp-docs' ) ); 
  283. } else { 
  284. bp_core_add_message( __( 'Could not delete attachment', 'bp-docs' ), 'error' ); 
  285.  
  286. wp_redirect( wp_get_referer() ); 
  287.  
  288. /** 
  289. * Get a Doc's ID from its URL 
  290. * Note that this only works for existing Docs, because (duh) new Docs 
  291. * don't yet have a URL 
  292. * @since 1.4 
  293. * @param string $url 
  294. * @return int $doc_id 
  295. */ 
  296. public static function get_doc_id_from_url( $url ) { 
  297. $doc_id = null; 
  298. $url = untrailingslashit( $url ); 
  299. $edit_location = strrpos( $url, BP_DOCS_EDIT_SLUG ); 
  300. if ( false !== $edit_location && BP_DOCS_EDIT_SLUG == substr( $url, $edit_location ) ) { 
  301. $doc_id = url_to_postid( substr( $url, 0, $edit_location - 1 ) ); 
  302. return $doc_id; 
  303.  
  304. /** 
  305. * Filter upload_dir to customize Doc upload locations 
  306. * @since 1.4 
  307. * @return array $uploads 
  308. */ 
  309. function mod_upload_dir( $uploads ) { 
  310. $subdir = '/bp-attachments/' . $this->doc_id; 
  311.  
  312. $uploads['subdir'] = $subdir; 
  313. $uploads['path'] = $uploads['basedir'] . $subdir; 
  314. $uploads['url'] = $uploads['baseurl'] . '/bp-attachments/' . $this->doc_id; 
  315.  
  316. return $uploads; 
  317.  
  318. function enqueue_scripts() { 
  319. if ( bp_docs_is_doc_edit() || bp_docs_is_doc_create() ) { 
  320. wp_enqueue_script( 'bp-docs-attachments', plugins_url( BP_DOCS_PLUGIN_SLUG . '/includes/js/attachments.js' ), array( 'media-editor', 'media-views', 'bp-docs-js' ), false, true ); 
  321.  
  322. wp_localize_script( 'bp-docs-attachments', 'bp_docs_attachments', array( 
  323. 'upload_title' => __( 'Upload File', 'bp-docs' ),  
  324. 'upload_button' => __( 'OK', 'bp-docs' ),  
  325. ) ); 
  326.  
  327. /** 
  328. * Filter the posts query on attachment pages, to ensure that only the 
  329. * specific Doc's attachments show up in the Gallery 
  330. * Hooked to 'pre_get_posts'. Bail out if we're not in a Gallery 
  331. * request for a Doc 
  332. * @since 1.4 
  333. */ 
  334. public function filter_gallery_posts( $query ) { 
  335. // This is also a surrogate check for DOING_AJAX 
  336. if ( ! isset( $_POST['action'] ) || 'query-attachments' !== $_POST['action'] ) { 
  337. return; 
  338.  
  339. $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; 
  340.  
  341. if ( ! $post_id ) { 
  342. return; 
  343.  
  344. $post = get_post( $post_id ); 
  345.  
  346. if ( empty( $post ) || is_wp_error( $post ) || bp_docs_get_post_type_name() !== $post->post_type ) { 
  347. return; 
  348.  
  349. // Phew 
  350. $query->set( 'post_parent', $_REQUEST['post_id'] ); 
  351.  
  352. public function filter_directory_posts( $query ) { 
  353. if ( bp_docs_get_post_type_name() !== $query->get( 'post_type' ) ) { 
  354. return; 
  355.  
  356. remove_action( 'pre_get_posts', array( $this, 'filter_directory_posts' ), 48 ); 
  357.  
  358. $has_attachment = isset( $_REQUEST['has-attachment'] ) && in_array( $_REQUEST['has-attachment'], array( 'yes', 'no' ) ) ? $_REQUEST['has-attachment'] : ''; 
  359.  
  360. if ( $has_attachment ) { 
  361. $post__in = $query->get( 'post__in' ); 
  362. $att_posts = $this->get_docs_with_attachments(); 
  363. $att_posts = empty( $att_posts ) ? array( 0 ) : $att_posts; 
  364. $query_arg = 'yes' === $has_attachment ? 'post__in' : 'post__not_in'; 
  365. $query->set( $query_arg, array_merge( (array) $post__in, (array) $att_posts ) ); 
  366.  
  367. add_action( 'pre_get_posts', array( $this, 'filter_directory_posts' ), 48 ); 
  368.  
  369. public function get_docs_with_attachments() { 
  370. $atts = get_posts( array( 
  371. 'update_meta_cache' => false,  
  372. 'update_term_cache' => false,  
  373. 'post_type' => 'attachment',  
  374. 'post_parent__in' => bp_docs_get_doc_ids_accessible_to_current_user(),  
  375. 'posts_per_page' => -1,  
  376. ) ); 
  377.  
  378. return array_unique( wp_list_pluck( $atts, 'post_parent' ) ); 
  379.  
  380. /** 
  381. * Generates the rewrite rules to be put in .htaccess of the upload dir 
  382. * @since 1.4 
  383. * @return array $rules One per line, to be put together by insert_with_markers() 
  384. */ 
  385. public function generate_rewrite_rules() { 
  386. $rules = array(); 
  387. $doc_id = $this->get_doc_id(); 
  388.  
  389. if ( ! $doc_id ) { 
  390. return $rules; 
  391.  
  392. $url = bp_docs_get_doc_link( $doc_id ); 
  393. $url_parts = parse_url( $url ); 
  394.  
  395. if ( ! empty( $url_parts['path'] ) ) { 
  396. $rules = array( 
  397. 'RewriteEngine On',  
  398. 'RewriteBase ' . $url_parts['path'],  
  399. 'RewriteRule (.+) ?bp-attachment=$1 [R=302, NC]',  
  400. ); 
  401.  
  402. return apply_filters( 'bp_docs_attachments_generate_rewrite_rules', $rules, $this ); 
  403.  
  404. /** 
  405. * Check to see whether a filename is safe 
  406. * This is used to sanitize file paths passed via $_GET params 
  407. * @since 1.4 
  408. * @param string $filename Filename to validate 
  409. * @return bool 
  410. */ 
  411. public static function filename_is_safe( $filename ) { 
  412. // WP's core function handles most sanitization 
  413. if ( $filename !== sanitize_file_name( $filename ) ) { 
  414. return false; 
  415.  
  416. // No leading dots 
  417. if ( 0 === strpos( $filename, '.' ) ) { 
  418. return false; 
  419.  
  420. // No directory walking means no slashes 
  421. $filename_parts = pathinfo( $filename ); 
  422. if ( $filename_parts['basename'] !== $filename ) { 
  423. return false; 
  424.  
  425. // Check filetype 
  426. $ft = wp_check_filetype( $filename ); 
  427. if ( empty( $ft['ext'] ) ) { 
  428. return false; 
  429.  
  430. return true; 
  431.  
  432. /** 
  433. * Generate download headers 
  434. * @since 1.4 
  435. * @param string $filename Full path to file 
  436. * @return array Headers in key=>value format 
  437. */ 
  438. public static function generate_headers( $filename ) { 
  439. // Disable compression 
  440. if ( function_exists( 'apache_setenv' ) ) { 
  441. @apache_setenv( 'no-gzip', 1 ); 
  442. @ini_set( 'zlib.output_compression', 'Off' ); 
  443.  
  444. // @todo Make this more configurable 
  445. $headers = wp_get_nocache_headers(); 
  446.  
  447. // Content-Disposition 
  448. $filename_parts = pathinfo( $filename ); 
  449. $headers['Content-Disposition'] = 'attachment; filename="' . $filename_parts['basename'] . '"'; 
  450.  
  451. // Content-Type 
  452. $filetype = wp_check_filetype( $filename ); 
  453. $headers['Content-Type'] = $filetype['type']; 
  454.  
  455. // Content-Length 
  456. $filesize = filesize( $filename ); 
  457. $headers['Content-Length'] = $filesize; 
  458.  
  459. return $headers; 
  460.  
  461. public static function icon_dir( $dir ) { 
  462. if ( bp_docs_is_docs_component() ) { 
  463. $dir = BP_DOCS_INSTALL_PATH . 'lib/nuvola'; 
  464. return $dir; 
  465.  
  466. public static function icon_dir_uri( $url ) { 
  467. if ( bp_docs_is_docs_component() ) { 
  468. $url = plugins_url( BP_DOCS_PLUGIN_SLUG . '/lib/nuvola' ); 
  469. return $url; 
  470.  
  471. public static function filter_type( $types ) { 
  472. $types[] = array( 
  473. 'slug' => 'attachments',  
  474. 'title' => __( 'Attachments', 'bp-docs' ),  
  475. 'query_arg' => 'has-attachment',  
  476. ); 
  477. return $types; 
  478.  
  479. public static function filter_markup() { 
  480. $has_attachment = isset( $_REQUEST['has-attachment'] ) && in_array( $_REQUEST['has-attachment'], array( 'yes', 'no' ) ) ? $_REQUEST['has-attachment'] : ''; 
  481. $form_action = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; 
  482. foreach ( $_GET as $k => $v ) { 
  483. $form_action = remove_query_arg( $k, $form_action ); 
  484. ?> 
  485.  
  486. <div id="docs-filter-section-attachments" class="docs-filter-section<?php if ( $has_attachment ) : ?> docs-filter-section-open<?php endif ?>"> 
  487. <form method="get" action="<?php echo $form_action ?>"> 
  488. <label for="has-attachment"><?php _e( 'Has attachment?', 'bp-docs' ) ?></label> 
  489. <select id="has-attachment" name="has-attachment"> 
  490. <option value="yes"<?php selected( $has_attachment, 'yes' ) ?>><?php _e( 'Yes', 'bp-docs' ) ?></option> 
  491. <option value="no"<?php selected( $has_attachment, 'no' ) ?>><?php _e( 'No', 'bp-docs' ) ?></option> 
  492. <option value=""<?php selected( $has_attachment, '' ) ?>><?php _e( 'Doesn’t matter', 'bp-docs' ) ?></option> 
  493. </select> 
  494. <input type="submit" value="<?php _e( 'Filter', 'bp-docs' ) ?>" /> 
  495. <?php do_action( 'bp_docs_directory_filter_attachments_form' ) ?> 
  496. </form> 
  497. </div> 
  498.  
  499. <?php 
  500.  
  501.  
  502. /** 
  503. * Determine whether the directory view is filtered by 'has-attachment' status. 
  504. * @since 1.9.0 
  505. * @param bool $is_filtered Is the current directory view filtered? 
  506. * @param array $exclude Array of filter types to ignore. 
  507. * @return bool $is_filtered 
  508. */ 
  509. public function is_directory_view_filtered( $is_filtered, $exclude ) { 
  510. // If this filter is excluded, stop now. 
  511. if ( in_array( 'has-attachment', $exclude ) ) { 
  512. return $is_filtered; 
  513.  
  514. if ( isset( $_GET['has-attachment'] ) && ( 'yes' == $_GET['has-attachment'] || 'no' == $_GET['has-attachment'] ) ) { 
  515. $is_filtered = true; 
  516. return $is_filtered; 
  517.  
  518. /** 
  519. * Give users the 'edit_post' and 'upload_files' cap, when appropriate 
  520. * @since 1.4 
  521. * @param array $caps The mapped caps 
  522. * @param string $cap The cap being mapped 
  523. * @param int $user_id The user id in question 
  524. * @param $args 
  525. * @return array $caps 
  526. */ 
  527. public static function map_meta_cap( $caps, $cap, $user_id, $args ) { 
  528. if ( 'upload_files' !== $cap && 'edit_post' !== $cap ) { 
  529. return $caps; 
  530.  
  531. $maybe_user = new WP_User( $user_id ); 
  532. if ( ! is_a( $maybe_user, 'WP_User' ) || empty( $maybe_user->ID ) ) { 
  533. return $caps; 
  534.  
  535. $is_doc = false; 
  536.  
  537. // DOING_AJAX is not set yet, so we cheat 
  538. $is_ajax = isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] && 'async-upload.php' === substr( $_SERVER['REQUEST_URI'], strrpos( $_SERVER['REQUEST_URI'], '/' ) + 1 ); 
  539.  
  540. if ( $is_ajax ) { 
  541. // WordPress sends the 'media-form' nonce, which we use 
  542. // as an initial screen 
  543. $nonce = isset( $_REQUEST['_wpnonce'] ) ? stripslashes( $_REQUEST['_wpnonce'] ) : ''; 
  544. $post_id = isset( $_REQUEST['post_id'] ) ? intval( $_REQUEST['post_id'] ) : ''; 
  545.  
  546. if ( wp_verify_nonce( $nonce, 'media-form' ) && $post_id ) { 
  547. $post = get_post( $post_id ); 
  548.  
  549. // The dummy Doc created during the Create 
  550. // process should pass this test, in addition to 
  551. // existing Docs 
  552. $is_doc = isset( $post->post_type ) && bp_docs_get_post_type_name() === $post->post_type; 
  553. } else { 
  554. $is_doc = bp_docs_is_existing_doc() || bp_docs_is_doc_create(); 
  555.  
  556. if ( $is_doc ) { 
  557. $caps = array( 'exist' ); 
  558.  
  559. // Since we've already done the permissions check,  
  560. // we can filter future current_user_can() checks on 
  561. // this pageload 
  562. add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_cap_supp' ), 10, 4 ); 
  563.  
  564. return $caps; 
  565.  
  566. /** 
  567. * Make sure the current user has the 'edit_post' and 'upload_files' caps, when appropriate 
  568. * We do the necessary permissions checks in self::map_meta_cap(). If 
  569. * the checks pass, then we can blindly hook this filter without doing 
  570. * the permissions logic again. *Do not call this filter directly.* I'd 
  571. * mark it private but it's not possible given the way that WP's filter 
  572. * system works. 
  573. * @since 1.4 
  574. */ 
  575. public static function map_meta_cap_supp( $caps, $cap, $user_id, $args ) { 
  576. if ( 'upload_files' !== $cap && 'edit_post' !== $cap ) { 
  577. return $caps; 
  578.  
  579. return array( 'exist' ); 
  580.  
  581. public function admin_notice_init() { 
  582. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 
  583. return; 
  584.  
  585. if ( ! current_user_can( 'delete_users' ) ) { 
  586. return; 
  587.  
  588. // If the notice is being disabled, mark it as such and bail 
  589. if ( isset( $_GET['bpdocs-disable-attachment-notice'] ) ) { 
  590. check_admin_referer( 'bpdocs-disable-attachment-notice' ); 
  591. bp_update_option( 'bp_docs_disable_attachment_notice', 1 ); 
  592. return; 
  593.  
  594. // If the notice has already been disabled, bail 
  595. if ( 1 == bp_get_option( 'bp_docs_disable_attachment_notice' ) ) { 
  596. return; 
  597.  
  598. // Nothing to see here 
  599. if ( $this->check_is_protected() ) { 
  600. return; 
  601.  
  602. add_action( 'admin_notices', array( $this, 'admin_notice' ) ); 
  603. add_action( 'network_admin_notices', array( $this, 'admin_notice' ) ); 
  604.  
  605. public function admin_notice() { 
  606. global $is_apache, $is_nginx, $is_IIS, $is_iis7; 
  607.  
  608. $dismiss_url = add_query_arg( 'bpdocs-disable-attachment-notice', '1', $_SERVER['REQUEST_URI'] ); 
  609. $dismiss_url = wp_nonce_url( $dismiss_url, 'bpdocs-disable-attachment-notice' ); 
  610.  
  611. if ( ! bp_is_root_blog() ) { 
  612. switch_to_blog( bp_get_root_blog_id() ); 
  613.  
  614. $upload_dir = $this->mod_upload_dir( wp_upload_dir() ); 
  615. $att_url = str_replace( get_option( 'home' ), '', $upload_dir['url'] ); 
  616.  
  617. restore_current_blog(); 
  618.  
  619. if ( $is_nginx ) { 
  620. $help_url = 'https://github.com/boonebgorges/buddypress-docs/wiki/Attachment-Privacy#nginx'; 
  621.  
  622. $help_p = __( 'It looks like you are running <strong>nginx</strong>. We recommend the following setting in your site configuration file:', 'bp-docs' ); 
  623. $help_p .= '<pre><code>location ' . $att_url . ' { 
  624. rewrite ^.*' . str_replace( '/wp-content/', '', $att_url ) . '([0-9]+)/(.*) /?p=$1&bp-attachment=$2 permanent; 
  625. </code></pre>'; 
  626.  
  627. if ( $is_iis7 ) { 
  628. $help_url = 'https://github.com/boonebgorges/buddypress-docs/wiki/Attachment-Privacy#iis7'; 
  629.  
  630. $help_p = __( 'It looks like you are running <strong>IIS 7</strong>. We recommend the following setting in your Web.config file:', 'bp-docs' ); 
  631. $help_p .= '<pre><code><rule name="buddypress-docs-attachments"> 
  632. <match url="^' . $att_url . '([0-9]+)/(.*)$"/> 
  633. <conditions> 
  634. <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="false"/> 
  635. </conditions> 
  636. <action type="Redirect" url="?p={R:1}&bp-attachment={R:2}"/> 
  637. </rule> </code></pre>'; 
  638.  
  639. if ( $is_apache ) { 
  640. $help_url = 'https://github.com/boonebgorges/buddypress-docs/wiki/Attachment-Privacy#apache'; 
  641. $help_p = __( 'It looks like you are running <strong>Apache</strong>. The most likely cause of your problem is that the <code>AllowOverride</code> directive has been disabled, either globally (<code>httpd.conf</code>) or in a <code>VirtualHost</code> definition. Contact your host for assistance.', 'bp-docs' ); 
  642.  
  643. ?> 
  644.  
  645. <div class="message error"> 
  646. <p><?php _e( '<strong>Your BuddyPress Docs attachments directory is publicly accessible.</strong> Doc attachments will not be properly protected from direct viewing, even if the parent Docs are non-public.', 'bp-docs' ) ?></p> 
  647.  
  648. <?php if ( $help_p ) : ?> 
  649. <p><?php echo $help_p ?></p> 
  650. <?php endif ?> 
  651.  
  652. <?php if ( $help_url ) : ?> 
  653. <p><?php printf( __( 'See <a href="%s">this wiki page</a> for more information.', 'bp-docs' ), $help_url ) ?></p> 
  654. <?php endif ?> 
  655.  
  656. <p><a href="<?php echo $dismiss_url ?>"><?php _e( 'Dismiss this message', 'bp-docs' ) ?></a></p> 
  657. </div> 
  658. <?php 
  659.  
  660. /** 
  661. * Test whether the attachment upload directory is protected. 
  662. * We create a dummy file in the directory, and then test to see 
  663. * whether we can fetch a copy of the file with a remote request. 
  664. * @since 1.6.0 
  665. * @return True if protected, false if not. 
  666. */ 
  667. public function check_is_protected( $force_check = true ) { 
  668. global $is_apache; 
  669.  
  670. // Fall back on cached value if it exists 
  671. if ( ! $force_check ) { 
  672. $is_protected = bp_get_option( 'bp_docs_attachment_protection' ); 
  673. if ( '' !== $is_protected ) { 
  674. return (bool) $is_protected; 
  675.  
  676. $uploads = wp_upload_dir(); 
  677.  
  678. $test_dir = $uploads['basedir'] . DIRECTORY_SEPARATOR . 'bp-attachments' . DIRECTORY_SEPARATOR . '0'; 
  679. $test_file_dir = $test_dir . DIRECTORY_SEPARATOR . 'test.html'; 
  680. $test_text = 'This is a test of the Protected Attachment feature of BuddyPress Docs. Please do not remove.'; 
  681.  
  682. if ( ! file_exists( $test_file_dir ) ) { 
  683. if ( ! file_exists( $test_dir ) ) { 
  684. wp_mkdir_p( $test_dir ); 
  685.  
  686. // Create an .htaccess, if we can 
  687. if ( $is_apache ) { 
  688.  
  689. // Fake the doc ID 
  690. $this->doc_id = 0; 
  691.  
  692. $rules = array( 
  693. 'RewriteEngine On',  
  694. 'RewriteBase /',  
  695. 'RewriteRule (.+) ?bp-attachment=$1 [R=302, NC]',  
  696. ); 
  697.  
  698. if ( ! empty( $rules ) ) { 
  699. if ( ! file_exists( 'insert_with_markers' ) ) { 
  700. require_once( ABSPATH . 'wp-admin/includes/misc.php' ); 
  701. insert_with_markers( $test_dir . DIRECTORY_SEPARATOR . '.htaccess', 'BuddyPress Docs', $rules ); 
  702.  
  703. // Make a dummy file 
  704. file_put_contents( $test_dir . DIRECTORY_SEPARATOR . 'test.html', $test_text ); 
  705.  
  706. $test_url = $uploads['baseurl'] . '/bp-attachments/0/test.html'; 
  707. $r = wp_remote_get( $test_url ); 
  708.  
  709. // If the response body includes our test text, we have a problem 
  710. $is_protected = true; 
  711. if ( ! is_wp_error( $r ) && $r['body'] === $test_text ) { 
  712. $is_protected = false; 
  713.  
  714. // Cache 
  715. $cache = $is_protected ? '1' : '0'; 
  716. bp_update_option( 'bp_docs_attachment_protection', $cache ); 
  717.  
  718. return $is_protected;