balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

uploads-manager.php (10860B)


      1 <?php
      2 namespace Elementor\Core\Files;
      3 
      4 use Elementor\Core\Base\Base_Object;
      5 use Elementor\Core\Files\File_Types\Base as File_Type_Base;
      6 use Elementor\Core\Files\File_Types\Json;
      7 use Elementor\Core\Files\File_Types\Zip;
      8 use Elementor\Core\Utils\Exceptions;
      9 
     10 if ( ! defined( 'ABSPATH' ) ) {
     11 	exit; // Exit if accessed directly.
     12 }
     13 
     14 /**
     15  * Elementor uploads manager.
     16  *
     17  * Elementor uploads manager handler class is responsible for handling file uploads that are not done with WP Media.
     18  *
     19  * @since 3.3.0
     20  */
     21 class Uploads_Manager extends Base_Object {
     22 
     23 	const UNFILTERED_FILE_UPLOADS_KEY = 'elementor_unfiltered_files_upload';
     24 	const INVALID_FILE_CONTENT = 'Invalid Content In File';
     25 
     26 	/**
     27 	 * @var File_Type_Base[]
     28 	 */
     29 	private $file_type_handlers = [];
     30 
     31 	private $allowed_file_extensions;
     32 
     33 	private $are_unfiltered_files_enabled;
     34 
     35 	/**
     36 	 * @var string
     37 	 */
     38 	private $temp_dir;
     39 
     40 	/**
     41 	 * Register File Types
     42 	 *
     43 	 * To Add a new file type to Elementor, with its own handling logic, you need to add it to the $file_types array here.
     44 	 *
     45 	 * @since 3.3.0
     46 	 */
     47 	public function register_file_types() {
     48 		// All file types that have handlers should be included here.
     49 		$file_types = [
     50 			'json' => new Json(),
     51 			'zip' => new Zip(),
     52 		];
     53 
     54 		foreach ( $file_types as $file_type => $file_handler ) {
     55 			$this->file_type_handlers[ $file_type ] = $file_handler;
     56 		}
     57 	}
     58 
     59 	/**
     60 	 * Extract and Validate Zip
     61 	 *
     62 	 * This method accepts a $file array (which minimally should include a 'tmp_name')
     63 	 *
     64 	 * @param string $file_path
     65 	 * @param array $allowed_file_types
     66 	 * @return array|\WP_Error
     67 	 */
     68 	public function extract_and_validate_zip( $file_path, $allowed_file_types = null ) {
     69 		$result = [];
     70 
     71 		/** @var Zip $zip_handler - File Type */
     72 		$zip_handler = $this->file_type_handlers['zip'];
     73 
     74 		// Returns an array of file paths.
     75 		$extracted = $zip_handler->extract( $file_path, $allowed_file_types );
     76 
     77 		// If there are no extracted file names, no files passed the extraction validation.
     78 		if ( empty( $extracted['files'] ) ) {
     79 			// TODO: Decide what to do if no files passed the extraction validation
     80 			return new \WP_Error( 'file_error', self::INVALID_FILE_CONTENT );
     81 		}
     82 
     83 		$result['extraction_directory'] = $extracted['extraction_directory'];
     84 
     85 		foreach ( $extracted['files'] as $extracted_file_path ) {
     86 			// Each file is an array with a 'name' (file path) property.
     87 			if ( ! is_wp_error( $this->validate_file( $extracted_file_path ) ) ) {
     88 				$result['files'][] = $extracted_file_path;
     89 			}
     90 		}
     91 
     92 		return $result;
     93 	}
     94 
     95 	/**
     96 	 * Handle Elementor Upload
     97 	 *
     98 	 * This method receives a $file array. If the received file is a Base64 string, the $file array should include a
     99 	 * 'fileData' property containing the string, which is decoded and has its contents stored in a temporary file.
    100 	 * If the $file parameter passed is a standard $file array, the 'name' and 'tmp_name' properties are used for
    101 	 * validation.
    102 	 *
    103 	 * The file goes through validation; if it passes validation, the file is returned. Otherwise, an error is returned.
    104 	 *
    105 	 * @param array $file
    106 	 * @param array $allowed_file_extensions Optional. an array of file types that are allowed to pass validation for each
    107 	 * upload.
    108 	 * @return array|\WP_Error
    109 	 */
    110 	public function handle_elementor_upload( array $file, $allowed_file_extensions = null ) {
    111 		// If $file['fileData'] is set, it signals that the passed file is a Base64 string that needs to be decoded and
    112 		// saved to a temporary file.
    113 		if ( isset( $file['fileData'] ) ) {
    114 			$file = $this->save_base64_to_tmp_file( $file );
    115 		}
    116 
    117 		$validation_result = $this->validate_file( $file['tmp_name'], $allowed_file_extensions );
    118 
    119 		if ( is_wp_error( $validation_result ) ) {
    120 			return $validation_result;
    121 		}
    122 
    123 		return $file;
    124 	}
    125 
    126 	/**
    127 	 * Runs on the 'wp_handle_upload_prefilter' filter.
    128 	 *
    129 	 * @param $file
    130 	 * @return mixed
    131 	 */
    132 	public function handle_elementor_wp_media_upload( $file ) {
    133 		// If it isn't a file uploaded by Elementor, we do not intervene.
    134 		if ( ! $this->is_elementor_wp_media_upload() ) {
    135 			return $file;
    136 		}
    137 
    138 		$result = $this->validate_file( $file['tmp_name'] );
    139 
    140 		if ( is_wp_error( $result ) ) {
    141 			$file['error'] = $result->get_error_message();
    142 		}
    143 
    144 		return $file;
    145 	}
    146 
    147 	/**
    148 	 * Get File Type Handler
    149 	 *
    150 	 * Initialize the proper file type handler according to the file extension
    151 	 * and assign it to the file type handlers array.
    152 	 *
    153 	 * @since 3.3.0
    154 	 *
    155 	 * @param string|null $file_extension - file extension
    156 	 * @return File_Type_Base[]|File_Type_Base
    157 	 */
    158 	public function get_file_type_handlers( $file_extension = null ) {
    159 		return self::get_items( $this->file_type_handlers, $file_extension );
    160 	}
    161 
    162 	/**
    163 	 * Create Temp File
    164 	 *
    165 	 * Create a random temporary file.
    166 	 *
    167 	 * @since 3.3.0
    168 	 *
    169 	 * @param string $file_content
    170 	 * @param string $file_name
    171 	 * @return string|\WP_Error
    172 	 */
    173 	public function create_temp_file( $file_content, $file_name ) {
    174 		$temp_filename = $this->create_unique_dir() . $file_name;
    175 
    176 		file_put_contents( $temp_filename, $file_content ); // phpcs:ignore
    177 
    178 		return $temp_filename;
    179 	}
    180 
    181 	/**
    182 	 * Get Temp Directory
    183 	 *
    184 	 * Get the temporary files directory path. If the directory does not exist, this method creates it.
    185 	 *
    186 	 * @since 3.3.0
    187 	 *
    188 	 * @return string $temp_dir
    189 	 */
    190 	public function get_temp_dir() {
    191 		if ( ! $this->temp_dir ) {
    192 			$wp_upload_dir = wp_upload_dir();
    193 
    194 			$this->temp_dir = implode( DIRECTORY_SEPARATOR, [ $wp_upload_dir['basedir'], 'elementor', 'tmp' ] ) . DIRECTORY_SEPARATOR;
    195 
    196 			if ( ! is_dir( $this->temp_dir ) ) {
    197 				wp_mkdir_p( $this->temp_dir );
    198 			}
    199 		}
    200 
    201 		return $this->temp_dir;
    202 	}
    203 
    204 	/**
    205 	 * Create Unique Temp Dir
    206 	 *
    207 	 * Create a unique temporary directory
    208 	 *
    209 	 * @since 3.3.0
    210 	 *
    211 	 * @return string the new directory path
    212 	 */
    213 	public function create_unique_dir() {
    214 		$unique_dir_path = $this->get_temp_dir() . uniqid() . DIRECTORY_SEPARATOR;
    215 
    216 		wp_mkdir_p( $unique_dir_path );
    217 
    218 		return $unique_dir_path;
    219 	}
    220 
    221 	/**
    222 	 * Are Unfiltered Uploads Enabled
    223 	 *
    224 	 * Checks if the user allowed uploading unfiltered files.
    225 	 *
    226 	 * @since 3.3.0
    227 	 *
    228 	 * @return bool
    229 	 */
    230 	private function are_unfiltered_uploads_enabled() {
    231 		if ( ! $this->are_unfiltered_files_enabled ) {
    232 			$this->are_unfiltered_files_enabled = ! ! get_option( self::UNFILTERED_FILE_UPLOADS_KEY );
    233 		}
    234 
    235 		return $this->are_unfiltered_files_enabled;
    236 	}
    237 
    238 	/**
    239 	 * Add File Extension To Allowed Extensions List
    240 	 *
    241 	 * @since 3.3.0
    242 	 *
    243 	 * @param string $file_type
    244 	 */
    245 	private function add_file_extension_to_allowed_extensions_list( $file_type ) {
    246 		$file_handler = $this->file_type_handlers[ $file_type ];
    247 
    248 		$file_extension = $file_handler->get_file_extension();
    249 
    250 		// Only add the file extension to the list if it doesn't already exist in it.
    251 		if ( ! in_array( $file_extension, $this->allowed_file_extensions, true ) ) {
    252 			$this->allowed_file_extensions[] = $file_extension;
    253 		}
    254 	}
    255 
    256 	/**
    257 	 * Save Base64 as File
    258 	 *
    259 	 * Saves a Base64 string as a .tmp file in Elementor's temporary files directory.
    260 	 *
    261 	 * @since 3.3.0
    262 	 *
    263 	 * @param $file
    264 	 * @return array|\WP_Error
    265 	 */
    266 	private function save_base64_to_tmp_file( $file ) {
    267 		$file_content = base64_decode( $file['fileData'] ); // phpcs:ignore
    268 
    269 		// If the decode fails
    270 		if ( ! $file_content ) {
    271 			return new \WP_Error( 'file_error', self::INVALID_FILE_CONTENT );
    272 		}
    273 
    274 		$temp_filename = $this->create_temp_file( $file_content, $file['fileName'] );
    275 
    276 		if ( is_wp_error( $temp_filename ) ) {
    277 			return $temp_filename;
    278 		}
    279 
    280 		$new_file_array = [
    281 			// the original uploaded file name
    282 			'name' => $file['fileName'],
    283 			// The path to the temporary file
    284 			'tmp_name' => $temp_filename,
    285 		];
    286 
    287 		return $new_file_array;
    288 	}
    289 
    290 	/**
    291 	 * is_elementor_wp_media_upload
    292 	 *
    293 	 * @since 3.3.0
    294 	 *
    295 	 * @return bool
    296 	 */
    297 	private function is_elementor_wp_media_upload() {
    298 		return isset( $_POST['elementor_wp_media_upload'] ); // phpcs:ignore
    299 	}
    300 
    301 	/**
    302 	 * Validate File
    303 	 *
    304 	 * @since 3.3.0
    305 	 *
    306 	 * @param string $file_path
    307 	 * @param array $file_extensions Optional
    308 	 * @return bool|\WP_Error
    309 	 *
    310 	 */
    311 	private function validate_file( $file_path, $file_extensions = [] ) {
    312 		$file_extension = pathinfo( $file_path, PATHINFO_EXTENSION );
    313 
    314 		$allowed_file_extensions = $this->get_allowed_file_extensions();
    315 
    316 		if ( $file_extensions ) {
    317 			$allowed_file_extensions = array_intersect( $allowed_file_extensions, $file_extensions );
    318 		}
    319 
    320 		// Check if the file type (extension) is in the allowed extensions list. If it is a non-standard file type (not
    321 		// enabled by default in WordPress) and unfiltered file uploads are not enabled, it will not be in the allowed
    322 		// file extensions list.
    323 		if ( ! in_array( $file_extension, $allowed_file_extensions, true ) ) {
    324 			return new \WP_Error( Exceptions::FORBIDDEN, 'Uploading this file type is not allowed.' );
    325 		}
    326 
    327 		$file_type_handler = $this->get_file_type_handlers( $file_extension );
    328 
    329 		// If Elementor does not have a handler for this file type, don't block it.
    330 		if ( ! $file_type_handler ) {
    331 			return true;
    332 		}
    333 
    334 		// Here is each file type handler's chance to run its own specific validations
    335 		return $file_type_handler->validate_file( $file_path );
    336 	}
    337 
    338 	/**
    339 	 * Remove File Or Directory
    340 	 *
    341 	 * Directory is deleted recursively with all of its contents (subdirectories and files).
    342 	 *
    343 	 * @since 3.3.0
    344 	 *
    345 	 * @param string $path
    346 	 */
    347 	public function remove_file_or_dir( $path ) {
    348 		if ( is_dir( $path ) ) {
    349 			$this->remove_directory_with_files( $path );
    350 		} else {
    351 			unlink( $path );
    352 		}
    353 	}
    354 
    355 	/**
    356 	 * Remove Directory with Files
    357 	 *
    358 	 * @since 3.3.0
    359 	 *
    360 	 * @param string $dir
    361 	 * @return bool
    362 	 */
    363 	private function remove_directory_with_files( $dir ) {
    364 		$dir_iterator = new \RecursiveDirectoryIterator( $dir, \RecursiveDirectoryIterator::SKIP_DOTS );
    365 
    366 		foreach ( new \RecursiveIteratorIterator( $dir_iterator, \RecursiveIteratorIterator::CHILD_FIRST ) as $name => $item ) {
    367 			if ( is_dir( $name ) ) {
    368 				rmdir( $name );
    369 			} else {
    370 				unlink( $name );
    371 			}
    372 		}
    373 
    374 		return rmdir( $dir );
    375 	}
    376 
    377 	/**
    378 	 * Get Allowed File Extensions
    379 	 *
    380 	 * Retrieve an array containing the list of file extensions allowed for upload.
    381 	 *
    382 	 * @since 3.3.0
    383 	 *
    384 	 * @return array file extension/s
    385 	 */
    386 	private function get_allowed_file_extensions() {
    387 		if ( ! $this->allowed_file_extensions ) {
    388 			$this->allowed_file_extensions = array_keys( get_allowed_mime_types() );
    389 
    390 			foreach ( $this->get_file_type_handlers() as $file_type => $handler ) {
    391 				if ( $handler->is_upload_allowed() ) {
    392 					// Add the file extension to the allowed extensions list only if unfiltered files upload is enabled.
    393 					$this->add_file_extension_to_allowed_extensions_list( $file_type );
    394 				}
    395 			}
    396 		}
    397 
    398 		return $this->allowed_file_extensions;
    399 	}
    400 
    401 	public function __construct() {
    402 		$this->register_file_types();
    403 
    404 		add_filter( 'wp_handle_upload_prefilter', [ $this, 'handle_elementor_wp_media_upload' ] );
    405 	}
    406 }