Create Custom Modules in OpenCart: A Step-by-Step Tutorial

Opencart custom module development is a core part of any Opencart project, because at any stage of the project you may want to add your own custom functionality(module) in your existing Opencart package.

In this part of the tutorial, we will learn how to create an admin section of Opencart custom module Popular Products. In the next part of this article we will see how to display this module on the frontend of the store.

Throughout this series, I’m referring to current Opencart latest version(at the time of writing this article) 2.0.1.1, though custom module structures are the same in all 2.x versions of Opencart. With minor modifications in code, you can develop the custom module for Opencart 2.0.0.0 version. I will write a separate tutorial for the newer version of Opencart. Before starting the actual module development, first, understand the basic structure of the Opencart module.

 

The Structure of Opencart Module

There are mainly six to eight files that need to be created for any module. The following screenshot shows the hierarchy of files and folders of an OpenCart module:

opencart custom module structure

As you can see in the image, the module file structure is having two parts: admin and catalog. The admin section folders and files deal with the module settings and data handling from the backend, while the catalog section folders and files responsible for displaying data in the frontend.

 

Creating the Admin Section

Admin section will have three files:

  • Controller File
  • Language File
  • View File

Create a Controller File at  path: admin/controller/module/popular.php
Create a Language File at  path: admin/language/english/module/popular.php
Create a View File at path: admin/view/template/module/popular.tpl

 

Controller File

In OpenCart a Controller is a class file that is named in a way that can be associated with a URL. For example, note the below URL :

http://sitename.com/index.php?route=module/popular

When the above URL is requested, Opencart would try to find a controller file called popular in the module folder having class name ControllerModulePopular. So let's extend the parent controller class.

class ControllerModulePopular extends Controller {

Whenever the controller is called, by default index() function is executed.

$this->load->language('module/popular');

The above line of code will load the language file variables of popular.php which is at admin/language/english/module/popular.php

Now we can get the messages or text with reference to variables like $this->language->get(‘heading_title’). So the popular text will be transferred to the template view files. The below line of code will set the document title as Popular.

$this->document->setTitle($this->language->get('heading_title'));
$this->load->model('extension/module');

 

All of the OpenCart modules and general settings are saved using this module. When the form is submitted through the POST method and the validate function of the controller returns true, all the changes are saved and the success message is assigned to the success variable and will be redirected to the module listing page.

if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
    if (!isset($this->request->get['module_id'])) {
        $this->model_extension_module->addModule('popular', $this->request->post);
    } else {
        $this->model_extension_module->editModule($this->request->get['module_id'], $this->request->post);
    }

	$this->cache->delete('product');
	$this->session->data['success'] = $this->language->get('text_success');
	$this->response->redirect($this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL'));
}

 

As mentioned above when the form is submitted, the validation function is called. It checks whether the user has permission to modify this module or not, after that other fields validations are written. For example in this module, below validations are written:

  • The module name must be between 3 and 64 characters.
  • Product image width and height must not be left blank.
protected function validate() {
	if (!$this->user->hasPermission('modify', 'module/popular')) {
	    $this->error['warning'] = $this->language->get('error_permission');
	}

	if ((utf8_strlen($this->request->post['name']) < 3) || (utf8_strlen($this->request->post['name']) > 64)) {
	    $this->error['name'] = $this->language->get('error_name');
	}

	if (!$this->request->post['width']) {
	    $this->error['width'] = $this->language->get('error_width');
	}

	if (!$this->request->post['height']) {
	    $this->error['height'] = $this->language->get('error_height');
	}
    return !$this->error;      
}

As stated in the below lines of code, language variables values are fetched from the language file and assigned to data variables so that they will be available in the template view file.

$data['heading_title'] = $this->language->get('heading_title');
$data['text_edit']     = $this->language->get('text_edit');
$data['text_enabled']  = $this->language->get('text_enabled');
$data['text_disabled'] = $this->language->get('text_disabled');

 

The below lines of code will check if there is an error related to permission or the fields validation, if so then it’ll assign those error messages to the related variables and the error messages will be displayed in the template view file.

if (isset($this->error['warning'])) {
    $data['error_warning'] = $this->error['warning'];
} else {
    $data['error_warning'] = '';
}

if (isset($this->error['name'])) {
    $data['error_name'] = $this->error['name'];
} else {
    $data['error_name'] = '';
}

 

We have defined the array of breadcrumbs. It has some elements like text, href, and separator(if required). The text element has the value to be displayed in the template view file, href has the link, and separator has the value to be shown between the words.

$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
    'text' => $this->language->get('text_home'),
    'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], 'SSL')
);

