class-attachment.php
<?php
/**
* Attachment component.
*
* @package HivePress\Components
*/
namespace HivePress\Components;
use HivePress\Helpers as hp;
use HivePress\Models;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;
/**
* Handles file attachments.
*/
final class Attachment extends Component {
/**
* Class constructor.
*
* @param array $args Component arguments.
*/
public function __construct( $args = [] ) {
// Set parent ID.
add_filter( 'wp_insert_attachment_data', [ $this, 'set_parent_id' ], 10, 2 );
// Delete attachment.
add_action( 'hivepress/v1/models/attachment/delete', [ $this, 'delete_attachment' ], 10, 2 );
// Delete attachments.
add_action( 'hivepress/v1/models/user/delete', [ $this, 'delete_attachments' ], 10, 2 );
add_action( 'hivepress/v1/models/post/delete', [ $this, 'delete_attachments' ], 10, 2 );
add_action( 'hivepress/v1/models/term/delete', [ $this, 'delete_attachments' ], 10, 2 );
add_action( 'hivepress/v1/models/comment/delete', [ $this, 'delete_attachments' ], 10, 2 );
// Generate filename.
add_filter( 'hivepress/v1/models/attachment/filename', [ $this, 'generate_filename' ], 10, 3 );
// Restrict REST attachments.
add_filter( 'rest_attachment_query', [ $this, 'filter_attachments' ] );
add_filter( 'rest_prepare_attachment', [ $this, 'restrict_rest_attachment' ], 1000, 2 );
// Restrict attachment page.
add_filter( 'posts_results', [ $this, 'restrict_attachment_page' ], 10, 2 );
// Restrict attachment embed.
add_filter( 'oembed_request_post_id', [ $this, 'restrict_attachment_embed' ] );
// Restrict attachment shortcode.
add_filter( 'shortcode_atts_gallery', [ $this, 'restrict_attachment_shortcode' ] );
add_filter( 'shortcode_atts_playlist', [ $this, 'restrict_attachment_shortcode' ] );
parent::__construct( $args );
}
/**
* Checks if the attachment is restricted.
*
* @param int $attachment_id Attachment ID.
* @return bool
*/
protected function is_restricted_attachment( $attachment_id ) {
return $attachment_id && get_post_meta( $attachment_id, 'hp_parent_model', true ) && ! current_user_can( 'edit_post', $attachment_id );
}
/**
* Checks if the file is valid.
*
* @param string $path File path.
* @param string $name File name.
* @param array $exts Allowed extensions.
* @return bool
*/
public function is_valid_file( $path, $name, $exts ) {
$type = wp_check_filetype_and_ext( $path, $name );
if ( ! $type['ext'] || ! in_array( strtoupper( $type['ext'] ), array_map( 'strtoupper', $exts ), true ) ) {
return false;
}
return true;
}
/**
* Gets allowed parent statuses.
*
* @return array
*/
public function get_parent_statuses() {
$statuses = [ 'auto-draft', 'draft', 'publish' ];
if ( hp\is_plugin_active( 'woocommerce' ) ) {
$statuses[] = 'wc-processing';
}
return $statuses;
}
/**
* Checks if the parent object is editable.
*
* @param object $object Parent object.
* @param string $field_name Parent field name.
* @return bool
*/
public function is_editable_parent( $object, $field_name = null ) {
// Get user ID.
$user_id = $object->get_user__id();
if ( $object::_get_meta( 'type' ) === 'user' ) {
$user_id = $object->get_id();
}
// Check user.
if ( get_current_user_id() !== $user_id ) {
return false;
}
// Check attribute.
if ( $field_name ) {
$attribute = hp\get_array_value( hivepress()->attribute->get_attributes( $object::_get_meta( 'name' ) ), $field_name );
if ( $attribute && ! $attribute['editable'] ) {
return false;
}
}
// Check status.
if ( $object::_get_meta( 'type' ) === 'post' ) {
return in_array( $object->get_status(), $this->get_parent_statuses(), true );
}
// Check draft.
if ( $object::_get_meta( 'type' ) === 'comment' ) {
$comment = get_comment( $object->get_id() );
return $comment && ! $comment->comment_post_ID && ! $comment->comment_karma;
}
return true;
}
/**
* Sets the attachment parent ID.
*
* @param array $attachment Attachment arguments.
* @param array $args Query arguments.
* @return array
*/
public function set_parent_id( $attachment, $args ) {
if ( isset( $args['comment_count'] ) ) {
$attachment['comment_count'] = absint( $args['comment_count'] );
}
return $attachment;
}
/**
* Deletes attachment.
*
* @param int $attachment_id Attachment ID.
* @param object $attachment Attachment object.
*/
public function delete_attachment( $attachment_id, $attachment ) {
// Get parent object.
$parent = $attachment->get_parent();
if ( empty( $parent ) ) {
return;
}
// Get parent field.
$parent_field = hp\get_array_value( $parent->_get_fields(), $attachment->get_parent_field() );
if ( empty( $parent_field ) || $parent_field::get_meta( 'name' ) !== 'attachment_upload' ) {
return;
}
if ( ! $parent_field->is_multiple() ) {
// Update parent object.
$parent->fill(
[
$parent_field->get_name() => null,
]
)->save( [ $parent_field->get_name() ] );
}
}
/**
* Deletes attachments.
*
* @param int $parent_id Parent ID.
* @param string $parent_alias Parent alias.
*/
public function delete_attachments( $parent_id, $parent_alias ) {
// Get parent type.
$parent_type = hp\get_first_array_value( array_slice( explode( '/', current_action() ), -2, 1 ) );
// Get parent model.
$parent_model = hivepress()->model->get_model_name( $parent_type, $parent_alias );
if ( empty( $parent_model ) || 'attachment' === $parent_model ) {
return;
}
// Delete attachments.
Models\Attachment::query()->filter(
[
'parent' => $parent_id,
'parent_model' => $parent_model,
]
)->delete();
}
/**
* Moves attachments.
*
* @param object $from Source object.
* @param object $to Target object.
* @param string $field Field name.
*/
public function move_attachments( $from, $to, $field ) {
// Get attachment IDs.
$attachment_ids = array_filter( (array) call_user_func( [ $from, 'get_' . $field . '__id' ] ) );
if ( ! $attachment_ids ) {
return;
}
// Get attachments.
$attachments = Models\Attachment::query()->filter(
[
'id__in' => $attachment_ids,
'parent' => $from->get_id(),
'parent_model' => $from::_get_meta( 'name' ),
]
)->limit( count( $attachment_ids ) )
->get();
// Get parent values.
$values = [
'parent' => $to->get_id(),
];
if ( $to::_get_meta( 'name' ) !== $from::_get_meta( 'name' ) ) {
$values['parent_model'] = $to::_get_meta( 'name' );
$values['parent__id'] = $to::_get_meta( 'type' ) === 'post' ? $to->get_id() : null;
}
// Set parent.
foreach ( $attachments as $attachment ) {
$attachment->fill( $values )->save( array_keys( $values ) );
}
// Clear field.
$from->fill(
[
$field => null,
]
)->save( [ $field ] );
}
/**
* Filters attachments.
*
* @param array $args Query arguments.
* @return array
*/
public function filter_attachments( $args ) {
if ( ! current_user_can( 'edit_others_posts' ) ) {
$meta_query = [
[
'key' => 'hp_parent_model',
'compare' => 'NOT EXISTS',
],
];
if ( ! empty( $args['meta_query'] ) ) {
$meta_query[] = $args['meta_query'];
}
$args['meta_query'] = $meta_query;
}
return $args;
}
/**
* Restricts REST attachment.
*
* @param object $response REST response.
* @param object $attachment Attachment object.
* @return object
*/
public function restrict_rest_attachment( $response, $attachment ) {
if ( $this->is_restricted_attachment( $attachment->ID ) ) {
return hp\rest_error( 403 );
}
return $response;
}
/**
* Restricts attachment page.
*
* @param array $posts Post objects.
* @param object $query Query object.
* @return array
*/
public function restrict_attachment_page( $posts, $query ) {
if ( $query->is_main_query() && $query->is_singular() && count( $posts ) === 1 ) {
$attachment = hp\get_first_array_value( $posts );
if ( get_post_type( $attachment ) === 'attachment' && $this->is_restricted_attachment( $attachment->ID ) ) {
$posts = [];
remove_action( 'template_redirect', 'redirect_canonical' );
}
}
return $posts;
}
/**
* Restricts attachment embed.
*
* @param int $post_id Post ID.
* @return int
*/
public function restrict_attachment_embed( $post_id ) {
if ( get_post_type( $post_id ) === 'attachment' && $this->is_restricted_attachment( $post_id ) ) {
return 0;
}
return $post_id;
}
/**
* Restricts attachment shortcode.
*
* @param array $atts Shortcode attributes.
* @return array
*/
public function restrict_attachment_shortcode( $atts ) {
// Get author ID.
$author_id = wp_doing_ajax() ? get_current_user_id() : get_post_field( 'post_author' );
if ( ! user_can( $author_id, 'edit_others_posts' ) ) {
add_action( 'pre_get_posts', [ $this, 'filter_shortcode_attachments' ] );
}
return $atts;
}
/**
* Filters shortcode attachments.
*
* @param object $query Query object.
*/
public function filter_shortcode_attachments( $query ) {
if ( $query->get( 'post_type' ) === 'attachment' ) {
remove_action( 'pre_get_posts', [ $this, 'filter_shortcode_attachments' ] );
$query->query_vars = $this->filter_attachments( $query->query_vars );
}
}
/**
* Generates a unique filename.
*
* @param string $filename Filename.
* @param string $ext Extension.
* @param string $dir Directory.
* @return string
*/
public function generate_filename( $filename, $ext, $dir ) {
$name = pathinfo( $filename, PATHINFO_FILENAME );
do {
$filename = $name . '-' . strtolower( wp_generate_password( 6, false, false ) ) . $ext;
} while ( file_exists( $dir . '/' . $filename ) );
return $filename;
}
}