BP_Attachment

BP Attachment class.

Defined (1)

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

/bp-core/classes/class-bp-attachment.php  
  1. abstract class BP_Attachment { 
  2.  
  3. /** Upload properties *****************************************************/ 
  4.  
  5. /** 
  6. * The file being uploaded. 
  7. * @var array 
  8. */ 
  9. public $attachment = array(); 
  10.  
  11. /** 
  12. * The default args to be merged with the 
  13. * ones passed by the child class. 
  14. * @var array 
  15. */ 
  16. protected $default_args = array( 
  17. 'original_max_filesize' => 0,  
  18. 'allowed_mime_types' => array(),  
  19. 'base_dir' => '',  
  20. 'action' => '',  
  21. 'file_input' => '',  
  22. 'upload_error_strings' => array(),  
  23. 'required_wp_files' => array( 'file' ),  
  24. 'upload_dir_filter_args' => 0,  
  25. ); 
  26.  
  27. /** 
  28. * Construct Upload parameters. 
  29. * @since 2.3.0 
  30. * @since 2.4.0 Add the $upload_dir_filter_args argument to the $arguments array 
  31. * @param array|string $args { 
  32. * @type int $original_max_filesize Maximum file size in kilobytes. Defaults to php.ini settings. 
  33. * @type array $allowed_mime_types List of allowed file extensions (eg: array( 'jpg', 'gif', 'png' ) ). 
  34. * Defaults to WordPress allowed mime types. 
  35. * @type string $base_dir Component's upload base directory. Defaults to WordPress 'uploads'. 
  36. * @type string $action The upload action used when uploading a file, $_POST['action'] must be set 
  37. * and its value must equal $action {@link wp_handle_upload()} (required). 
  38. * @type string $file_input The name attribute used in the file input. (required). 
  39. * @type array $upload_error_strings A list of specific error messages (optional). 
  40. * @type array $required_wp_files The list of required WordPress core files. Default: array( 'file' ). 
  41. * @type int $upload_dir_filter_args 1 to receive the original Upload dir array in the Upload dir filter, 0 otherwise. 
  42. * Defaults to 0 (optional). 
  43. * } 
  44. */ 
  45. public function __construct( $args = '' ) { 
  46. // Upload action and the file input name are required parameters. 
  47. if ( empty( $args['action'] ) || empty( $args['file_input'] ) ) { 
  48. return false; 
  49.  
  50. // Sanitize the action ID and the file input name. 
  51. $this->action = sanitize_key( $args['action'] ); 
  52. $this->file_input = sanitize_key( $args['file_input'] ); 
  53.  
  54. /** 
  55. * Max file size defaults to php ini settings or, in the case of 
  56. * a multisite config, the root site fileupload_maxk option 
  57. */ 
  58. $this->default_args['original_max_filesize'] = (int) wp_max_upload_size(); 
  59.  
  60. $params = bp_parse_args( $args, $this->default_args, $this->action . '_upload_params' ); 
  61.  
  62. foreach ( $params as $key => $param ) { 
  63. if ( 'upload_error_strings' === $key ) { 
  64. $this->{$key} = $this->set_upload_error_strings( $param ); 
  65.  
  66. // Sanitize the base dir. 
  67. } elseif ( 'base_dir' === $key ) { 
  68. $this->{$key} = sanitize_title( $param ); 
  69.  
  70. // Sanitize the upload dir filter arg to pass. 
  71. } elseif ( 'upload_dir_filter_args' === $key ) { 
  72. $this->{$key} = (int) $param; 
  73.  
  74. // Action & File input are already set and sanitized. 
  75. } elseif ( 'action' !== $key && 'file_input' !== $key ) { 
  76. $this->{$key} = $param; 
  77.  
  78. // Set the path/url and base dir for uploads. 
  79. $this->set_upload_dir(); 
  80.  
  81. /** 
  82. * Set upload path and url for the component. 
  83. * @since 2.3.0 
  84. */ 
  85. public function set_upload_dir() { 
  86. // Set the directory, path, & url variables. 
  87. $this->upload_dir = bp_upload_dir(); 
  88.  
  89. if ( empty( $this->upload_dir ) ) { 
  90. return false; 
  91.  
  92. $this->upload_path = $this->upload_dir['basedir']; 
  93. $this->url = $this->upload_dir['baseurl']; 
  94.  
  95. // Ensure URL is https if SSL is set/forced. 
  96. if ( is_ssl() ) { 
  97. $this->url = str_replace( 'http://', 'https://', $this->url ); 
  98.  
  99. /** 
  100. * Custom base dir. 
  101. * If the component set this property, set the specific path, url and create the dir 
  102. */ 
  103. if ( ! empty( $this->base_dir ) ) { 
  104. $this->upload_path = trailingslashit( $this->upload_path ) . $this->base_dir; 
  105. $this->url = trailingslashit( $this->url ) . $this->base_dir; 
  106.  
  107. // Finally create the base dir. 
  108. $this->create_dir(); 
  109.  
  110. /** 
  111. * Set Upload error messages. 
  112. * Used into the $overrides argument of BP_Attachment->upload() 
  113. * @since 2.3.0 
  114. * @param array $param A list of error messages to add to BuddyPress core ones. 
  115. * @return array $upload_errors The list of upload errors. 
  116. */ 
  117. public function set_upload_error_strings( $param = array() ) { 
  118. /** 
  119. * Index of the array is the error code 
  120. * Custom errors will start at 9 code 
  121. */ 
  122. $upload_errors = array( 
  123. 0 => __( 'The file was uploaded successfully', 'buddypress' ),  
  124. 1 => __( 'The uploaded file exceeds the maximum allowed file size for this site', 'buddypress' ),  
  125. 2 => sprintf( __( 'The uploaded file exceeds the maximum allowed file size of: %s', 'buddypress' ), size_format( $this->original_max_filesize ) ),  
  126. 3 => __( 'The uploaded file was only partially uploaded.', 'buddypress' ),  
  127. 4 => __( 'No file was uploaded.', 'buddypress' ),  
  128. 5 => '',  
  129. 6 => __( 'Missing a temporary folder.', 'buddypress' ),  
  130. 7 => __( 'Failed to write file to disk.', 'buddypress' ),  
  131. 8 => __( 'File upload stopped by extension.', 'buddypress' ),  
  132. ); 
  133.  
  134. if ( ! array_intersect_key( $upload_errors, (array) $param ) ) { 
  135. foreach ( $param as $key_error => $error_message ) { 
  136. $upload_errors[ $key_error ] = $error_message; 
  137.  
  138. return $upload_errors; 
  139.  
  140. /** 
  141. * Include the WordPress core needed files. 
  142. * @since 2.3.0 
  143. */ 
  144. public function includes() { 
  145. foreach ( array_unique( $this->required_wp_files ) as $wp_file ) { 
  146. if ( ! file_exists( ABSPATH . "/wp-admin/includes/{$wp_file}.php" ) ) { 
  147. continue; 
  148.  
  149. require_once( ABSPATH . "/wp-admin/includes/{$wp_file}.php" ); 
  150.  
  151. /** 
  152. * Upload the attachment. 
  153. * @since 2.3.0 
  154. * @param array $file The appropriate entry the from $_FILES superglobal. 
  155. * @param string $upload_dir_filter A specific filter to be applied to 'upload_dir' (optional). 
  156. * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null. 
  157. * @return array On success, returns an associative array of file attributes. 
  158. * On failure, returns an array containing the error message 
  159. * (eg: array( 'error' => $message ) ) 
  160. */ 
  161. public function upload( $file, $upload_dir_filter = '', $time = null ) { 
  162. /** 
  163. * Upload action and the file input name are required parameters. 
  164. * @see BP_Attachment:__construct() 
  165. */ 
  166. if ( empty( $this->action ) || empty( $this->file_input ) ) { 
  167. return false; 
  168.  
  169. /** 
  170. * Add custom rules before enabling the file upload 
  171. */ 
  172. add_filter( "{$this->action}_prefilter", array( $this, 'validate_upload' ), 10, 1 ); 
  173.  
  174. /** 
  175. * The above dynamic filter was introduced in WordPress 4.0, as we support WordPress 
  176. * back to 3.6, we need to also use the pre 4.0 static filter and remove it after 
  177. * the upload was processed. 
  178. */ 
  179. add_filter( 'wp_handle_upload_prefilter', array( $this, 'validate_upload' ), 10, 1 ); 
  180.  
  181. // Set Default overrides. 
  182. $overrides = array( 
  183. 'action' => $this->action,  
  184. 'upload_error_strings' => $this->upload_error_strings,  
  185. ); 
  186.  
  187. /** 
  188. * Add a mime override if needed 
  189. * Used to restrict uploads by extensions 
  190. */ 
  191. if ( ! empty( $this->allowed_mime_types ) ) { 
  192. $mime_types = $this->validate_mime_types(); 
  193.  
  194. if ( ! empty( $mime_types ) ) { 
  195. $overrides['mimes'] = $mime_types; 
  196.  
  197. /** 
  198. * If you need to add some overrides we haven't thought of. 
  199. * @param array $overrides The wp_handle_upload overrides 
  200. */ 
  201. $overrides = apply_filters( 'bp_attachment_upload_overrides', $overrides ); 
  202.  
  203. $this->includes(); 
  204.  
  205. /** 
  206. * If the $base_dir was set when constructing the class,  
  207. * and no specific filter has been requested, use a default 
  208. * filter to create the specific $base dir 
  209. * @see BP_Attachment->upload_dir_filter() 
  210. */ 
  211. if ( empty( $upload_dir_filter ) && ! empty( $this->base_dir ) ) { 
  212. $upload_dir_filter = array( $this, 'upload_dir_filter' ); 
  213.  
  214. // Make sure the file will be uploaded in the attachment directory. 
  215. if ( ! empty( $upload_dir_filter ) ) { 
  216. add_filter( 'upload_dir', $upload_dir_filter, 10, $this->upload_dir_filter_args ); 
  217.  
  218. // Upload the attachment. 
  219. $this->attachment = wp_handle_upload( $file[ $this->file_input ], $overrides, $time ); 
  220.  
  221. // Restore WordPress Uploads data. 
  222. if ( ! empty( $upload_dir_filter ) ) { 
  223. remove_filter( 'upload_dir', $upload_dir_filter, 10 ); 
  224.  
  225. // Remove the pre WordPress 4.0 static filter. 
  226. remove_filter( 'wp_handle_upload_prefilter', array( $this, 'validate_upload' ), 10 ); 
  227.  
  228. // Finally return the uploaded file or the error. 
  229. return $this->attachment; 
  230.  
  231. /** 
  232. * Validate the allowed mime types using WordPress allowed mime types. 
  233. * In case of a multisite, the mime types are already restricted by 
  234. * the 'upload_filetypes' setting. BuddyPress will respect this setting. 
  235. * @see check_upload_mimes() 
  236. * @since 2.3.0 
  237. */ 
  238. protected function validate_mime_types() { 
  239. $wp_mimes = get_allowed_mime_types(); 
  240. $valid_mimes = array(); 
  241.  
  242. // Set the allowed mimes for the upload. 
  243. foreach ( (array) $this->allowed_mime_types as $ext ) { 
  244. foreach ( $wp_mimes as $ext_pattern => $mime ) { 
  245. if ( $ext !== '' && strpos( $ext_pattern, $ext ) !== false ) { 
  246. $valid_mimes[$ext_pattern] = $mime; 
  247. return $valid_mimes; 
  248.  
  249. /** 
  250. * Specific upload rules. 
  251. * Override this function from your child class to build your specific rules 
  252. * By default, if an original_max_filesize is provided, a check will be done 
  253. * on the file size. 
  254. * @see BP_Attachment_Avatar->validate_upload() for an example of use 
  255. * @since 2.3.0 
  256. * @param array $file The temporary file attributes (before it has been moved). 
  257. * @return array The file. 
  258. */ 
  259. public function validate_upload( $file = array() ) { 
  260. // Bail if already an error. 
  261. if ( ! empty( $file['error'] ) ) { 
  262. return $file; 
  263.  
  264. if ( ! empty( $this->original_max_filesize ) && $file['size'] > $this->original_max_filesize ) { 
  265. $file['error'] = 2; 
  266.  
  267. // Return the file. 
  268. return $file; 
  269.  
  270. /** 
  271. * Default filter to save the attachments. 
  272. * @since 2.3.0 
  273. * @since 2.4.0 Add the $upload_dir parameter to the method 
  274. * regarding to context 
  275. * @param array $upload_dir The original Uploads dir. 
  276. * @return array The upload directory data. 
  277. */ 
  278. public function upload_dir_filter( $upload_dir = array() ) { 
  279.  
  280. /** 
  281. * Filters the component's upload directory. 
  282. * @since 2.3.0 
  283. * @since 2.4.0 Include the original Upload directory as the second parameter of the filter. 
  284. * @param array $value Array containing the path, URL, and other helpful settings. 
  285. * @param array $upload_dir The original Uploads dir. 
  286. */ 
  287. return apply_filters( 'bp_attachment_upload_dir', array( 
  288. 'path' => $this->upload_path,  
  289. 'url' => $this->url,  
  290. 'subdir' => false,  
  291. 'basedir' => $this->upload_path,  
  292. 'baseurl' => $this->url,  
  293. 'error' => false 
  294. ), $upload_dir ); 
  295.  
  296. /** 
  297. * Create the custom base directory for the component uploads. 
  298. * Override this function in your child class to run specific actions. 
  299. * (eg: add an .htaccess file) 
  300. * @since 2.3.0 
  301. */ 
  302. public function create_dir() { 
  303. // Bail if no specific base dir is set. 
  304. if ( empty( $this->base_dir ) ) { 
  305. return false; 
  306.  
  307. // Check if upload path already exists. 
  308. if ( ! is_dir( $this->upload_path ) ) { 
  309.  
  310. // If path does not exist, attempt to create it. 
  311. if ( ! wp_mkdir_p( $this->upload_path ) ) { 
  312. return false; 
  313.  
  314. // Directory exists. 
  315. return true; 
  316.  
  317. /** 
  318. * Crop an image file. 
  319. * @since 2.3.0 
  320. * @param array $args { 
  321. * @type string $original_file The source file (absolute path) for the Attachment. 
  322. * @type int $crop_x The start x position to crop from. 
  323. * @type int $crop_y The start y position to crop from. 
  324. * @type int $crop_w The width to crop. 
  325. * @type int $crop_h The height to crop. 
  326. * @type int $dst_w The destination width. 
  327. * @type int $dst_h The destination height. 
  328. * @type int $src_abs Optional. If the source crop points are absolute. 
  329. * @type string $dst_file Optional. The destination file to write to. 
  330. * } 
  331. * @return string|WP_Error New filepath on success, WP_Error on failure. 
  332. */ 
  333. public function crop( $args = array() ) { 
  334. $wp_error = new WP_Error(); 
  335.  
  336. $r = bp_parse_args( $args, array( 
  337. 'original_file' => '',  
  338. 'crop_x' => 0,  
  339. 'crop_y' => 0,  
  340. 'crop_w' => 0,  
  341. 'crop_h' => 0,  
  342. 'dst_w' => 0,  
  343. 'dst_h' => 0,  
  344. 'src_abs' => false,  
  345. 'dst_file' => false,  
  346. ), 'bp_attachment_crop_args' ); 
  347.  
  348. if ( empty( $r['original_file'] ) || ! file_exists( $r['original_file'] ) ) { 
  349. $wp_error->add( 'crop_error', __( 'Cropping the file failed: missing source file.', 'buddypress' ) ); 
  350. return $wp_error; 
  351.  
  352. // Check image file pathes. 
  353. $path_error = __( 'Cropping the file failed: the file path is not allowed.', 'buddypress' ); 
  354.  
  355. // Make sure it's coming from an uploaded file. 
  356. if ( false === strpos( $r['original_file'], $this->upload_path ) ) { 
  357. $wp_error->add( 'crop_error', $path_error ); 
  358. return $wp_error; 
  359.  
  360. /** 
  361. * If no destination file is provided, WordPress will use a default name 
  362. * and will write the file in the source file's folder. 
  363. * If a destination file is provided, we need to make sure it's going into uploads. 
  364. */ 
  365. if ( ! empty( $r['dst_file'] ) && false === strpos( $r['dst_file'], $this->upload_path ) ) { 
  366. $wp_error->add( 'crop_error', $path_error ); 
  367. return $wp_error; 
  368.  
  369. // Check image file types. 
  370. $check_types = array( 'src_file' => array( 'file' => $r['original_file'], 'error' => _x( 'source file', 'Attachment source file', 'buddypress' ) ) ); 
  371. if ( ! empty( $r['dst_file'] ) ) { 
  372. $check_types['dst_file'] = array( 'file' => $r['dst_file'], 'error' => _x( 'destination file', 'Attachment destination file', 'buddypress' ) ); 
  373.  
  374. /** 
  375. * WordPress image supported types. 
  376. * @see wp_attachment_is() 
  377. */ 
  378. $supported_image_types = array( 
  379. 'jpg' => 1,  
  380. 'jpeg' => 1,  
  381. 'jpe' => 1,  
  382. 'gif' => 1,  
  383. 'png' => 1,  
  384. ); 
  385.  
  386. foreach ( $check_types as $file ) { 
  387. $is_image = wp_check_filetype( $file['file'] ); 
  388. $ext = $is_image['ext']; 
  389.  
  390. if ( empty( $ext ) || empty( $supported_image_types[ $ext ] ) ) { 
  391. $wp_error->add( 'crop_error', sprintf( __( 'Cropping the file failed: %s is not a supported image file.', 'buddypress' ), $file['error'] ) ); 
  392. return $wp_error; 
  393.  
  394. // Add the image.php to the required WordPress files, if it's not already the case. 
  395. $required_files = array_flip( $this->required_wp_files ); 
  396. if ( ! isset( $required_files['image'] ) ) { 
  397. $this->required_wp_files[] = 'image'; 
  398.  
  399. // Load the files. 
  400. $this->includes(); 
  401.  
  402. // Finally crop the image. 
  403. return wp_crop_image( $r['original_file'], (int) $r['crop_x'], (int) $r['crop_y'], (int) $r['crop_w'], (int) $r['crop_h'], (int) $r['dst_w'], (int) $r['dst_h'], $r['src_abs'], $r['dst_file'] ); 
  404.  
  405. /** 
  406. * Build script datas for the Uploader UI. 
  407. * Override this method from your child class to build the script datas. 
  408. * @since 2.3.0 
  409. * @return array The javascript localization data. 
  410. */ 
  411. public function script_data() { 
  412. $script_data = array( 
  413. 'action' => $this->action,  
  414. 'file_data_name' => $this->file_input,  
  415. 'max_file_size' => $this->original_max_filesize,  
  416. 'feedback_messages' => array( 
  417. 1 => __( 'Sorry, uploading the file failed.', 'buddypress' ),  
  418. 2 => __( 'File successfully uploaded.', 'buddypress' ),  
  419. ),  
  420. ); 
  421.  
  422. return $script_data; 
  423.  
  424. /** 
  425. * Get full data for an image 
  426. * @since 2.4.0 
  427. * @param string $file Absolute path to the uploaded image. 
  428. * @return bool|array An associate array containing the width, height and metadatas. 
  429. * False in case an important image attribute is missing. 
  430. */ 
  431. public static function get_image_data( $file ) { 
  432. // Try to get image basic data. 
  433. list( $width, $height, $sourceImageType ) = @getimagesize( $file ); 
  434.  
  435. // No need to carry on if we couldn't get image's basic data. 
  436. if ( is_null( $width ) || is_null( $height ) || is_null( $sourceImageType ) ) { 
  437. return false; 
  438.  
  439. // Initialize the image data. 
  440. $image_data = array( 
  441. 'width' => $width,  
  442. 'height' => $height,  
  443. ); 
  444.  
  445. /** 
  446. * Make sure the wp_read_image_metadata function is reachable for the old Avatar UI 
  447. * or if WordPress < 3.9 (New Avatar UI is not available in this case) 
  448. */ 
  449. if ( ! function_exists( 'wp_read_image_metadata' ) ) { 
  450. require_once( ABSPATH . 'wp-admin/includes/image.php' ); 
  451.  
  452. // Now try to get image's meta data. 
  453. $meta = wp_read_image_metadata( $file ); 
  454.  
  455. if ( ! empty( $meta ) ) { 
  456. // Before 4.0 the Orientation wasn't included. 
  457. if ( ! isset( $meta['orientation'] ) && 
  458. is_callable( 'exif_read_data' ) && 
  459. in_array( $sourceImageType, apply_filters( 'wp_read_image_metadata_types', array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) ) ) 
  460. ) { 
  461. $exif = exif_read_data( $file ); 
  462.  
  463. if ( ! empty( $exif['Orientation'] ) ) { 
  464. $meta['orientation'] = $exif['Orientation']; 
  465.  
  466. // Now add the metas to image data. 
  467. $image_data['meta'] = $meta; 
  468.  
  469. /** 
  470. * Filter here to add/remove/edit data to the image full data 
  471. * @since 2.4.0 
  472. * @param array $image_data An associate array containing the width, height and metadatas. 
  473. */ 
  474. return apply_filters( 'bp_attachments_get_image_data', $image_data ); 
  475.  
  476. /** 
  477. * Edit an image file to resize it or rotate it 
  478. * @since 2.4.0 
  479. * @param string $attachment_type The attachment type (eg: avatar or cover_image). Required. 
  480. * @param array $args { 
  481. * @type string $file Absolute path to the image file (required). 
  482. * @type int $max_w Max width attribute for the editor's resize method (optional). 
  483. * @type int $max_h Max height attribute for the editor's resize method (optional). 
  484. * @type bool $crop Crop attribute for the editor's resize method (optional). 
  485. * @type float $rotate Angle for the editor's rotate method (optional). 
  486. * @type int $quality Compression quality on a 1-100% scale (optional). 
  487. * @type bool $save Whether to use the editor's save method or not (optional). 
  488. * } 
  489. * @return string|WP_Image_Editor|WP_Error The edited image path or the WP_Image_Editor object in case of success,  
  490. * an WP_Error object otherwise. 
  491. */ 
  492. public static function edit_image( $attachment_type, $args = array() ) { 
  493. if ( empty( $attachment_type ) ) { 
  494. return new WP_Error( 'missing_parameter' ); 
  495.  
  496. $r = bp_parse_args( $args, array( 
  497. 'file' => '',  
  498. 'max_w' => 0,  
  499. 'max_h' => 0,  
  500. 'crop' => false,  
  501. 'rotate' => 0,  
  502. 'quality' => 90,  
  503. 'save' => true,  
  504. ), 'attachment_' . $attachment_type . '_edit_image' ); 
  505.  
  506. // Make sure we have to edit the image. 
  507. if ( empty( $r['max_w'] ) && empty( $r['max_h'] ) && empty( $r['rotate'] ) && empty( $r['file'] ) ) { 
  508. return new WP_Error( 'missing_parameter' ); 
  509.  
  510. // Get the image editor. 
  511. $editor = wp_get_image_editor( $r['file'] ); 
  512.  
  513. if ( is_wp_error( $editor ) ) { 
  514. return $editor; 
  515.  
  516. $editor->set_quality( $r['quality'] ); 
  517.  
  518. if ( ! empty( $r['rotate'] ) ) { 
  519. $rotated = $editor->rotate( $r['rotate'] ); 
  520.  
  521. // Stop in case of error. 
  522. if ( is_wp_error( $rotated ) ) { 
  523. return $rotated; 
  524.  
  525. if ( ! empty( $r['max_w'] ) || ! empty( $r['max_h'] ) ) { 
  526. $resized = $editor->resize( $r['max_w'], $r['max_h'], $r['crop'] ); 
  527.  
  528. // Stop in case of error. 
  529. if ( is_wp_error( $resized ) ) { 
  530. return $resized; 
  531.  
  532. // Use the editor save method to get a path to the edited image. 
  533. if ( true === $r['save'] ) { 
  534. return $editor->save( $editor->generate_filename() ); 
  535.  
  536. // Need to do some other edit actions or use a specific method to save file. 
  537. } else { 
  538. return $editor;