$data['breadcrumbs'][] = array(
    'text' => $this->language->get('text_module'),
    'href' => $this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL')
);

Setting the action variable depending upon the presence of module_id.

if (!isset($this->request->get['module_id'])) {
    $data['action'] = $this->url->link('module/popular', 'token=' . $this->session->data['token'], 'SSL');
} else {
    $data['action'] = $this->url->link('module/popular', 'token=' . $this->session->data['token'] . '&module_id=' . $this->request->get['module_id'], 'SSL');
}

 

If the module_id is present in URL and the action is not POST then this will fetch the module information based on the module_id. The module_info will be assigned to the variables for the template view file. You can set the default value for the fields also.

if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
    $module_info = $this->model_extension_module->getModule($this->request->get['module_id']);
}

if (isset($this->request->post['name'])) {
    $data['name'] = $this->request->post['name'];
} elseif (!empty($module_info)) {
    $data['name'] = $module_info['name'];
} else {
    $data['name'] = '';
}

if (isset($this->request->post['limit'])) {
    $data['limit'] = $this->request->post['limit'];
} elseif (!empty($module_info)) {
    $data['limit'] = $module_info['limit'];
} else {
    $data['limit'] = 5;
}

Loading the header, footer and column_left controller and then set the output with the $data. Finally HTML will be constructed from the template/data.

$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('module/popular.tpl', $data));

 

Complete File

