XUpload Workflow

36 followers

This wiki describes a more complex workflow using the XUpload widget

If you want to know how to send additional data with your files like title, or description, check this other wiki

The workflow will basically go like this:

Workflow

Workflow

Action to show the form

First we need to create the action that will show the form to the user:

// application/controllers/FancyController.php
 
public function actionForm( ) {
    $model = new SomeModel;
    Yii::import( "xupload.models.XUploadForm" );
    $photos = new XUploadForm;
    $this->render( 'form', array(
        'model' => $model,
        'photos' => $photos,
    ) );
}

The view file

And the view form that will display 'SomeModel' fields and our XUpload widget

// application/views/fancy/form.php
 
<fieldset>
    <?php
    $form = $this->beginWidget('CActiveForm', array(
          'id' => 'somemodel-form',
          'enableAjaxValidation' => false,
            //This is very important when uploading files
          'htmlOptions' => array('enctype' => 'multipart/form-data'),
        ));
      ?>    
        <div class="row">
            <?php echo $form->labelEx($model,'field1'); ?>
            <?php echo $form->textField($model,'field1'); ?>
            <?php echo $form->error($model,'field1'); ?>
        </div>
        <!-- Other Fields... -->
        <div class="row">
            <?php echo $form->labelEx($model,'photos'); ?>
            <?php
            $this->widget( 'xupload.XUpload', array(
                'url' => Yii::app( )->createUrl( "/controller/upload"),
                //our XUploadForm
                'model' => $photos,
                //We set this for the widget to be able to target our own form
                'htmlOptions' => array('id'=>'somemodel-form'),
                'attribute' => 'file',
                'multiple' => true,
                //Note that we are using a custom view for our widget
                //Thats becase the default widget includes the 'form' 
                //which we don't want here
                'formView' => 'application.views.somemodel._form',
                )    
            );
            ?>
        </div>
        <button type="submit">Submit</button>
    <?php $this->endWidget(); ?>
</fieldset>

The Upload Action

Now that we have our XUpload widget in place, we need to create the action that will handle the upload of files (we specified as controller/upload), so lets go ahead and create this action. I just copy/pasted the code from the XUploadAction in my own controller and edited as necessary.

<?php
// application/controllers/FancyController.php
 
public function actionUpload( ) {
    Yii::import( "xupload.models.XUploadForm" );
    //Here we define the paths where the files will be stored temporarily
    $path = realpath( Yii::app( )->getBasePath( )."/../images/uploads/tmp/" )."/";
    $publicPath = Yii::app( )->getBaseUrl( )."/images/uploads/tmp/";
 
    //This is for IE which doens't handle 'Content-type: application/json' correctly
    header( 'Vary: Accept' );
    if( isset( $_SERVER['HTTP_ACCEPT'] ) 
        && (strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) !== false) ) {
        header( 'Content-type: application/json' );
    } else {
        header( 'Content-type: text/plain' );
    }
 
    //Here we check if we are deleting and uploaded file
    if( isset( $_GET["_method"] ) ) {
        if( $_GET["_method"] == "delete" ) {
            if( $_GET["file"][0] !== '.' ) {
                $file = $path.$_GET["file"];
                if( is_file( $file ) ) {
                    unlink( $file );
                }
            }
            echo json_encode( true );
        }
    } else {
        $model = new XUploadForm;
        $model->file = CUploadedFile::getInstance( $model, 'file' );
        //We check that the file was successfully uploaded
        if( $model->file !== null ) {
            //Grab some data
            $model->mime_type = $model->file->getType( );
            $model->size = $model->file->getSize( );
            $model->name = $model->file->getName( );
            //(optional) Generate a random name for our file
            $filename = md5( Yii::app( )->user->id.microtime( ).$model->name);
            $filename .= ".".$model->file->getExtensionName( );
            if( $model->validate( ) ) {
                //Move our file to our temporary dir
                $model->file->saveAs( $path.$filename );
                chmod( $path.$filename, 0777 );
                //here you can also generate the image versions you need 
                //using something like PHPThumb
 
 
                //Now we need to save this path to the user's session
                if( Yii::app( )->user->hasState( 'images' ) ) {
                    $userImages = Yii::app( )->user->getState( 'images' );
                } else {
                    $userImages = array();
                }
                 $userImages[] = array(
                    "path" => $path.$filename,
                    //the same file or a thumb version that you generated
                    "thumb" => $path.$filename,
                    "filename" => $filename,
                    'size' => $model->size,
                    'mime' => $model->mime_type,
                    'name' => $model->name,
                );
                Yii::app( )->user->setState( 'images', $userImages );
 
                //Now we need to tell our widget that the upload was succesfull
                //We do so, using the json structure defined in
                // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup
                echo json_encode( array( array(
                        "name" => $model->name,
                        "type" => $model->mime_type,
                        "size" => $model->size,
                        "url" => $publicPath.$filename,
                        "thumbnail_url" => $publicPath."thumbs/$filename",
                        "delete_url" => $this->createUrl( "upload", array(
                            "_method" => "delete",
                            "file" => $filename
                        ) ),
                        "delete_type" => "POST"
                    ) ) );
            } else {
                //If the upload failed for some reason we log some data and let the widget know
                echo json_encode( array( 
                    array( "error" => $model->getErrors( 'file' ),
                ) ) );
                Yii::log( "XUploadAction: ".CVarDumper::dumpAsString( $model->getErrors( ) ),
                    CLogger::LEVEL_ERROR, "xupload.actions.XUploadAction" 
                );
            }
        } else {
            throw new CHttpException( 500, "Could not upload file" );
        }
    }
}

