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