YiiBooster: How to implement files and images upload on TbRedactor Widget

Introduction

I have been requested to provide a tutorial on how to use the file upload feature of the TbRedactor widget, and even though I am no longer supporting the library due that the work belongs to Clevertech, I feel that I owe that to the Yii developers. So, here is the  tutorial.

How to do it

As you know, if you try to use TbRedactor “out of the box” and if you click on the “insert image”, you will see a “popup window” as the following image:

Captura de pantalla 2013-03-24 a la(s) 15.34.57

Not bad… but too plain. What about the really cool Fileupload shown on redactorJS site? How do we set it up? The answer is easier that you can imagine. In fact, the only thing that we need to tell the plugin is the URL address that will handle file and image upload processes.

// with a TbActiveForm
echo $form->redactorRow(
    $model,
    'attribute_name',
    array(
       'class'=>'span4',
       'rows'=>5,
       'options'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
       )
    )
);

// as a widget
$this->widget('bootstrap.widgets.TbRedactorJs',
    array(
      'model'=>$model,
      'attribute'=>'attribute_name',
      'editorOptions'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
          'width'=>'100%',
          'height'=>'400px'
       )
    ));

As soon as we have that setup, the popup window will display differently:

Files Upload
File Upload
Images Upload
Images Upload
Important How to handle file uploads is out of the scope of this tutorial. If you wish to know how to handle file uploads on the server from redactorJS, I highly recommend you to read the documentation at imperavi.

But wait a minute, what about if we wish to insert an image that has been previously uploaded? If we look at the redactor’s site we find really cool thumbnail images to choose from. Well, is a bit tricky but not too hard. The only thing that you have to do is to provide a URL where to download the “json” file that will get the uploaded files and their location.

// with a TbActiveForm
echo $form->redactorRow(
    $model,
    'attribute_name',
    array(
       'class'=>'span4',
       'rows'=>5,
       'options'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
          'imageGetJson'=> $this->createUrl('site/testImageThumbs')
       )
    )
);

// as a widget
$this->widget('bootstrap.widgets.TbRedactorJs',
    array(
      'model'=>$model,
      'attribute'=>'attribute_name',
      'editorOptions'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
          'imageGetJson'=> $this->createUrl('site/testImageThumbs')
          'width'=>'100%',
          'height'=>'400px'
       )
    ));

The following is an example action to return the thumbs and image information:

public function actionTestUpload()
{
   echo json_encode(array(
        array(
           'thumb' => '/images/image1_thumb.png',
           'image' => '/images/image1_big.png',
           'title' => 'Title1', // optional
           'folder' => 'myFolder' // optional
        ),
        array(
           'thumb' => '/images/image2_thumb.png',
           'image' => '/images/image2_big.png',
           'title' => 'Title2', // optional
           'folder' => 'myFolder' // optional
        )
   ));
}

Obviously, the above example has to contain the right resources to work but the result would endup with something similar to the following:

Images Choose
Images Choose Option

Further Reading


[ad]

YiiBooster: How to use TbFileUpload Widget

Introduction

The following wiki is to explain how to make use of the TbFileUpload widget from YiiBooster. I have received a couple of requests and I thought was worth writing a wiki for it.

Usage

Preparation

First, we need to make sure we have a folder with write permissions, that will be the place where we are going to save our uploaded files.

I normally create a files folder and I allocate it within my www one, that is where I keep my css, js, images files. So, even though you can place it where ever you wish and for the sake of the example, I will use thefiles folder name.

I also assume that you have configured a path alias that references the files folder. On this example, the path alias is frontend.www.files.

Note

  • This demo only works if the MyModel class already includes the function getImageUrl($img_type=null). If not, comment the 2 lines inside the data array, where ‘url’ and ‘thumbnail_url’ are defined, to make the demo work
  • If the MyModel has a different name, it needs to be replaced 1-2 times inside the upload action
  • The url where we are going to upload our files => upload action

Thanks Don Felipe

Setup your model

The way you handle validation on the server is up to you, the configuration I am going to provide now is just an example.

We are going to add one extra attribute to the model picture, which is going to hold any $_FILE type resource uploaded and validated when model is on upload scenario.