Handle submitted form

Now that we have uploaded our images/files, we need to process the form data the user submits using the normal form.

To do that, lets modify our original action, the one with the form, and handle the submitted data.

<?php
 
// application/controllers/FancyController.php
 
public function actionForm( ) {
    $model = new SomeModel;
    Yii::import( "xupload.models.XUploadForm" );
    $photos = new XUploadForm;
    //Check if the form has been submitted
    if( isset( $_POST['SomeModel'] ) ) {
        //Assign our safe attributes
        $model->attributes = $_POST['SomeModel'];
        //Start a transaction in case something goes wrong
        $transaction = Yii::app( )->db->beginTransaction( );
        try {
            //Save the model to the database
            if($model->save()){
                $transaction->commit();
            }
        } catch(Exception $e) {
            $transaction->rollback( );
            Yii::app( )->handleException( $e );
        }
    }
    $this->render( 'form', array(
        'model' => $model,
        'photos' => $photos,
    ) );
}

And That's all, nothing fancy, thanks for reading.

Really? aren't you going to ask about moving our files to their final destination? well keep reading.

Moving Files to their final destination

We don't want to put that logic into the controller, I like it slim, we have enough in our controller with the upload action. so instead, lets put that logic in the afterSave method of our model, so we don't have to add/edit our controller.

//in protected/models/SomeModel.php
public function afterSave( ) {
    $this->addImages( );
    parent::afterSave( );
}
 
public function addImages( ) {
    //If we have pending images
    if( Yii::app( )->user->hasState( 'images' ) ) {
        $userImages = Yii::app( )->user->getState( 'images' );
        //Resolve the final path for our images
        $path = Yii::app( )->getBasePath( )."/../images/uploads/{$this->id}/";
        //Create the folder and give permissions if it doesnt exists
        if( !is_dir( $path ) ) {
            mkdir( $path );
            chmod( $path, 0777 );
        }
 
        //Now lets create the corresponding models and move the files
        foreach( $userImages as $image ) {
            if( is_file( $image["path"] ) ) {
                if( rename( $image["path"], $path.$image["filename"] ) ) {
                    chmod( $path.$image["filename"], 0777 );
                    $img = new Image( );
                    $img->size = $image["size"];
                    $img->mime = $image["mime"];
                    $img->name = $image["name"];
                    $img->source = "/images/uploads/{$this->id}/".$image["filename"];
                    $img->somemodel_id = $this->id;
                    if( !$img->save( ) ) {
                        //Its always good to log something
                        Yii::log( "Could not save Image:\n".CVarDumper::dumpAsString( 
                            $img->getErrors( ) ), CLogger::LEVEL_ERROR );
                        //this exception will rollback the transaction
                        throw new Exception( 'Could not save Image');
                    }
                }
            } else {
                //You can also throw an execption here to rollback the transaction
                Yii::log( $image["path"]." is not a file", CLogger::LEVEL_WARNING );
            }
        }
        //Clear the user's session
        Yii::app( )->user->setState( 'images', null );
    }
}

And that's it. if you have questions please ask.

Total 18 comments

#12636 report it
venuk at 2013/04/02 05:32pm
resolved

Asgaroth, Thanks lot for quick reply.

I am able to get temporary uploaded file path using "$model->{$this->fileAttribute}->tempName", so files are successfully uploading Amazon S3 storage.

This extension is great. I am just trying to find an efficient way to resize image before or after uploading to AWS S3.

#12635 report it
Asgaroth at 2013/04/02 03:34pm
@venuk

I havent work with Amazon S3, so I wouldn't really know what you need, but if you need the full path to a file on your server, you can use realpath

