Difference between #3 and #4 of XUpload Workflow

unchanged
Title
XUpload Workflow
unchanged
Category
Tutorials
unchanged
Tags
xupload, Forms, Widgets, tutorial
changed
Content
This wiki describesToday I'm going to describe a more
complex workflow using the
[XUpload](http://www.yiiframework.com/extension/xupload/ "XUpload")
widget


If you want to know how to send additional data with your files like title, or
description, check [this other
wiki](http://www.yiiframework.com/wiki/395/additional-form-data-with-xupload/
"sending additional data")

The workflow will basically go like this:

Workflow
------------------

![Workflow](http://i45.tinypic.com/293xbpw.jpg "Workflow")

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

~~~
[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

~~~
[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]
<?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]
<?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.



~~~
[php]
//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.