class MyModel extends CActiveRecord {
// ... more code here
/**
* This is the attribute holding the uploaded picture
* @var CUploadedFile
*/
public $picture;
// ... more code

/**
* @return array validation rules for model attributes.
*/
public function rules()
{
  return array(
    // ... more rules here
    array('picture', 'length', 'max' => 255, 'tooLong' => '{attribute} is too long (max {max} chars).', 'on' => 'upload'),
    array('picture', 'file', 'types' => 'jpg,jpeg,gif,png', 'maxSize' => 1024 * 1024 * 2, 'tooLarge' => 'Size should be less then 2MB !!!', 'on' => 'upload'),
    // ... more rules here
  );
}

Configuring the Widget

To render the widget on your view is quite straightforward, the are a couple of things very important to configure:

  • the url where we are going to upload our files
  • the file types we are going to accept and their maximum size

Now, lets render it:

<!--?php $this--->widget('bootstrap.widgets.TbFileUpload', array(
    'url' => $this->createUrl("my/upload"),
    'model' => $model,
    'attribute' =>'picture', // see the attribute?
    'multiple' => true,
    'options' => array(
    'maxFileSize' => 2000000,
    'acceptFileTypes' => 'js:/(\.|\/)(gif|jpe?g|png)$/i',
))); ?>

Handling Upload

Everything is ready now but the controller. We have to configure the action that will handle the upload process. Here you go, the upload action of our controller -very, very basic – and not fully tested:

class myController extends CController {
   // ... more code here/**
   * Handles resource upload
   * @throws CHttpException
   */
   public function actionUpload()
   {
      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');
      }
      $data = array();

      $model = new MyModel('upload');
      $model->picture = CUploadedFile::getInstance($model, 'picture');
      if ($model->picture !== null && $model->validate(array('picture')))
      {
         $model->picture->saveAs(
            Yii::getPathOfAlias('frontend.www.files').'/'.$model->picture->name);
         $model>file_name = $model->picture->name;
         // save picture name
         if( $model->save())
         {
            // return data to the fileuploader
            $data[] = array(
            'name' => $model->picture->name,
            'type' => $model->picture->type,
            'size' => $model->picture->size,
            // we need to return the place where our image has been saved
            'url' => $model->getImageUrl(), // Should we add a helper method?
            // we need to provide a thumbnail url to display on the list
            // after upload. Again, the helper method now getting thumbnail.
            'thumbnail_url' => $model->getImageUrl(MyModel::IMG_THUMBNAIL),
            // we need to include the action that is going to delete the picture
            // if we want to after loading
            'delete_url' => $this->createUrl('my/delete',
            array('id' => $model->id, 'method' => 'uploader')),
               'delete_type' => 'POST');
         } else {
               $data[] = array('error' => 'Unable to save model after saving picture');
         }
      } else {
         if ($model->hasErrors('picture'))
         {
            $data[] = array('error', $model->getErrors('picture'));
         } else {
            throw new CHttpException(500, "Could not upload file ". CHtml::errorSummary($model));
         }
      }
      // JQuery File Upload expects JSON data
      echo json_encode($data);
   }

   // .... more code here

} // end of controller

Resources

Implementing a User Level Access System

Yii Framework

I would like to explain this time how easy is to implement a level access system with Yii framework.

Please note that this article is a simple example and good security should be taken into account when we play with authentication systems.

Step 1: Setting Up

a. Include a field on your user’s table named, yep you guessed, level
b. Create an object ‘LevelLookUp’ that will tell us who is who on this system

class LevelLookUp{
      const MEMBER = 0;
      const ADMIN  = 2;
      // For CGridView, CListView Purposes
      public static function getLabel( $level ){
          if($level == self::MEMBER)
             return 'Member';
          if($level == self::ADMIN)
             return 'Administrator';
          return false;
      }
      // for dropdown lists purposes
      public static function getLevelList(){
          return array(
                 self::MEMBER=>'Member',
                 self::ADMIN=>'Administrator');
	}
}

c.Modifying UserIdentity so we can store the user Model id on authentication, as CWebUser’s default id is set to the user Model’s username:

class UserIdentity extends CUserIdentity
{
	private $_id;
	/**
	 * Authenticates a user.
	 * @return boolean whether authentication succeeds.
	 */
	public function authenticate()
	{

		$username = strtolower($this->username);
                // from database... change to suite your authentication criteria
		$user = User::model()->find('LOWER(username)=?', array($username));
		
		if($user===null)
			$this->errorCode=self::ERROR_USERNAME_INVALID;
			
		else if(!$user->validatePassword($this->password))
			$this->errorCode = self::ERROR_PASSWORD_INVALID;
			
		else{
			$this->_id = $user->id;
			$this->username = $user->username;
			$this->errorCode = self::ERROR_NONE;
		}
		return $this->errorCode == self::ERROR_NONE;
	}
	public function getId()
	{
		return $this->_id;
	}
}

d. Modify CWebUser’s application in order to hold the ‘level’ property. We are going to call it EWebUser and will extend from CWebUser, and save it on protected/components to be loaded automatically by our default’s configuration file.

class EWebUser extends CWebUser{
 
    protected $_model;
 
    function isAdmin(){
        $user = $this->loadUser();
        if ($user)
           return $user->level==LevelLookUp::ADMIN;
        return false;
    }
 
    // Load user model.
    protected function loadUser()
    {
        if ( $this->_model === null ) {
                $this->_model = User::model()->findByPk( $this->id );
        }
        return $this->_model;
    }
}

e. Modify our main.php config file (this file is in protected/config folder)