#12634 report it
venuk at 2013/04/02 03:10pm
Source file path before upload

I am trying to upload to Amazon S3 which is expecting source file path as parameter. How Can I get the full path of selected file?

#12354 report it
xNicox at 2013/03/15 10:24am
Upload filtered by file type

Hi @Asgaroth, yes I changed , multiple option to false, and now I can upload one file.

but I can't make xUpload to filter specific file types. It's not a problem with the extension. May be you can figure it out how to asign options to "acceptFileTypes" jquery upload file function.

I post and entry in the forum here

Best regards

#12352 report it
Asgaroth at 2013/03/15 09:28am
@xNicox

This is more for multifile uploads, but you can use it for single uploads if you wish. kind of misses the point but... whatever

#12345 report it
xNicox at 2013/03/14 11:15pm
Upload a simple txt file....

Hi, is this worth to upload a simple txt file ? I test the extension and it upload a txt file, but I con't find how to open dialog box, filtering by file extension... you know : "Text File (.txt)" legend.

Best Regards. Nicolas

#10764 report it
Ajmalkhan at 2012/11/22 04:57am
Have called the view file, i got a one error...

Alias "xupload.models.XUploadForm" is invalid. Make sure it points to an existing directory or file.

Please help me how to rectify dis... b'coz im newbie

done with extract the xupload extension also...

#10626 report it
Asgaroth at 2012/11/08 05:31pm
@innocube

I just added minimal file hinting, so its clearer, but explaining further is out of the scope of this article. have you gone through the guide and the blog demo?

#10621 report it
innocube at 2012/11/08 10:30am
File Names

Hi Asgaroth,

Great work! Thank you so much for such a good work. I am a novice in yii and tyring to build something based on your XUpload module. As I go through with your "XUpload Workflow" tutorial, it is kind of hard to follow because most of the time you did not mention which file I need to put the code in. This must be a trivial issue to experienced yii developers. But it is still confusing and don't know how to proceed for beginners like me. If you could kindly note where I need to put each code segment, it will be a great help! Thank you for a good work again!

#8983 report it
Asgaroth at 2012/07/11 10:52am
@vinodc

Check this post, and please use the forum for support

#8977 report it
VinodC at 2012/07/11 07:30am
Thanks

Thank you very much Asgaroth
it's nice extension that's why i never gave up.
I have one more doubt - can we restrict number of files to be uploaded?

#8966 report it
Asgaroth at 2012/07/10 10:30am
@vinodc

Hi there, I' sorry you are totally right, I just corrected the wiki, the correct param name its 'formView'.

#8962 report it
VinodC at 2012/07/10 05:18am
xupload-v0.4+demo.zip

I think i use the current version.when i remove the form tags of extensions/xupload/views/form.php (and hide formTemplate) it works.i'm near to it but dont know the problem.

#8949 report it
Asgaroth at 2012/07/09 10:09am
@vinodc

What version of the plugin are you using? looks like you are using an outdated version.

#8937 report it
VinodC at 2012/07/09 05:01am
Property "XUpload.formTemplate" is not defined.

I created custom view in site(_form.php) of your demo and copy the contents of extensions/xupload/views/form.php to _form.php and removed the form tags.
Then i referenced it .

'formTemplate' => 'application.views.site._form',

But it shows an error Property "XUpload.formTemplate" is not defined.

#8920 report it
Asgaroth at 2012/07/07 01:44pm
@vinodc

Please check the comment on the xupload configuration

//Note that we are using a custom view for our widget
//Thats becase the default widget includes the 'form' 
//which we don't want here
'formTemplate' => 'application.views.somemodel._form',

What you need to do its, create view, copy the contents of extensions/xupload/views/form.php to your custom view, remove the form tags, and reference that view in the widget configuration.

The reason the xupload widget its not working, for you its because its nesting 2 forms.

Also, please ask your questions in the forum

#8918 report it
VinodC at 2012/07/07 08:00am
Demo

Could you please upload a demo of xupload with other model.
It will help all.
Thanks in advance!!!

#8914 report it
VinodC at 2012/07/07 01:55am
Cannot submit form

i cannot submit form i added image schema with one field title and id(primary, auto inc) if i remove xupload part it will work

controller

