Integrate OpenCart and Yii

I was looking for a long time, if someone has tried to integrate the state of the art Yii framework and opencart shopping cart. This integration will make yii work under opencart and yii will handle the request in case of failure.

The reasons for selecting yii as a framework is obvious, it’s fast, simple, easy to learn and flexibility.

While the reason for chosing opencart is it’s better design (then oscommerce), more features installed by default, and the same MVC structure like Yii.

This guide will enable you to

  1. Integrate yii and opencart
  2. Call yii function any where with the opencart system using the standard Yii::app() or Yii::xyz approach
  3. Call yii’s partial rendered pages within opencart
  4. Shift the path finder to yii within try {} catch {} when opencart cannot resolve the given path, this will enable us to show 404 error using opencarts controller when even yii cannot resolve the path.

This Guide Will NOT enable you to

  1. Call opencart’s controller within yii.
  2. get Open cart header and footer and supplement them instead of yii (using opencart’s theme).

So this is much like a one way integration.

Now let’s start understanding how the integration started.

 Yii

  • Renaming

No matter where we place yii, if we are calling it from within opencart, we have to rename few of the files.
The most important one is Controller.php in protected/compotents. This file is wides used and all of our controllers are extending from it, so rename it ( I have used WController.php – W for webcare) and extend all of our controllers from WController instead of Controller only.