// go to the 'user' section
// application components
	'components'=>array(
		'user'=>array(
                        // There you go, use our 'extended' version
			'class'=>'application.components.EWebUser',
			// enable cookie-based authentication
			'allowAutoLogin'=>true,
		),

Step 2: Putting everything together

Now that we know, who logged, we could easily find out if it is just a member or an administrator and render the elements by checking the level as simple as this:

// for normal content
if(Yii::app()->user->isAdmin())
     echo 'Is administrator';
 
// for CMenus
$this->widget('zii.widgets.CMenu',array(
    array('label'=>'Categories',
           'url'=>array('/category/index'),
           'visible'=>(Yii::app()->user->isAdmin()),
     //... More stuff
     //...
 
// for data chuncks
<?php if(Yii::app()->user->isAdmin():?>
<b>My HTML</b>
<?php endif;?>
 
// for access rules
return array(
      array('allow', 
        'actions'=>array('create','delete','update'),
        'expression'=>'$user->isAdmin()'
      ),
// ...


[ad]

Paths, Paths, Yii Paths!

Yii Framework

Introduction

When we start learning something and we go through some parts, we completely forget about what was a small issue in our way to get where we are. I started talking about things that can be hard to understand if the initial concepts are not well understood. For this reason, even though for some this small tutorials are too basic, I would like to post them every now and then to help newcomers to better understand the basic concepts of the articles.

Paths

Before, you continue with this article, I assume that you have read the fundamentals of Path Aliasing and Namespaces from the Definitive Guide of Yii

We are going to talk about paths and before we start, we should first explain how Yii creates its folder structure after you use its yiic tool. This is how it looks and the definitions of the folders (assuming you have created an application called testweb):

testweb/             containing your web
   assets/           containing published resource files
   css/              containing CSS files
   images/           containing image files
   themes/           containing application themes
   protected/        containing protected application files
      commands/      containing customized 'yiic' commands
         shell/      containing customized 'yiic shell' commands
      components/    containing reusable user components authentication
      config/        containing configuration files
      controllers/   containing controller class files
      data/          containing the sample database
      extensions/    containing third-party extensions
      messages/      containing translated messages
      models/        containing model class files
      runtime/       containing temporarily generated files
      tests/         containing test scripts
      views/         containing controller view and layout files
         layouts/    containing layout view files
         site/       containing view files for the 'site' controller
            pages/   containing "static" pages

Quite impressive, Yii MVC folder structure is very well created and for simple projects, we probably don’t need to change it, but if we wish to do it, or we create some widgets or portlets or components or whatever, and those need to access different folder locations in our application, Yii paths related functions come to resue:

  1. getPathOfAlias
  2. setPathOfAlias
  3. import

So, tell me, how do they work

A path alias is ‘nickname’ that we give to a folder in our Web project. Yii comes with five of them already established.

  • system: refers to the Yii framework directory;
  • zii: refers to the Zii library directory;
  • application: refers to the application’s base directory;
  • webroot: refers to the directory containing the entry script file. This alias has been available since version 1.0.3.
  • ext: refers to the directory containing all third-party extensions. This alias has been available since version 1.0.8.

So, in our example above, these are the correspondent translations:

Yii::getPathOfAlias(‘webroot.images’) is equal to the translated images folder path on your computer or server (ie. windows: C:\mywebserverpath\testweb\images, linux: /usr/web/www/testweb/images). I do use this feature to save images or folders on a folder  when I develop CMS for my clients. Here is an example:

$picture_file = CUploadedFile::getInstanceByName('Filedata');
//...
//... some validation code here
//...
// create a picture name
$picture_name = Picture::createPictureName($picture_file->name);	

// saving file
// 'othersubfolder' is just an imaginary subfolder under images
$picture_file->saveAs(
          Yii::getPathOfAlias('webroot.images.othersubfolder') . 
          DIRECTORY_SEPARATOR . 
         $picture_name );

Now, lets imagine that I wish to create a new folder 2010 for my application and it is located very far down on the folder structure and I wish to access it in order to save documents there. Here is the example:

testweb/
   assets/
   css/
   docs/
      clients/
         2010/
           January/

To make our life easier, we are going to use Yii::setPathOfAlias to provide an alias to the 2010 folder. For the sake of the example we are going to configure it in our index.php file after the creation of our Application:

defined('DS') or define('DS',DIRECTORY_SEPARATOR);

Yii::createWebApplication($config)-&gt;run();

Yii::setPathOfAlias('2010',
           dirname($_SERVER['SCRIPT_FILENAME']).DS.
           'docs'.DS.
           'clients'.DS.'2010');

// Now we can access the folder to read and/or write like this:
$pathToDocs = Yii::getPathOfAlias('2010');

Remember, than setPathOfAlias doesn’t normalizes the path and getPathOfAlias do not check for the existence of the folder just check if the alias exist. We need to put special attention to them.

And what about import?

I wouldn’t explain it better than Yii:

Using aliases, it is very convenient to include the definition of a class. For example, if we want to include theCController class, we can call the following:

Yii::import('system.web.CController');

The import method differs from include and require in that it is more efficient. The class definition being imported is actually not included until it is referenced for the first time (implemented via PHP autoloading mechanism). Importing the same namespace multiple times is also much faster than include_once and require_once.

We can also use the following syntax to import a whole directory so that the class files under the directory can be automatically included when needed.

Yii::import('system.web.*');

Besides import, aliases are also used in many other places to refer to classes. For example, an alias can be passed to Yii::createComponent() to create an instance of the corresponding class, even if the class file was not included previously.

Further Reading

We have covered just a small portion of the great funcionality of Yii. I highly recommend you to also look at the following path related functions:

  • getBasePath -Returns the root path of the application. Your root URL.
  • setBasePath -Sets the root directory of the application. This method can only be invoked at the begin of the constructor.
  • getExtensionPath – Returns the root directory that holds all third-party extensions.
  • setExtensionPath – Sets the root directory that holds all third-party extensions.
  • setRuntimePath – Sets the directory that stores runtime files.
  • getRuntimePath – Returns the root directory that stores runtime files
  • getLocaleDataPath – Returns the directory that contains the locale data. It defaults to ‘framework/i18n/data’.
  • setLocaleDataPath – Sets the directory that contains the locale data.

Hope this helps new comers to better understand the concepts working with path Yii way.


[ad]

How to create a Custom Pagination Widget for Yii Framework

Yii FrameworkYii Framework is great, I truly like this framework, makes complicated things an easy task and its learning curve quite easy. But… yeah man, is always a but… what happens to all those fellow programmers, like me, that do not like the way ‘automated things’ are? Where are the tutorials to change and improve the framework and make it work just the way we like? There are not many around…

Again, a programmer needs to investigate the guts of the framework, in order to find out how to properly do it. But don’t worry, if you have a question in the format: would I be able to do this with Yii? The answer will be always positive. It is a great framework.

So, I would like to share with you guys my personal experience and put some small tutorials to ‘open’ the minds of others begginning with this framework. This will be the first of a set of small tuts on this Framework.

Creating a Custom Pagination Widget

A pagination Widget is the responsible of displaying a set of numbered links that lead to different pages of target. When you first create a Web application through the yiic tool – this tool creates the skeleton of the application, the automated CMS works with CLinkPager widget and its style and Page Links were not suitable for my project. My project had to end up making AJAX calls and its STYLE like the following picture:

Please not that if you want to just change its style, you can create a skin as exposed here: http://www.yiiframework.com/doc/guide/topics.theming#customizing-widgets-globally. Once I look at the code, I realized that creating a custom widget was easier than I thought. The first thing we need to do is to extend our new Widget class from CLinkPager, my class is named SimplaPager as I have used a template admin theme called SIMPLA.


class SimplaPager extends CLinkPager

Now, there are three methods to override from our father and grandfather classes (CLinkPager and CBasePager). The parent methods we need to override are run and createPageButtons and from our grandfather class createPageUrl.

a)  run Function


public function run()
{
     //
     // here we call our createPageButtons
     //
     $buttons=$this->createPageButtons();
     //
     // if there is nothing to display return
     if(empty($buttons))
          return;
     //
     // display the buttons
     //
     echo $this->header; // if any
     echo implode("&nbsp;",$buttons);
     echo $this->footer;  // if any
 }

b) createPageButton Function

/**
 * Creates a page button.
 * You may override this method to customize the page buttons.
 * @param string the text label for the button
 * @param integer the page number
 * @param string the CSS class for the page button. This could be 'page', 'first', 'last', 'next' or 'previous'.
 * @param boolean whether this page button is visible
 * @param boolean whether this page button is selected
 * @return string the generated button
 */
protected function createPageButton($label,$page,$class,$hidden,$selected)
{
       //
       // CSS_HIDDEN_PAGE and CSS_SELECTED_PAGE
       // are constants that we use to apply our styles
       //
	if($hidden || $selected)
		$class=' '.($hidden ? self::CSS_HIDDEN_PAGE : self::CSS_SELECTED_PAGE);
	$class .= ' number';

       //
       // here I write my custom link - site.call is a JS function that takes care of an AJAX call
       //
	return CHtml::link($label,'#',array(
                'class'=>$class,
                'onclick'=>"site.call(CONST_MAIN_LAYER,'{$this->createPageUrl($this->getController(),$page)}');"));
}

c) createPageUrl Function

/**
* Creates the URL suitable for pagination.
* This method is mainly called by pagers when creating URLs used to
* perform pagination. The default implementation is to call
* the controller's createUrl method with the page information.
* @param CController the controller that will create the actual URL
* @param integer the page that the URL should point to. This is a zero-based index.
* @return string the created URL
*/
public function createPageUrl($controller,$page)
{
    // HERE I USE POST AS I DO AJAX CALLS VIA POST NOT GET AS IT IS BY 
    // DEFAULT ON YII
    $params=$this->getPages()->params===null ? $_POST : $this->getPages()->params;
    if($page>0) // page 0 is the default
       $params[$this->getPages()->pageVar]=$page+1;
   else
      unset($params[$this->getPages()->pageVar]);
   return $controller->createUrl($this->getPages()->route,$params);
}

Styling is controlled by a couple of constants in CLinkPager class, so the only thing we need to do is to override those constants.


const CSS_HIDDEN_PAGE='hidden';
const CSS_SELECTED_PAGE='current';

How to use the Widget

In order to use this widget first we create a widget subfolder in our application’s protected folder (in my case I saved a file named SimplaPager.php) and call it in our views like this:


$this->widget('application.widgets.SimplaPager', array(
    'pages'=>$pagination,
));

I have created another widget to display records in a table because I had the same problems than with the pagination so I use the the pagination within the Table Widget I created like this:

/**
 * @var array the configuration for the pager. Defaults to <code>array('class'=>'CLinkPager')</code>.
* @see enablePagination
 */
public $pager=array('class'=>'application.widgets.SimplaPager');

I promise I will discuss how to create a Table Widget in the next post :). Nevertheless, you can view how both widgets look at the end:

SimplaTable and SimplaPager working together

DOWNLOAD

Please remember than this class has some AJAX calls in its createPageButton function that is exclusive for my project. Change that part to suit your needs. This class was for you to better understand how to create custom Widgets by extending those from Yii framework.


[ad]