Although OpenCart provides a number of useful shipping methods in the core itself, there's always a chance that you'll need to create your own. On the other hand, being a web developer, you'll always try to explore the framework of your choice to see how to create your own custom stuff!
In this series, we're going to create a custom shipping method module in OpenCart. It'll be a two-part series, and in the first part we'll create a back-end configuration form for our custom shipping method.
To create a new custom shipping method in OpenCart requires implementation of the files as per the conventions of OpenCart. In the back-end, you'll need to provide a configuration form which allows the administrator to configure the price, geo-zone and other parameters related to the shipping method. In the front-end, you'll implement the required files so that your custom shipping method is picked up during the checkout!
Today, we'll go through the back-end setup. I assume that you're using the latest version of the OpenCart. In the second part, we'll explore the front-end counterpart, in which we'll see the front-end file setup and demo in the front-end.
A Glance at the File Setup in the Back-End
Let's start with a list of the files required in the back-end. We'll use "custom" as the name of our custom shipping method.
-
admin/controller/shipping/custom.php
: It's a controller file in which we'll set up everything necessary for the configuration form. -
admin/language/english/shipping/custom.php
: It's a language file in which we'll define the labels for our form. -
admin/view/template/shipping/custom.tpl
: It's a view template file which holds the HTML code of our configuration form.
So that's it as far as the back-end setup is concerned.
File Setup
Let's start with the controller setup.
Create a Controller File
Create a file admin/controller/shipping/custom.php
and paste the following contents in that file.
<?php class ControllerShippingCustom extends Controller { private $error = array(); public function index() { $this->load->language('shipping/custom'); $this->document->setTitle($this->language->get('heading_title')); $this->load->model('setting/setting'); if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) { $this->model_setting_setting->editSetting('custom', $this->request->post); $this->session->data['success'] = $this->language->get('text_success'); $this->response->redirect($this->url->link('extension/shipping', '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['text_all_zones'] = $this->language->get('text_all_zones'); $data['text_none'] = $this->language->get('text_none'); $data['entry_cost'] = $this->language->get('entry_cost'); $data['entry_tax_class'] = $this->language->get('entry_tax_class'); $data['entry_geo_zone'] = $this->language->get('entry_geo_zone'); $data['entry_status'] = $this->language->get('entry_status'); $data['entry_sort_order'] = $this->language->get('entry_sort_order'); $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'] = ''; } $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_shipping'), 'href' => $this->url->link('extension/shipping', 'token=' . $this->session->data['token'], 'SSL') ); $data['breadcrumbs'][] = array( 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('shipping/custom', 'token=' . $this->session->data['token'], 'SSL') ); $data['action'] = $this->url->link('shipping/custom', 'token=' . $this->session->data['token'], 'SSL'); $data['cancel'] = $this->url->link('extension/shipping', 'token=' . $this->session->data['token'], 'SSL'); if (isset($this->request->post['custom_cost'])) { $data['custom_cost'] = $this->request->post['custom_cost']; } else { $data['custom_cost'] = $this->config->get('custom_cost'); } if (isset($this->request->post['custom_tax_class_id'])) { $data['custom_tax_class_id'] = $this->request->post['custom_tax_class_id']; } else { $data['custom_tax_class_id'] = $this->config->get('custom_tax_class_id'); } if (isset($this->request->post['custom_geo_zone_id'])) { $data['custom_geo_zone_id'] = $this->request->post['custom_geo_zone_id']; } else { $data['custom_geo_zone_id'] = $this->config->get('custom_geo_zone_id'); } if (isset($this->request->post['custom_status'])) { $data['custom_status'] = $this->request->post['custom_status']; } else { $data['custom_status'] = $this->config->get('custom_status'); } if (isset($this->request->post['custom_sort_order'])) { $data['custom_sort_order'] = $this->request->post['custom_sort_order']; } else { $data['custom_sort_order'] = $this->config->get('custom_sort_order'); } $this->load->model('localisation/tax_class'); $data['tax_classes'] = $this->model_localisation_tax_class->getTaxClasses(); $this->load->model('localisation/geo_zone'); $data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones(); $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('shipping/custom.tpl', $data)); } protected function validate() { if (!$this->user->hasPermission('modify', 'shipping/custom')) { $this->error['warning'] = $this->language->get('error_permission'); } return !$this->error; } }
It's an important file which defines most of the logic for the back-end configuration form. We'll go through the important snippets in the index
method of the controller. As per the convention, you'll need to define the class name ControllerShippingCustom
.
In the index
method, we start by loading the language file and setting up the page title.
Next, we load the setting
model and save the settings into the database available as a POST data of the configuration form. Before saving data, we validate the form using the validate
method defined in that file.
$this->load->model('setting/setting'); if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) { $this->model_setting_setting->editSetting('custom', $this->request->post); $this->session->data['success'] = $this->language->get('text_success'); $this->response->redirect($this->url->link('extension/shipping', 'token=' . $this->session->data['token'], 'SSL')); }
After that, we assign language labels into the $data
array so that we can access those in the view template file.
Moving ahead, there's a standard snippet to set up the proper breadcrumb links.
$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_shipping'), 'href' => $this->url->link('extension/shipping', 'token=' . $this->session->data['token'], 'SSL') ); $data['breadcrumbs'][] = array( 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('shipping/custom', 'token=' . $this->session->data['token'], 'SSL') );
Next, we set up the action
variable to make sure that the form is submitted to our index
method. And in the same way, users are taken back to the list of shipping methods if they click on the Cancel
button.
$data['action'] = $this->url->link('shipping/custom', 'token=' . $this->session->data['token'], 'SSL'); $data['cancel'] = $this->url->link('extension/shipping', 'token=' . $this->session->data['token'], 'SSL');
Further, there's code to populate the default values of the configuration form fields either in add or edit mode.
if (isset($this->request->post['custom_cost'])) { $data['custom_cost'] = $this->request->post['custom_cost']; } else { $data['custom_cost'] = $this->config->get('custom_cost'); } if (isset($this->request->post['custom_tax_class_id'])) { $data['custom_tax_class_id'] = $this->request->post['custom_tax_class_id']; } else { $data['custom_tax_class_id'] = $this->config->get('custom_tax_class_id'); } if (isset($this->request->post['custom_geo_zone_id'])) { $data['custom_geo_zone_id'] = $this->request->post['custom_geo_zone_id']; } else { $data['custom_geo_zone_id'] = $this->config->get('custom_geo_zone_id'); } if (isset($this->request->post['custom_status'])) { $data['custom_status'] = $this->request->post['custom_status']; } else { $data['custom_status'] = $this->config->get('custom_status'); } if (isset($this->request->post['custom_sort_order'])) { $data['custom_sort_order'] = $this->request->post['custom_sort_order']; } else { $data['custom_sort_order'] = $this->config->get('custom_sort_order'); }
In the next section, we load the tax classes and geo zones from the database, which will be used as the drop-down options in the configuration form.
$this->load->model('localisation/tax_class'); $data['tax_classes'] = $this->model_localisation_tax_class->getTaxClasses(); $this->load->model('localisation/geo_zone'); $data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones();
Finally, we assign the children templates and the main template of the view.
$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('shipping/custom.tpl', $data));
Create a Language File
Create a file admin/language/english/shipping/custom.php
and paste the following contents in that file.
<?php // Heading $_['heading_title'] = 'Custom Rate'; // Text $_['text_shipping'] = 'Shipping'; $_['text_success'] = 'Success: You have modified custom rate shipping!'; $_['text_edit'] = 'Edit Custom Rate Shipping'; // Entry $_['entry_cost'] = 'Cost'; $_['entry_tax_class'] = 'Tax Class'; $_['entry_geo_zone'] = 'Geo Zone'; $_['entry_status'] = 'Status'; $_['entry_sort_order'] = 'Sort Order'; // Error $_['error_permission'] = 'Warning: You do not have permission to modify custom rate shipping!';
The contents of the file should be self-explanatory!
Create a View File
Create a file admin/view/template/shipping/custom.php
and paste the following contents in that file.
<?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-custom" 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">×</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-custom" class="form-horizontal"> <div class="form-group"> <label class="col-sm-2 control-label" for="input-cost"><?php echo $entry_cost; ?></label> <div class="col-sm-10"> <input type="text" name="custom_cost" value="<?php echo $custom_cost; ?>" placeholder="<?php echo $entry_cost; ?>" id="input-cost" class="form-control" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-tax-class"><?php echo $entry_tax_class; ?></label> <div class="col-sm-10"> <select name="custom_tax_class_id" id="input-tax-class" class="form-control"> <option value="0"><?php echo $text_none; ?></option> <?php foreach ($tax_classes as $tax_class) { ?> <?php if ($tax_class['tax_class_id'] == $custom_tax_class_id) { ?> <option value="<?php echo $tax_class['tax_class_id']; ?>" selected="selected"><?php echo $tax_class['title']; ?></option> <?php } else { ?> <option value="<?php echo $tax_class['tax_class_id']; ?>"><?php echo $tax_class['title']; ?></option> <?php } ?> <?php } ?> </select> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label" for="input-geo-zone"><?php echo $entry_geo_zone; ?></label> <div class="col-sm-10"> <select name="custom_geo_zone_id" id="input-geo-zone" class="form-control"> <option value="0"><?php echo $text_all_zones; ?></option> <?php foreach ($geo_zones as $geo_zone) { ?> <?php if ($geo_zone['geo_zone_id'] == $custom_geo_zone_id) { ?> <option value="<?php echo $geo_zone['geo_zone_id']; ?>" selected="selected"><?php echo $geo_zone['name']; ?></option> <?php } else { ?> <option value="<?php echo $geo_zone['geo_zone_id']; ?>"><?php echo $geo_zone['name']; ?></option> <?php } ?> <?php } ?> </select> </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="custom_status" id="input-status" class="form-control"> <?php if ($custom_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> <div class="form-group"> <label class="col-sm-2 control-label" for="input-sort-order"><?php echo $entry_sort_order; ?></label> <div class="col-sm-10"> <input type="text" name="custom_sort_order" value="<?php echo $custom_sort_order; ?>" placeholder="<?php echo $entry_sort_order; ?>" id="input-sort-order" class="form-control" /> </div> </div> </form> </div> </div> </div> </div> <?php echo $footer; ?>
Again, this should be pretty easy to understand. The purpose of this template file is to provide the configuration form for our custom shipping method. It uses the variables which we set up earlier in the controller file.
So, that's it as far as the back-end file setup is concerned for our custom shipping method. In the next section, we'll see how to enable our custom shipping method and what the custom configuration form looks like!
Enable the Custom Shipping Method
Head over to the admin section and Go to Extensions > Shipping. You should see that our custom shipping method is listed as Custom Rate. Click on the + sign to install our custom shipping method. After installing, you should be able to see the Edit link to open the configuration form. Click on the Edit link and the form should look as shown in the following screenshot.
The important fields in the above form are Tax Class and Geo Zone.
The Tax Class field allows you to select the appropriate option if you need to impose any further tax in addition to the amount defined in the Cost field. Let's select Taxable Goods for now.
The Geo Zone field allows you to select the region to which this method is applicable; for the sake of simplicity select All Zones. Also, make sure to set the status to Enabled, otherwise it won't be listed in the front-end checkout.
Hit the Save button once you've filled in the necessary data and you should be fine. That's it for today's article, and I'll get back to you soon with the next part, which will explain the front-end file setup.
Conclusion
Today, we started a series on how to create a custom shipping method in OpenCart. In this first part, we went through the back-end section and explored how to set up the configuration form. Post your queries and suggestions if any!
Comments