public function actionIndex() {
        // renders the view file 'protected/views/site/index.php'
        // using the default layout 'protected/views/layouts/main.php'
            $model=new Images;
    Yii::import( "xupload.models.XUploadForm" );
    $photos = new XUploadForm;
    //Check if the form has been submitted
    if( isset( $_POST['Images'] ) ) {
        //Assign our safe attributes
        $model->attributes = $_POST['Images'];
        //Start a transaction in case something goes wrong
        $transaction = Yii::app( )->db->beginTransaction( );
        try {
            //Save the model to the database
            if($model->save()){
                $transaction->commit();
            }
        } catch(Exception $e) {
            $transaction->rollback( );
            Yii::app( )->handleException( $e );
        }
    }
    $this->render( 'index', array(
        'model' => $model,
        'photos' => $photos,
    ) );
    }

view

<?php
    $form = $this->beginWidget('CActiveForm', array(
          'id' => 'images-form',
          'enableAjaxValidation' => false,
            //This is very important when uploading files
          'htmlOptions' => array('enctype' => 'multipart/form-data'),
        ));
      ?>    
        <p class="note">Fields with <span class="required">*</span> are required.</p>
 
    <?php echo $form->errorSummary($model); ?>
        <div class="row">
            <?php echo $form->labelEx($model,'title'); ?>
            <?php echo $form->textField($model,'title'); ?>
            <?php echo $form->error($model,'title'); ?>
        </div>
        <!-- Other Fields... -->
        <div class="row">
            <?php echo $form->labelEx($model,'file'); ?>
            <?php
            $this->widget( 'xupload.XUpload', array(
                'url' => Yii::app( )->createUrl( "/site/upload"),
                //our XUploadForm
                'model' => $photos,
                //We set this for the widget to be able to target our own form
                'htmlOptions' => array('id'=>'images-form'),
                'attribute' => 'file',
                'multiple' => true,
                )    
            );
            ?>
        </div>
  <?php echo CHtml::submitButton('Submit'); ?>
    <?php $this->endWidget(); ?>

upload function

public function actionUpload( ) {
    Yii::import( "xupload.models.XUploadForm" );
    //Here we define the paths where the files will be stored temporarily
    $path = realpath( Yii::app( )->getBasePath( )."/../images/uploads/tmp/" )."/";
    $publicPath = Yii::app( )->getBaseUrl( )."/images/uploads/tmp/";
 
    //This is for IE which doens't handle 'Content-type: application/json' correctly
    header( 'Vary: Accept' );
    if( isset( $_SERVER['HTTP_ACCEPT'] ) 
        && (strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) !== false) ) {
        header( 'Content-type: application/json' );
    } else {
        header( 'Content-type: text/plain' );
    }
 
    //Here we check if we are deleting and uploaded file
    if( isset( $_GET["_method"] ) ) {
        if( $_GET["_method"] == "delete" ) {
            if( $_GET["file"][0] !== '.' ) {
                $file = $path.$_GET["file"];
                if( is_file( $file ) ) {
                    unlink( $file );
                }
            }
            echo json_encode( true );
        }
    } else {
        $model = new XUploadForm;
        $model->file = CUploadedFile::getInstance( $model, 'file' );
        //We check that the file was successfully uploaded
        if( $model->file !== null ) {
            //Grab some data
            $model->mime_type = $model->file->getType( );
            $model->size = $model->file->getSize( );
            $model->name = $model->file->getName( );
            //(optional) Generate a random name for our file
            $filename = md5( Yii::app( )->user->id.microtime( ).$model->name);
            $filename .= ".".$model->file->getExtensionName( );
            if( $model->validate( ) ) {
                //Move our file to our temporary dir
                $model->file->saveAs( $path.$filename );
                chmod( $path.$filename, 0777 );
                //here you can also generate the image versions you need 
                //using something like PHPThumb
 
 
 
 
                //Now we need to tell our widget that the upload was succesfull
                //We do so, using the json structure defined in
                // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup
                echo json_encode( array( array(
                        "name" => $model->name,
                        "type" => $model->mime_type,
                        "size" => $model->size,
                        "url" => $publicPath.$filename,
                        "thumbnail_url" => $publicPath.$filename,
                        "delete_url" => $this->createUrl( "upload", array(
                            "_method" => "delete",
                            "file" => $filename
                        ) ),
                        "delete_type" => "POST"
                    ) ) );
            } else {
                //If the upload failed for some reason we log some data and let the widget know
                echo json_encode( array( 
                    array( "error" => $model->getErrors( 'file' ),
                ) ) );
                Yii::log( "XUploadAction: ".CVarDumper::dumpAsString( $model->getErrors( ) ),
                    CLogger::LEVEL_ERROR, "xupload.actions.XUploadAction" 
                );
            }
        } else {
            throw new CHttpException( 500, "Could not upload file" );
        }
    }
}

please help me

thanks in advance..

Leave a comment

Please to leave your comment.

Write new article