• Resolved Mr.Meerkat

    (@mrmeerkat)


    I would like to have an upload field in the backend that allows logged in users to upload zip files that are then unzipped to a subdirectory in the upload folder.

    Is this possible with CMB2 or generally in WordPress? Does anyone know of an example, additional CMB2 plugin or snippet for this?

    The reason why I need this is the following: I want to create a custom post type for multiresolution panoramas (framework Marzipano).

    Such panoramas are based on an image pyramid of tiles (similar to inactive maps like Google Maps or OpenStreetMap). Hundreds of image tiles quickly accumulate, so that a manual file upload is not an option.

    Here is an example, how a muliresolution panorama looks like: https://www.marzipano.net/demos/cube-multi-res/

    • This topic was modified 1 year, 10 months ago by Mr.Meerkat.
Viewing 5 replies - 1 through 5 (of 5 total)
  • Plugin Contributor Michael Beckwith

    (@tw2113)

    The BenchPresser

    I don’t know how easily the unzipping aspect would be, CMB2 or not, but I know that CMB2 has file and file list fields available

    https://github.com/CMB2/CMB2/wiki/Field-Types#file

    https://github.com/CMB2/CMB2/wiki/Field-Types#file_list

    Thread Starter Mr.Meerkat

    (@mrmeerkat)

    Thanks for the answer. I didn’t think that would work for zip files. Is there a way to register a callback that is called when the upload is complete?

    Plugin Contributor Michael Beckwith

    (@tw2113)

    The BenchPresser

    Closest I can think of would be https://github.com/CMB2/CMB2/blob/develop/includes/CMB2_Field.php#L583-L607 but that’s after saving the meta field value, not necessarily after the file upload is completed. Worth a shot.

    Thread Starter Mr.Meerkat

    (@mrmeerkat)

    Thanks!

    Thread Starter Mr.Meerkat

    (@mrmeerkat)

    The Upload of zip-Files with CMB2 as suggested by Michael works fine. Thanks again! Just for the case someone likes to do the same:

    I found two solutions for the unzip problem.

    First solution: use the updated_post_meta action hook to unzip if the value and validate whenever zipfieldname_id changes (Note here that the _id suffix must be attached to the field name!)

    class Unzipper {
    
    	/**
    	 * Run all Hooks.
    	 *
    	 * @return void
    	 */
    	public function init() {
    		require_once ABSPATH . 'wp-admin/includes/file.php';
    		WP_Filesystem();
    		add_action( 'updated_post_meta', array( $this, 'unzip_and_validate' ), 10, 4 );
    	}
    
    	/**
    	 * Executed when a new zipfile is assigned to my_cpt
    	 * Unpacks the zip-file checks if its content meets the requirements.
    	 *
    	 * @param int    $meta_id ID of updated metadata entry.
    	 * @param int    $post_id ID of the object metadata is for.
    	 * @param string $meta_key Metadata key.
    	 * @param mixed  $meta_value Metadata value.
    	 *
    	 * @return void
    	 */
    	public function unzip_and_validate( $meta_id, $post_id, $meta_key, $meta_value ) {
    		if ( 'panorama_zip_id' !== $meta_key ) {
    			return;
    		}
    
    		write_log( "new panorama_zip_id: {$meta_value}" );
    
    		global $wp_filesystem;
    
    		$zip_file_path = get_attached_file( $meta_value );
    		$upload_array  = wp_upload_dir();
    		$upload_folder = $upload_array['basedir'];
    		$dest_folder   = "{$upload_folder}/my_plugin_folder/{$post_id}";
    
    		$wp_filesystem->delete( $dest_folder, true, 'd' );
    
    		wp_mkdir_p( $dest_folder );
    
    		unzip_file( $zip_file_path, $dest_folder );
    // validation
    ...

    2. Solution (More complicated but also more user friendly): Use a JS-Observer and Ajax. The advantage here is that we can give the user validation feedback as soon as he selects a new zip file.

    In your admin JavaScript file use an observer to run a function everytime the value of the hidden field zipfieldname_id (Again: Note the id suffix) changes. (A simple change event wont fire, if the value is not changed by the user but by WordPress scripts, so you have to use an observer here).

    The function called by the Observer will send an Ajax request, that sends the zipfile id. The invoked PHP-Funktion will find the zipfile by its id and unzips it, validates the unzipped folder and sends a validation message or code in response.

    Use this response in JavaScript to add a validation message to output a validation feedback to the admin page.

    If your validation prosses works fine, you can prevent the zip file to be unziped and validated more the one time, by storing a validation code in the meta table after validation and check if such a code is stored in the meta table, before you unzip and validat it.

    Admittedly: quite elaborate, but as user friendly as possible.

    Here is my JS-Code

    jQuery( function( $ ) {
    
    	if ( $( 'body.post-type-tree_panorama' ).length ) {
    
    		const url = zipAjaxObject.ajaxURL;
    		const data = {};
    		data.action = 'zip-validation';
    		data.nonce = zipAjaxObject.nonce;
    
    		// Observe if the zip file is changing
    
    		const hiddenZip         = document.getElementById( 'panorama_zip_id' );
    		const validationMessage = $( 'div.cmb2-id-panorama-zip div.validation' );
    
    		function ajaxValidationRequest() {
    			let zipId = hiddenZip.getAttribute( 'value' );
    			data.zipId = zipId;
    
    			$.ajax({
    				url: url,
    				type: 'post',
    				data: data,
    				success( response ) {
    					response = JSON.parse( response );
    					console.log( response.notice );
    
    					validationMessage.html( response.notice );
    				}
    			});
    		}
    
    		ajaxValidationRequest();
    
    		const mutationCallback = ( mutationsList ) => {
    			for ( const mutation of mutationsList ) {
    
    				if ( 'attributes' === mutation.type	&& 'value' === mutation.attributeName ) {
    					ajaxValidationRequest();
    				}
    			}
    		};
    
    		const observer = new MutationObserver( mutationCallback );
    
    		observer.observe( hiddenZip, { attributes: true });
    
    	}
    });
    

    And some of my php code:

    <?php
    /**
     * Ajax-Resopnse on panorama_zip_id change.
     *
     * @link       https://digidrom.de
     * @since      1.0.0
     *
     * @package    digipano
     * @subpackage digipano/includes
     */
    
    namespace digidrom\digipano;
    
    use digidrom\digipano\ZipError;
    
    /**
     * Handels Ajax requests that is send when ever the panorama_zip_id of a panoaram_tree changes.
     *
     * @package    Digipano
     * @author     Jan Tack <[email protected]>
     */
    class ZipAjax {
    
    	/**
    	 * Run all Hooks.
    	 *
    	 * @return void
    	 */
    	public function init() {
    		add_action( 'admin_enqueue_scripts', array( $this, 'load' ) );
    		add_action( 'wp_ajax_zip-validation', array( $this, 'response_ajax' ) );
    	}
    
    	/**
    	 * The user should get an instant feedback when selecting a zip file for a tree-panoram,
    	 * even before adas post form is submitted.
    	 *
    	 * Therefore this function is called via Ajax whenever the user selects a new zip file.
    	 * The zip file is then unpacked and validated. The response to the Ajax request then contains
    	 * a div container with a matching success or error message.
    	 *
    	 * @return void
    	 */
    	public function response_ajax() {
    		// phpcs:ignore
    		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'ajax-nonce' ) ) {
    			die( 'YOU SHALL NOT PASS!' );
    		}
    
    		if ( isset( $_POST['zipId'] ) ) {
    			$zip_id = sanitize_text_field( wp_unslash( $_POST['zipId'] ) );
    
    			// $note = get_post_meta( $zip_id, 'zip-validation', true );
    
    			$note = '';
    			if ( '' === $note ) {
    
    				$note = $this->unzip_and_validate( $zip_id );
    
    				if ( 'allright' === $note ) {
    					$upload_array  = wp_upload_dir();
    					$upload_folder = $upload_array['basedir'];
    					$source_dir    = "{$upload_folder}/unzip/{$zip_id}/";
    
    				}
    
    				update_post_meta( $zip_id, 'zip-validation', $note );
    			}
    
    			$response = array(
    				'zipId'  => $zip_id,
    				'note'   => $note,
    				'notice' => ZipError::get_notice( $note ),
    			);
    
    			echo wp_json_encode( $response );
    		}
    
    		die();
    	}
    
    	/**
    	 * Enqueue script and inject data
    	 *
    	 * @return void
    	 */
    	public function load() {
    		wp_enqueue_script( 'zip-script', plugins_url( '/js/zipvalidation.js', DIGIPANO_FILE ), array( 'jquery' ), '1.0.0', true );
    
    		$zip_ajax_object = array(
    			'ajaxURL' => admin_url( 'admin-ajax.php' ),
    			'nonce'   => wp_create_nonce( 'ajax-nonce' ),
    		);
    
    		wp_localize_script( 'zip-script', 'zipAjaxObject', $zip_ajax_object );
    	}
    
    	/**
    	 * Unzip and validate
    	 *
    	 * @param int $zip_id The id of the zip file.
    	 *
    	 * @return ZipError
    	 */
    	private function unzip_and_validate( $zip_id ) {
    		global $wp_filesystem;
    
    		$upload_array  = wp_upload_dir();
    		$upload_folder = $upload_array['basedir'];
    		$dest_folder   = "{$upload_folder}/unzip/{$zip_id}/";
    
    		if ( $wp_filesystem->exists( $dest_folder ) ) {
    			$wp_filesystem->delete( $dest_folder, true, 'd' );
    		}
    		wp_mkdir_p( $dest_folder );
    
    		$zip_file = get_attached_file( $zip_id );
    		unzip_file( $zip_file, $dest_folder );
    
    		require_once __DIR__ . '/class-unzipvalidator.php';
    
    		$validator = new UnzipValidator( $dest_folder );
    		return $validator->validate_and_sanitize();
    	}
    }
    

Viewing 5 replies - 1 through 5 (of 5 total)
  • The topic ‘zip file upload’ is closed to new replies.