The second important file is User.php (used by any user management program, and if not create your own one. here is the code

<?php 
/**  
  * This is the model class for table "user".  
  *
  * The followings are the available columns in table 'user':
  * @property integer $user_id
  * @property integer $user_group_id
  * @property string $username
  * @property string $password
  * @property string $firstname
  * @property string $lastname
  * @property string $email
  * @property string $code
  * @property string $ip
  * @property integer $status
  * @property string $date_added
  */
 class WUser extends CActiveRecord {
 	/**
 	 * Returns the static model of the specified AR class.
 	 * @param string $className active record class name.
 	 * @return User the static model class
 	 */
 	public static function model($className=__CLASS__) 	{
 		return parent::model($className); 	
        }

        public function getTableSchema()         {
            $table = parent::getTableSchema();
            $table->columns['user_group_id']->isForeignKey = true; // relations with User Groups
            $table->foreignKeys['user_group_id'] = array('WUserGroup', 'user_group_id');

            return $table;
        }

        public function scopes() {
            return array(
                'active' => array(
                    'condition' => 'status = 1',
                ),
                'admins'=>array(
                    'with' => 'userGroup',
                    'condition' => 'LOWER(userGroup.name) = 'top administrator' OR LOWER(userGroup.name) = 'admin'',
                ),
                'notAdmins'=>array(
                    'with' => 'userGroup',
                    'condition' => 'NOT LOWER(userGroup.name) = 'top administrator' AND NOT LOWER(userGroup.name) = 'admin'',
                ),
                'superuser'=>array(
                    'with' => 'userGroup',
                    'condition' => 'LOWER(userGroup.name) = 'top administrator'',
                ),
            );
        }

	/**
	 * @return string the associated database table name
	 */
	public function tableName()
	{
		return 'user';
	}

	/**
	 * @return array validation rules for model attributes.
	 */
	public function rules()
	{
		// NOTE: you should only define rules for those attributes that
		// will receive user inputs.
		return array(
			array('user_group_id, code, status', 'required'),
			array('user_group_id, status', 'numerical', 'integerOnly'=>true),
			array('username', 'length', 'max'=>20),
			array('password, firstname, lastname, code', 'length', 'max'=>32),
			array('email', 'length', 'max'=>96),
			array('ip', 'length', 'max'=>15),
			array('date_added', 'safe'),
			// The following rule is used by search().
			// Please remove those attributes that should not be searched.
			array('user_id, user_group_id, username, password, firstname, lastname, email, code, ip, status, date_added', 'safe', 'on'=>'search'),
		);
	}

	/**
	 * @return array relational rules.
	 */
	public function relations()
	{
		// NOTE: you may need to adjust the relation name and the related
		// class name for the relations automatically generated below.
		return array(
                    'userGroup' => array(self::BELONGS_TO, 'WUserGroup', 'user_group_id'),
		);
	}

	/**
	 * @return array customized attribute labels (name=>label)
	 */
	public function attributeLabels()
	{
		return array(
			'user_id' => 'User',
			'user_group_id' => 'User Group',
			'username' => 'Username',
			'password' => 'Password',
			'firstname' => 'Firstname',
			'lastname' => 'Lastname',
			'email' => 'Email',
			'code' => 'Code',
			'ip' => 'Ip',
			'status' => 'Status',
			'date_added' => 'Date Added',
		);
	}

	/**
	 * Retrieves a list of models based on the current search/filter conditions.
	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
	 */
	public function search()
	{
		// Warning: Please modify the following code to remove attributes that
		// should not be searched.

		$criteria=new CDbCriteria;

		$criteria->compare('user_id',$this->user_id);
		$criteria->compare('user_group_id',$this->user_group_id);
		$criteria->compare('username',$this->username,true);
		$criteria->compare('password',$this->password,true);
		$criteria->compare('firstname',$this->firstname,true);
		$criteria->compare('lastname',$this->lastname,true);
		$criteria->compare('email',$this->email,true);
		$criteria->compare('code',$this->code,true);
		$criteria->compare('ip',$this->ip,true);
		$criteria->compare('status',$this->status);
		$criteria->compare('date_added',$this->date_added,true);

		return new CActiveDataProvider($this, array(
			'criteria'=>$criteria,
		));
	}

        public function validatePassword($password){
            $valid = false;
            if($this->password == md5($password)){
                $valid = true;
            }
            return $valid;
        }

        public function isAdmin(){
            $isAdmin = false;
            if(!Yii::app()->user->isGuest){
                if(strtolower($this->userGroup->name) == 'admin' || strtolower($this->userGroup->name) == 'top administrator'){
                    $isAdmin = true;
                }
            }
            return $isAdmin;
        }

        public static function getAdmins() {
                $admins = WUser::model()->active()->admins()->findAll();
                $return_name = array();
                foreach ($admins as $admin)
                        array_push($return_name,$admin->username);

                return $return_name;
	}

        public static function getNonAdmins() {
                $users = WUser::model()->active()->notAdmins()->findAll();
                $return_name = array();
                foreach ($users as $user)
                        array_push($return_name,$user->username);

                return $return_name;
	}

        public function getAdminDropDownList(){
            if($this->isAdmin()){
                return CHtml::listData(WUser::model()->active()->admins()->findAll(), 'user_id', 'username');
            }
            return false;
        }
}

 

This file has two important functions, validatePassword, which is identical to opencart’s password verification.

Also note that this WUser.php under protected/models is based on the same table used by opecart. this way we don’t have to manage to separate tables for the same data.

As opencart has user_groups also, so we need a WUserGroup.php also. here it is

<?php
 /**
  * This is the model class for table "user_group".
  *
  * The followings are the available columns in table 'user_group':
  * @property integer $user_group_id
  * @property string $name
  * @property string $permission
  */
 class WUserGroup extends CActiveRecord
 {
 	/**
 	 * Returns the static model of the specified AR class.
 	 * @param string $className active record class name.
 	 * @return UserGroup the static model class
 	 */
 	public static function model($className=__CLASS__)
 	{
 		return parent::model($className);
 	}
 	/**
 	 * @return string the associated database table name
 	 */
 	public function tableName()
 	{
 		return 'user_group';
 	}

 	/**
 	 * @return array validation rules for model attributes.
 	 */
 	public function rules()
 	{
 		// NOTE: you should only define rules for those attributes that
 		// will receive user inputs.
 		return array(
 			array('name, permission', 'required'),
 			array('name', 'length', 'max'=>64),
			// The following rule is used by search().
			// Please remove those attributes that should not be searched.
			array('user_group_id, name, permission', 'safe', 'on'=>'search'),
		);
	}

	/**
	 * @return array relational rules.
	 */
	public function relations()
	{
		// NOTE: you may need to adjust the relation name and the related
		// class name for the relations automatically generated below.
		return array(
		);
	}

	/**
	 * @return array customized attribute labels (name=>label)
	 */
	public function attributeLabels()
	{
		return array(
			'user_group_id' => 'User Group',
			'name' => 'Name',
			'permission' => 'Permission',
		);
	}

	/**
	 * Retrieves a list of models based on the current search/filter conditions.
	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
	 */
	public function search()
	{
		// Warning: Please modify the following code to remove attributes that
		// should not be searched.

		$criteria=new CDbCriteria;

		$criteria->compare('user_group_id',$this->user_group_id);
		$criteria->compare('name',$this->name,true);
		$criteria->compare('permission',$this->permission,true);

		return new CActiveDataProvider($this, array(
			'criteria'=>$criteria,
		));
	}
}

So, this is a fillup class for User Groups in opencart.

  • User Access

For proper user access and management combined with opencart, modify the access function as follows

 

/**
     * Specifies the access control rules.
     * This method is used by the 'accessControl' filter.
     * @return array access control rules
     */
    public function accessRules() {
        $user = WUser::model(); // same WUser we have created earlier
        return array(
            array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'users' => $user->getAdmins(), // modified function to get Admins or TopAdmin
            ),
            array('deny', // deny all users
                'users' => array('*'),
            ),
        );
    }

 

  • Controller – Modification

As I have explained earlier, I cannot find a way to call the opencart’s header and footer, so what I have done is I made the whole Yii project ajax based, and while calling the yii’s part, It will renderPartial the view file and return it with complete jquery and css requirement, here is how.

