post_content, 'wp-dropzone' ) ) {
wp_enqueue_style( 'dropzone', WP_DROPZONE_URL . 'css/dropzone.min.css', [], WP_DROPZONE_VERSION );
wp_enqueue_script( 'dropzone', WP_DROPZONE_URL . 'js/dropzone.min.js', [], WP_DROPZONE_VERSION, true );
wp_enqueue_script( 'wp-dropzone', WP_DROPZONE_URL . 'js/wp-dropzone.js', [ 'dropzone' ], WP_DROPZONE_VERSION, true );
}
}
/**
* Handle AJAX file upload to WordPress media library
*
* Processes file uploads with security verification, nonce validation, and media library integration.
* Includes performance optimizations for better upload handling.
*
* @since 1.0.0
* @return void
*/
public function ajax_upload_handle() {
// Verify nonce for security.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wp_dropzone_nonce' ) ) {
wp_send_json_error( __( 'Security check failed.', 'wp-dropzone' ), 403 );
return;
}
// Verify user has permission to upload files.
if ( ! current_user_can( 'upload_files' ) ) {
wp_send_json_error( __( 'You do not have permission to upload files.', 'wp-dropzone' ), 403 );
return;
}
if ( ! isset( $_FILES['file'] ) || empty( $_FILES['file'] ) ) {
wp_send_json_error( __( 'no file to upload.', 'wp-dropzone' ), 400 );
return;
}
// phpcs:ignore
$file = $_FILES['file'];
// Initialize variables for cleanup.
$tmp_file = null;
$wp_filesystem = null;
// Handle chunked uploads.
if ( isset( $_POST['dzuuid'] ) && isset( $_POST['dzchunkindex'] ) && isset( $_POST['dztotalchunkcount'] ) ) {
$uid = trim( sanitize_text_field( wp_unslash( $_POST['dzuuid'] ) ) );
$total_chunks = intval( $_POST['dztotalchunkcount'] );
$chunk_index = intval( $_POST['dzchunkindex'] ) + 1;
$uploads = wp_upload_dir();
// Validate file extension before processing chunks (security fix).
$file_extension = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
// Get allowed file types from WordPress.
$allowed_types = get_allowed_mime_types();
$allowed_extensions = [];
foreach ( $allowed_types as $ext => $mime ) {
$exts = explode( '|', $ext );
$allowed_extensions = array_merge( $allowed_extensions, $exts );
}
// Block dangerous file extensions.
$dangerous_extensions = [ 'php', 'php3', 'php4', 'php5', 'phtml', 'phps', 'pht', 'phar', 'shtml', 'htaccess', 'htpasswd', 'sh', 'bash', 'py', 'pl', 'rb', 'js', 'jsp', 'asp', 'aspx', 'exe', 'dll', 'bat', 'cmd', 'com', 'scr', 'vbs', 'wsf' ];
if ( in_array( $file_extension, $dangerous_extensions, true ) || ! in_array( $file_extension, $allowed_extensions, true ) ) {
wp_send_json_error( __( 'File type not allowed.', 'wp-dropzone' ), 400 );
return;
}
if ( ! class_exists( 'WP_Filesystem_Direct' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
}
if ( ! defined( 'FS_CHMOD_FILE' ) ) {
define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
}
$wp_filesystem = new WP_Filesystem_Direct( null );
// Use a safer temporary location with sanitized filename (security fix).
$safe_filename = sanitize_file_name( $file['name'] );
$tmp_file = get_temp_dir() . 'wp-dropzone-' . $uid . '-' . $safe_filename;
// Combine file chunks.
$existing_content = '';
if ( $wp_filesystem->exists( $tmp_file ) ) {
$existing_content = $wp_filesystem->get_contents( $tmp_file );
}
$chunk_content = $wp_filesystem->get_contents( $file['tmp_name'] );
$contents = $existing_content . $chunk_content;
$wp_filesystem->put_contents( $tmp_file, $contents, false );
if ( $total_chunks !== $chunk_index ) {
wp_send_json_success( [ 'chunk_uploaded' => true ] );
return;
}
// Final chunk - validate before moving to uploads directory.
$file['tmp_name'] = $tmp_file;
$file['type'] = isset( $_POST['origtype'] ) ? sanitize_text_field( wp_unslash( $_POST['origtype'] ) ) : '';
$file['size'] = $wp_filesystem->size( $tmp_file );
}
// Include WordPress file handling library.
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
// Fire hook before upload.
do_action( 'wp_dropzone_before_upload_file', $file );
// Upload file to server.
$movefile = wp_handle_upload( $file, [ 'test_form' => false ] );
// Handle successful upload.
if ( $movefile && ! isset( $movefile['error'] ) ) {
// Clean up temporary chunk file if it exists (security fix).
if ( isset( $tmp_file ) && ! empty( $tmp_file ) && file_exists( $tmp_file ) ) {
if ( $wp_filesystem && is_a( $wp_filesystem, 'WP_Filesystem_Direct' ) ) {
$wp_filesystem->delete( $tmp_file );
} else {
wp_delete_file( $tmp_file );
}
}
// Fire hook after upload.
do_action( 'wp_dropzone_after_upload_file', $file );
$filename = $movefile['file'];
$filetype = wp_check_filetype( basename( $filename ), null );
$wp_upload_dir = wp_upload_dir();
$attachment = [
'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ),
'post_mime_type' => $filetype['type'],
'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ),
'post_content' => '',
'post_status' => 'inherit',
];
// Add file to media library.
$attachment_id = wp_insert_attachment( $attachment, $filename );
// Fire hook after media insertion.
do_action( 'wp_dropzone_after_insert_attachment', $attachment_id );
// Generate attachment metadata.
if ( $attachment_id ) {
require_once ABSPATH . 'wp-admin/includes/image.php';
$attachment_data = wp_generate_attachment_metadata( $attachment_id, $filename );
wp_update_attachment_metadata( $attachment_id, $attachment_data );
}
$message = [
'error' => false,
'data' => wp_get_attachment_url( $attachment_id ),
];
} else {
// Clean up temporary chunk file on error (security fix).
if ( isset( $tmp_file ) && ! empty( $tmp_file ) && file_exists( $tmp_file ) ) {
if ( $wp_filesystem && is_a( $wp_filesystem, 'WP_Filesystem_Direct' ) ) {
$wp_filesystem->delete( $tmp_file );
} else {
wp_delete_file( $tmp_file );
}
}
wp_send_json_error( $movefile['error'], 400 );
return;
}
wp_send_json( $message );
}
/**
* Render wp-dropzone shortcode with customizable options
*
* Generates HTML for dropzone upload areas with configurable styling,
* file restrictions, and upload behavior. Configuration is passed via
* data-config attribute for JavaScript initialization.
*
* @since 1.0.0
* @param array $atts Shortcode attributes with default values.
* @return string HTML output for dropzone with inline styles
*/
public function add_shortcode( $atts ) {
$atts = shortcode_atts(
[
'id' => bin2hex( random_bytes( 2 ) ),
'callback' => '',
'title' => '',
'desc' => '',
'border-width' => '',
'border-style' => '',
'border-color' => '',
'background' => '',
'margin-bottom' => '',
'max-file-size' => wp_max_upload_size(),
'remove-links' => 'false',
'clickable' => 'true',
'accepted-files' => null,
'max-files' => null,
'max-files-alert' => __( 'Max file limit exceeded.', 'wp-dropzone' ),
'auto-process' => 'true',
'upload-button-text' => __( 'Upload', 'wp-dropzone' ),
'dom-id' => '',
'resize-width' => null,
'resize-height' => null,
'resize-quality' => 0.8,
'resize-method' => 'contain',
'thumbnail-width' => 120,
'thumbnail-height' => 120,
'thumbnail-method' => 'crop',
],
$atts
);
if ( ! is_user_logged_in() ) {
$atts['desc'] = __( 'Please login to upload files.', 'wp-dropzone' );
} elseif ( ! current_user_can( 'upload_files' ) ) {
$atts['desc'] = __( 'You do not have permission to upload files.', 'wp-dropzone' );
}
// Sanitize the id for safe use both as an HTML id/class and inside the
// inline