<?php
class ControllerModulePopular extends Controller {
    private $error = array(); 

public function index() {
    $this->load->language('module/popular');
    $this->document->setTitle($this->language->get('heading_title'));
    $this->load->model('extension/module');

    if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
        if (!isset($this->request->get['module_id'])) {
            $this->model_extension_module->addModule('popular', $this->request->post);
        } else {
            $this->model_extension_module->editModule($this->request->get['module_id'], $this->request->post);
        }

        $this->cache->delete('product');
        $this->session->data['success'] = $this->language->get('text_success');
        $this->response->redirect($this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL'));
    }

    $data['heading_title'] = $this->language->get('heading_title');
    $data['text_edit'] = $this->language->get('text_edit');
    $data['text_enabled'] = $this->language->get('text_enabled');
    $data['text_disabled'] = $this->language->get('text_disabled');

    $data['entry_name'] = $this->language->get('entry_name');
    $data['entry_limit'] = $this->language->get('entry_limit');
    $data['entry_width'] = $this->language->get('entry_width');
    $data['entry_height'] = $this->language->get('entry_height');
    $data['entry_status'] = $this->language->get('entry_status');

    $data['button_save'] = $this->language->get('button_save');
    $data['button_cancel'] = $this->language->get('button_cancel');

    if (isset($this->error['warning'])) {
        $data['error_warning'] = $this->error['warning'];
    } else {
        $data['error_warning'] = '';
    }

    if (isset($this->error['name'])) {
        $data['error_name'] = $this->error['name'];
    } else {
        $data['error_name'] = '';
    }

    if (isset($this->error['width'])) {
        $data['error_width'] = $this->error['width'];
    } else {
        $data['error_width'] = '';
    }

    if (isset($this->error['height'])) {
        $data['error_height'] = $this->error['height'];
    } else {
        $data['error_height'] = '';
    }

    $data['breadcrumbs'] = array();
    $data['breadcrumbs'][] = array(
        'text' => $this->language->get('text_home'),
        'href' => $this->url->link('common/dashboard', 'token=' . $this->session->data['token'], 'SSL')
    );

    $data['breadcrumbs'][] = array(
        'text' => $this->language->get('text_module'),
        'href' => $this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL')
    );

    if (!isset($this->request->get['module_id'])) {
        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('heading_title'),
            'href' => $this->url->link('module/popular', 'token=' . $this->session->data['token'], 'SSL')
        );
    } else {
        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('heading_title'),
            'href' => $this->url->link('module/popular', 'token=' . $this->session->data['token'] . '&module_id=' . $this->request->get['module_id'], 'SSL')
        );          
    }

    if (!isset($this->request->get['module_id'])) {
        $data['action'] = $this->url->link('module/popular', 'token=' . $this->session->data['token'], 'SSL');
    } else {
        $data['action'] = $this->url->link('module/popular', 'token=' . $this->session->data['token'] . '&module_id=' . $this->request->get['module_id'], 'SSL');
    }

    $data['cancel'] = $this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL');

    if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
        $module_info = $this->model_extension_module->getModule($this->request->get['module_id']);
    }

    if (isset($this->request->post['name'])) {
        $data['name'] = $this->request->post['name'];
    } elseif (!empty($module_info)) {
        $data['name'] = $module_info['name'];
    } else {
        $data['name'] = '';
    }

    if (isset($this->request->post['limit'])) {
        $data['limit'] = $this->request->post['limit'];
    } elseif (!empty($module_info)) {
        $data['limit'] = $module_info['limit'];
    } else {
        $data['limit'] = 5;
    }   

    if (isset($this->request->post['width'])) {
        $data['width'] = $this->request->post['width'];
    } elseif (!empty($module_info)) {
        $data['width'] = $module_info['width'];
    } else {
        $data['width'] = 200;
    }   

    if (isset($this->request->post['height'])) {
        $data['height'] = $this->request->post['height'];
    } elseif (!empty($module_info)) {
        $data['height'] = $module_info['height'];
    } else {
        $data['height'] = 200;
    }       

    if (isset($this->request->post['status'])) {
        $data['status'] = $this->request->post['status'];
    } elseif (!empty($module_info)) {
        $data['status'] = $module_info['status'];
    } else {
        $data['status'] = '';
    }

    $data['header'] = $this->load->controller('common/header');
    $data['column_left'] = $this->load->controller('common/column_left');
    $data['footer'] = $this->load->controller('common/footer');

    $this->response->setOutput($this->load->view('module/popular.tpl', $data));
}

protected function validate() {
    if (!$this->user->hasPermission('modify', 'module/popular')) {
        $this->error['warning'] = $this->language->get('error_permission');
    }

    if ((utf8_strlen($this->request->post['name']) < 3) || (utf8_strlen($this->request->post['name']) > 64)) {
        $this->error['name'] = $this->language->get('error_name');
    }

    if (!$this->request->post['width']) {
        $this->error['width'] = $this->language->get('error_width');
    }

    if (!$this->request->post['height']) {
        $this->error['height'] = $this->language->get('error_height');
    }

    return !$this->error;
    }
  }
?>

 

Language file

As described, the language file will contain the language variables which describe what will be displayed in the view file. In language file constant name is used which never changes, only the values according to the language will be changed. If the currently selected language is other than English then the variables will be loaded from that language’s folder file.

<?php
// Heading
$_['heading_title']    = 'Popular Products';

// Text
$_['text_module']      = 'Modules';
$_['text_success']     = 'Success: You have modified module "Popular Products"!';
$_['text_edit']        = 'Edit Popular Products Module';

// Entry
$_['entry_name']       = 'Module Name';
$_['entry_limit']      = 'Limit';
$_['entry_width']      = 'Width';
$_['entry_height']     = 'Height';
$_['entry_status']     = 'Status';

// Error
$_['error_permission']  = 'Warning: You do not have permission to modify module "Popular Products"!';
$_['error_width']       = 'Width required!';
$_['error_height']      = 'Height required!';

 