in Each controller’s each action function, before render call e.g. in

   function actionView($id){

before

 $this->render('view', array(
            'model' => $this->loadModel($id),
        ));

add

        if (Yii::app()->request->isAjaxRequest) {
            $this->renderPartial('view', array(
                'model' => $this->loadModel($id),
            ), false, true);
            Yii::app()->end();
        }

As you can see the last two arguements, false, true

The first arguement will echo to screen, (true will return an object).

The second arugement will call the renderHeader, renderFooter and renderBody functions to include all the scripting involved including javascript, jquery and css.

This is to make sure that our yii version of views works well under opencart.

  • Yii config.php

I have modified the config/main.php to call it config/opencart.php to make sure it is opencart’s required configuration for yii. Have a look at it.

<?php
 // uncomment the following to define a path alias
 // Yii::setPathOfAlias('local','path/to/local-folder');
 // This is the main Web application configuration. Any writable
 // CWebApplication properties can be configured here.

 require_once('db.php'); // all my db information are stored separately

 return array(
     'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..',
    'name' => 'webcare.pk', // replace it to your taste
    'theme' => 'yourtheme', // replace it to your taste
    // preloading 'log' component
    'preload' => array('log'),
    // autoloading model and component classes
    'import' => array(
        'application.models.*',
        'application.components.*',
        'ext.AjaxMenu', // Ajax CMenu
    ),
    'modules' => array(
//        'gii' => array(
//            'class' => 'system.gii.GiiModule',
//            'password' => '',
//            // If removed, Gii defaults to localhost only. Edit carefully to taste.
//            'ipFilters' => array('127.0.0.1', '::1'),
//        ),
    ),
    // application components
    'components' => array(
        'assetManager' => array(
            // assetsPath is relative to config file now
            // must be configured according to the location of yii and opencart
            'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'assets', // most important
            'baseUrl' => $assets, // most important
        ),

        'themeManager' => array(
             // theme path is relative to config file now
            'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'themes',
            'baseUrl' => '../themes',
        ),

        'user' => array(
            // enable cookie-based authentication
            'allowAutoLogin' => true,
            //'loginUrl' => array('/user/login'),
        ),
        // uncomment the following to enable URLs in path-format
        'urlManager' => array(
            'urlFormat' => 'path',
            'rules' => array(
                '<controller:w+>/<id:d+>' => '/view',
                '<controller:w+>/<action:w+>/<id:d+>' => '/',
                '<controller:w+>/<action:w+>' => '/',
                '<action:(login|logout|about)>' => 'site/',
            ),
            'showScriptName' => false,
            'appendParams' => true,
        ),
        // another important aspect, use the opencart's script versions
        'clientScript' => array(
            'class' => 'CClientScript',
            'scriptMap' => array(
                'jquery.js' => false,
                'jquery.min.js' => false,
            ),
        ),
        /* 'db'=>array(
          'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
          ), */
        // uncomment the following to use a MySQL database
        'db' => array(
            'connectionString' => 'mysql:host=localhost;dbname=' . $database,
            'emulatePrepare' => true,
            'username' => $user,
            'password' => $pass,
            'tablePrefix' => 'yourprefix_',
            'charset' => 'utf8', // your charset
            'enableParamLogging' => true, // Log SQL parameters
        ),
        'errorHandler' => array(
            // use 'site/error' action to display errors
            'errorAction' => 'site/error',
        ),
        'log' => array(
            'class' => 'CLogRouter',
            'routes' => array(
                array(
                    'class' => 'CFileLogRoute',
                    'levels' => 'trace,info, error, warning',
                ),
                // uncomment the following to show log messages on web pages
//                array(
//                    'class' => 'CWebLogRoute',
//                    'levels' => 'trace,info, error, warning',
//                ),
            ),
        ),
        //session management
        'session' => array(
            'class' => 'CHttpSession',
        ),
    ),
    // application-level parameters that can be accessed
    // using Yii::app()->params['paramName']
    'params' => array(
        // this is used in contact page
        'adminEmail' => 'youremail@yourdomain.com',
        'dbname' => $database,
    ),
);

In this config file, following items are important

  1. ThemeUrl and ThemeManager
  2. AssetsUrl and AssetsManager
  3. logging to file instead of web
  4. ClientScript, stop loading jquery.js and jquery.min .js and include the files from opencart.
  • UserIdentity.php

Last but not least, we have to redesign UserIdentity so that we can use the same credentials from opencart to login and logout.

I would suggest simply putting the following code into your protected/components/UserIdentity.php

 

<?php

/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
class UserIdentity extends CUserIdentity {

    private $_id;

    /**
     * Authenticates a user.
     * The example implementation makes sure if the username and password
     * are both 'demo'.
     * In practical applications, this should be changed to authenticate
     * against some persistent user identity storage (e.g. database).
     * @return boolean whether authentication succeeds.
     */
    public function authenticate() {
        $username = strtolower($this->username);
        $user = WUser::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->user_id;
            $this->username = $user->username;
            $this->errorCode = self::ERROR_NONE;
        }
        return $this->errorCode == self::ERROR_NONE;
    }

    public function getId() {
        return $this->_id;
    }

}
?>

 

I hope this finishes the yii part

To know the opencart’s part, read further

 

1 thought on “Integrate OpenCart and Yii

Comments are closed.