View File

View file refers to the template file having .tpl extension. It will display all the set variables of the controller. Then after we loop through the $breadcrumbs array to display breadcrumbs links. One section is added to display the error messages. We have one form and when it's submitted, the controller method is called and submitted data will be processed.

<?php echo $header; ?><?php echo $column_left; ?>
<div id="content">
  <div class="page-header">
    <div class="container-fluid">
      <div class="pull-right">
        <button type="submit" form="form-popular" data-toggle="tooltip" title="<?php echo $button_save; ?>" class="btn btn-primary"><i class="fa fa-save"></i></button>
        <a href="<?php echo $cancel; ?>" data-toggle="tooltip" title="<?php echo $button_cancel; ?>" class="btn btn-default"><i class="fa fa-reply"></i></a></div>
      <h1><?php echo $heading_title; ?></h1>
      <ul class="breadcrumb">
        <?php foreach ($breadcrumbs as $breadcrumb) { ?>
        <li><a href="<?php echo $breadcrumb['href']; ?>"><?php echo $breadcrumb['text']; ?></a></li>
        <?php } ?>
      </ul>
    </div>
  </div>
  <div class="container-fluid">
    <?php if ($error_warning) { ?>
    <div class="alert alert-danger"><i class="fa fa-exclamation-circle"></i> <?php echo $error_warning; ?>
      <button type="button" class="close" data-dismiss="alert">&times;</button>
    </div>
    <?php } ?>
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><i class="fa fa-pencil"></i> <?php echo $text_edit; ?></h3>
      </div>
      <div class="panel-body">
        <form action="<?php echo $action; ?>" method="post" enctype="multipart/form-data" id="form-popular" class="form-horizontal">
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-name"><?php echo $entry_name; ?></label>
            <div class="col-sm-10">
              <input type="text" name="name" value="<?php echo $name; ?>" placeholder="<?php echo $entry_name; ?>" id="input-name" class="form-control" />
              <?php if ($error_name) { ?>
              <div class="text-danger"><?php echo $error_name; ?></div>
              <?php } ?>
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-limit"><?php echo $entry_limit; ?></label>
            <div class="col-sm-10">
              <input type="text" name="limit" value="<?php echo $limit; ?>" placeholder="<?php echo $entry_limit; ?>" id="input-limit" class="form-control" />
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-width"><?php echo $entry_width; ?></label>
            <div class="col-sm-10">
              <input type="text" name="width" value="<?php echo $width; ?>" placeholder="<?php echo $entry_width; ?>" id="input-width" class="form-control" />
              <?php if ($error_width) { ?>
              <div class="text-danger"><?php echo $error_width; ?></div>
              <?php } ?>
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-height"><?php echo $entry_height; ?></label>
            <div class="col-sm-10">
              <input type="text" name="height" value="<?php echo $height; ?>" placeholder="<?php echo $entry_height; ?>" id="input-height" class="form-control" />
              <?php if ($error_height) { ?>
              <div class="text-danger"><?php echo $error_height; ?></div>
              <?php } ?>
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-status"><?php echo $entry_status; ?></label>
            <div class="col-sm-10">
              <select name="status" id="input-status" class="form-control">
                <?php if ($status) { ?>
                <option value="1" selected="selected"><?php echo $text_enabled; ?></option>
                <option value="0"><?php echo $text_disabled; ?></option>
                <?php } else { ?>
                <option value="1"><?php echo $text_enabled; ?></option>
                <option value="0" selected="selected"><?php echo $text_disabled; ?></option>
                <?php } ?>
              </select>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>
<?php echo $footer; ?>

 

Conclusion


In this part of the article, we developed the admin section of the popular products OpenCart module. Read the next post to know Opencart Custom Frontend Module Development Tutorial.


  

Ketan Patel

As a backend and ecommerce developer, I have extensive experience in implementing robust and scalable solutions for ecommerce websites and applications. I have a deep understanding of server-side technologies and have worked with various programming languages. I have experience in integrating with payment gateways, and other third-party services.