AbanteCart Extension’s Developer Guide

The extension subsystem of the AbanteCart is an essential part of shopping cart application. Extensions allow efficient way to add new features to the cart and allow scalability and flexibility of ecommerce application. The extension subsystem of the AbanteCart allows shopping cart project grow indefinitely together with rapidly changing technologies, standards and techniques.
Lots of effort lays on the development of the system and extensions to allow shopping cart administrators and owners smooth and worry free cart experience.
Extension can be installed and managed in the control panel of AbanteCart. If you use market place to purchase extensions inside the control panel, extensions are downloaded and installed seamlessly without leaving your control panel and web browser.
Our extension architecture is partially based on Plexus API concept (http://code.google.com/p/phpplexus/ ) and blend with our own concepts described below.

Glossary

Extension – Collection of directories and files under one main directory that can be added to shopping cart to offer additional feature or function. Extension can be enabled and configured in the admin of shopping cart application
Extension Configuration – Collection of settings that can control the extension functionality of behavior. Configuration can be default (provided with the original installation of the extension) and custom, after installation once admin user change the settings.
Hook – This is the tool used in extension subsystem to connect extension to particular section in application.
Core Hook – This is the hook to the core function of application that allows extending functionality, effect data structures and values.
Template Hook - This is the hook to extend display of the particular page on the front end or admin. This is usually implemented within template file.
Dataset - Ability to save, access and manage data specific for extension. See more details later on.

Basic architecture and structure of the extension subsystem.

This paragraph to give you a generic idea about AbanteCart extension system and organization.
AbanteCart architecture is designed around flexibility and expandability in mind. We believe that application architecture should provide sufficient flexibility to be able extended with new features and configured or adjusted to rapidly changing technology. To do so we introduce concept of fully extension based system and empower developers to create extensions for any new features or functionality.
Extension can be installed and managed in the control panel of the AbanteCart. Marketplace is available to purchase, download and install extensions right in the control panel. In addition, any AbanteCart extension can be installed manually by simple upload of extension folder to AbanteCart and easy activation process.
Extension itself is a collection of files that modify or add functionality of AbanteCart. Extension files include installation scripts, configuration settings, extension MVC file.
Dependency concept is present to make sure that dependent extensions are installed to make extensions that depend on them work properly. Important stages in extension to understand:
Extension load: This is the step of getting extension directory (content with files) in to the AbanteCart extensions directory. This is essential before step before you can install and use extension.
Extension Installation: This is a process of loading extension configuration to the system and making extension visible to the system. This stage does dependency validation and error checking. Install script provided in the extension is executed. Note: Installation is not to be confused with downloading extension or loading extension to the directory. This is done prior to installation. This step also allows you to edit extension configuration and settings.
Enable Extension: This is a step to activate extension. Once extension is enabled it will be used by the system. Uninstall Extension: It is important to understand this step. If you decide no longer to use given extension you can uninstall it. This step will completely remove all the settings and data used by the extension. Uninstall script provided in the extension is executed. Consider backup.
Delete Extension: This is a step to remove extension components from the directory. Note: delete can only be done if extension is uninstalled.

Files and directory structure

All Extensions are located in extensions folder. Bellow is the set of files and directories that represent one extension.

AbanteCart Directory Structure
  • Expand all
  • Collapse all
  • extensions - All Extensions are located in extensions folder.
    • [extension unique name]
      • config.xml - Required configuration xml file with settings for given extension.
      • main.php - Required file that contain init instructions for extension.
      • admin - admin directory structure and contain files for admin section (directory structure under this directory resembles main admin directory for AbanteCart)
      • storefront - storefront directory structure and contain files for storefront section (directory structure under this directory resembles main storefront directory for AbanteCart)
      • core - directory that contain some core addon classes that implement additional functionality ( if needed )
      • install.sql - optional file that contain SQL instructions for extension install
      • uninstall.sql - optional file that contain SQL instructions for extension uninstall
      • install.php - optional file that contain PHP code for extension installation proces.
      • uninstall.php - optional file that contain PHP code for extension uninstall process

Note

you can use PHP or SQL or mixed depending on the requirements.

Directory structure

Pre-install actions - validation
Extensions are installed in control panel from Extension Manager section. Before installation there following checks are performed:

  • - check available space
  • - is extensions directory writable
  • - was extension already installed ( extension upgrade )
  • - does extension support used core version
  • - does hosting support all php modules used by extension
  • - does extension override some controllers/ template files that already overridden by another extension. if two or more extensions override completely same files there will be a conflict, so high priority extension will be used. But, there will be warning in the install about conflict.

Post-install actions

  • - check all files list and sizes
  • - run install process

Extension configuration file

Most of initial and default settings can be provided in configuration file. This files will be process on extension installation and configuration reset steps. Bellow is the description of all items that are available in configuration file

config.xml example:

copy

<?xml version="1.0"?>
<extension>
  <id>discount</id>
  <version>0.1.0</version>
  <cartversions>
        	<item>1.0</item>
  </cartversions>
  <phpmodules>
        	 <item>curl</item>
  </phpmodules>
  <layout></layout>
  <priority>10</priority>
  <type>template</type>
  <category>template</category>
  <dependencies>
       <item prior_version="0.9" version="1.0">slideshow</item>
       <item prior_version="0.9" version="1.0">product_slider</item>
       <item required="true" prior_version="0.9" version="1.0">default_cod</item>
  </dependencies>
  <settings>
  	<item id="discount_status">
  	  	<type>selectbox</type>
  	  	<default_value>0</default_value>
  	  	<variants>
            <item>0</item>
            <item>1</item>
  	  	</variants>
  	</item>
  	<item id="discount_percent">
  	  	<type>input</type>
  	  	<default_value>10</default_value>
  	</item>
  	<item id="discount_message">
  	  	<type>input</type>
  	  	<default_value>We are pleased to annouce september discounts</default_value>
        	</item>
  	<item id="discount_product_message">
  	  	<type>input</type>
  	  	<default_value>Old price: %s, You save: %s</default_value>
  	</item>
</settings>
<additional_settings><![CDATA[setting/setting&active=store]]></additional_settings>
<note>true</note>
<preview>
    <item>preview.jpg</item>
	<item>preview1.jpg</item>
  <item>preview2.jpg</item>
</preview>
<help_link><![CDATA[http://google.com]]></help_link>
<install>
  	 <sql>install.sql</sql>
       <trigger>install.php</trigger>
</install>
<uninstall>
  	<sql>uninstall.sql</sql>
  	<trigger>uninstall.php</trigger>
</uninstall>
</extension>

 		
 		

More details about each extension config item

<id>discount</id> - extension unique named identifier ( should be unique among all other extensions )

<version>0.1.0</version> - current extension version

<cartversions>
<item>1.0</item>
</cartversions>
- supported cart versions by the extension

<dependencies>...</dependencies> - if given extension depends on other extension(s) be installed, this item can be used. See examples:

Optional extension dependency. original extension can work without dependant extension installed. ( use only 0.9 - 1.0 versions of extension )

copy

<dependencies>
  	<item prior_version="0.9" version="1.0">slideshow</item>
</dependencies>
 	
 	

Required extension. Current extension can not work without dependant extension installed.

copy

<dependencies>
<item required="true" prior_version="0.9" version="1.0">default_cod</item> - extension.
</dependencies>

<phpmodules>
  	<item>gd</item>
</phpmodules> - Given extension depends on following php modules
 	
 	

<priority>10</priority> - Extension priority. This will set when current extension needs to be included and ran compare to other extensions installed.
Higher number will make extension with higher priority.

<type>template</type> - extension type. can be 'extensions', 'payment', 'shipping', 'template'. New types might be added in the future.

<category>template</category> - extension category. This mostly used for grouping extensions and not used in AbanteCart itself.

<settings>.. </settings> - contains extension settings
See allowed settings types below.
Example:

copy

 <settings>
       <item id="discount_status">
           <type>selectbox</type>
           <default_value>0</default_value>
           <variants>
               <item>0</item>
               <item>1</item>
           </variants>
       </item>
       <item id="discount_percent">
           <type>input</type>
           <default_value>10</default_value>
       </item>
  </settings>
  
 	

Starting from version 1.1.5 global settings have been implemented. Now common settings such as status, sort_order etc. are not required to be included.
If common settings are still included in given extension configuration file, they will override global settings.
If global setting is not needed, it can be disabled with adding <disabled>true</disabled>
Example:

copy

<item id="payment_minimum_total">
			<disabled>true</disabled>
</item> 	
 	

Global settings are located in core/extension/ directory and attached to top or bottom of the settings page.
Global settings available for any extension (default section) and for extension type as well, such as shipping and payment.


<additional_settings>...</additional_settings> - you can add link to additional extension settings. It could be controller inside extension or any existing admin controller. By default button title will be ‘Additional Settings’. If you want to change it - you should add language definition with key = ‘text_additional_settings’ to your language file - it will define button title
Example:

copy

<additional_settings><![CDATA[setting/setting&active=store]]
></additional_settings>
 	


<note>true</note> - define if extensions have notes or not. notes defined in language file with key = ‘<extension_name>_note’. note will be shown on extension edit page. This note can help with additional information to users how to manage extension or extra steps to set up extension.

<preview>...</preview> - extension preview images. stored in [extension_id]/image/ folder
Example:

copy

<preview>
<item>preview.jpg</item>
<item>preview1.jpg</item>
<item>preview2.jpg</item>
</preview>
 	


<help_link><![CDATA[http://www.google.com]]></help_link> - define help page link. if not empty - you will have help button on extension edit page. This can be external link.

Install and removal items:

copy

<install>
        <sql>install.sql</sql> - sql queries run on install
       <trigger>install.php</trigger> - php code run on install
</install>
<uninstall>
	    	<sql>uninstall.sql</sql> - sql queries run on uninstall
<trigger>uninstall.php</trigger> - php code run on uninstall
</uninstall>
 	

AbanteCart extension support following settings types:

  • hidden
  • input
  • password
  • textarea
  • selectbox
  • multiselect
  • checkbox
  • checkbox group
  • file
  • radio

Settings Values Validation (Starting v1.1.4)

In some cases there is a need to validate settings values provided by user. It is a developers responsibility to maintain stability of extension and AbanteCart core as a result of extension functionality. For this reason important to validate settings not only for required fields, but also for actual values and allowed value types. Validation is possible with 2 methods.

1. Pattern (regular expressions) based validation
2. Code side validation in custom function

Pattern based validation

This is a quick way to set validation to given setting field. Regular expression can be set per each setting filed to allow only specified value in the field.

Example of email validation:

copy

    <item id="email">
		<type>input</type>
		<default_value></default_value>
		<pattern_validate>/^[a-zA-Z0-9_\-.]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-.]+$/</pattern_validate>
    </item>

 	

Note: Error text needs to be added to language XML with key like this: [extention_id]_[item_id]_validation_error or [extention_id]_email_validation_error in example above.

Code side validation

In case more advanced settings values validation required, you need to provide your own code to validate input and generate errors. This is done in a special validate.php file included on the main extension directory level. This file will be automatically included Function inside the file have to be named settingsValidation () { …

Example:

copy

function settingsValidation ( $settings_data = array()) {
    $error_items = array(); // array with failed validation data keys

    foreach($settings_data as $item_id=>$item_value){
        if( !isset($item_value)){
            $error_items[$item_id] = $language->get('errot_text_key') ;
        }
    }
    if ($error_items) {
        return array( 'result' => false,
                      'errors' => $error_items);
    }
    return array('result'=>true);
}
 	

Input: settingsValidation function will get an array with settings and values submitted by user.

Example of input array:

copy

array (
 'store_id' => '0',
 'google_checkout_advanced_status' => '1',
 'google_checkout_advanced_merchant_id' => '11111111111111111111',
 'google_checkout_advanced_merchant_key' => 'dfjsdljadsfkjsdfklsdjfksdl',
 'google_checkout_advanced_currency' => 'USD',
 'google_checkout_advanced_total' => '0',
 'google_checkout_advanced_test' => '1',
 'google_checkout_advanced_merchant_calculation' => '1',
 'google_checkout_advanced_order_status_id' => '1',
 'google_checkout_advanced_location_id' => '0',
 'google_checkout_advanced_sort_order' => '0',
)

 	

Note

settingsValidation function will be call in all settings save and individual (quick) save. Keep in mind that validation needs to be done on each field separately. Required values handled by core and required="true" setting in the config

Return: return needs to be array with result = true/false and error message for each field with failed validation.

If validation passed

copy

array( 'result'=>true );

 	

or if failed

copy

array( 'result'=>false , 'errors' => array( 'field_name' => 'Missing Name' ) );
 	
 	

Main driver file for extension

This is a main file that gets processed every time extension is loaded. It is used to include files used by given extension, define extension resources and do other initial routines.
There are 4 types of resources that extension can use

  • controllers
  • languages
  • models
  • template files

Each resource can be in 2 sections – admin & storefront.

Controllers, languages and models defined as catalog/product
Templates defined with .tpl extension – catalog/product.tpl

Example:

copy

<?php
if ( !defined ( 'DIR_CORE' )) {
	header ( 'Location: static_pages/' );
}
include('core/template2.php');
include('core/template2_pagination.php');

$languages = array(
  	'storefront' => array(
	  'template2/template2',
  	),
  	'admin' => array(
	    	'template2/template2',
  	),
);
$templates = array(
  	'storefront' => array(
	  'common/head.tpl',
	  'common/header.tpl',
	  'common/footer.tpl',
  	),
  	'admin' => array(),
);
?>

 	
 	

Install or Uninstall scripts

There are 2 types of install or removal scripts that can be used. SQL and PHP.
SQL needs to have SQL commands that will be executed on extension install or uninstall respectively.
PHP can have code that will be executed during extension install and removal process. PHP files can include all the methods and classes available in the AbanteCart.

IMPORTANT

Be very careful with updating database and altering tables. Please account for data integrity. If update existing table, account for speed of SQL execution in case of large data in the tables you effect. If uninstall, be careful with data removal. Do not delete altered fields and drop tables that are not created by given extension.
Consider validation for data if you do multiple SQL insert or updates that depends on each other.

Languages

Each extension needs to have it own language files that it is using for text variables.
Extension needs to include all the language that it supports.

IMPORTANT

Please use translation (language based variables) for all the text you use.
This will help everybody and users can edit translation in the control panel if needed.
Refer to languages in developer’s manual.

copy

<?xml version="1.0"?>
<definitions>
 <definition>
   <key>discount_name</key>  - [extension]_name - will be output as an extension name
   <value><![CDATA[Discount on all products]]></value>
 </definition>
 <definition>
   <key>discount_description</key>  - [extension]_name - will be output as an extension description
   <value><![CDATA[discount products extension]]></value>
 </definition>
 <definition>
   <key>discount_percent</key> - translation for settings item
   <value><![CDATA[Discount percent]]></value>
 </definition>
 <definition>
   <key>discount_status</key> - translation for settings item
   <value><![CDATA[Status]]></value>
 </definition>
 <definition>
   <key>discount_status_0</key>  - translation for settings item variant ( in format [item_id][variant_value] - [discount_status][0] )
   <value><![CDATA[Disabled]]></value>
 </definition>
 <definition>
   <key>discount_status_1</key>
   <value><![CDATA[Enabled]]></value>
 </definition>
</definitions>
 	
 	

Hooks

In order to control data structures, functions, flow and views AbanteCart utilize hooks placed in various parts of the code in core, storefront and admin. Each hook will have unique name.
To call a hook you must use extensions api object. It is stored in registry as an extensions and since all classes can use registry properties as their own you can use $this->extensions To have an extensions api hook an object of class “ClassName” method call, you do:

copy

$this->extensions->hkMethodName($object, $param1, $param2);
 	
 	

Basically, when you tell the extensions api to hook ClassName::methodName(), it creates Extension hooks and calls them and the main method as follows (this is not always true and it is more complicated than this, but it is better to express more precise wiring in visual form):

  • Extension::beforeClassNameMethodName() - use it to update/change parameters passed to method
  • Extension::aroundClassNameMethodName() - use it to forbid original method call
  • ClassName::methodName()
  • Extension::onClassNameMethodName() - use it to change return data
  • Extension::afterClassNameMethodName() - use it to run some functions after method is executed

Extension_FlowChart
Extension_FlowChart

Some of the hooks may seem very similar, but they help to serve different purposes.

Extension automatically has access to the current controller object (class instance) loaded during the hooking process. Current controller instance used in the extension class will be available via $this->baseObject and provides access to all public methods and properties in the extended controller. This also includes access to global structures. Refer to developer’s manual for global data structures and controller public methods and properties.

Note

The state of extended controller instance can be different depending on the hook location and stage of execution.

Example of extension class:

copy

class ExtensionName extends Extension {
   public function onClassNameMethodName($param1, $param2) {
   	if ($this->baseObject->property === true) {
       	// ... let's do something now ...
   	}
   }
}

 	

Note

all class properties you want to be used in extensions must be declared as public

Extensions also have the current ExtensionsApi loaded into them so that they can make hookable calls on other objects if needed, such as the base object or another object:

copy

class ExtensionName extends Extension {
            	public function beforeClassNameMethodName($param1, $param2)
            	{
   	// ...
   	$object = new AnotherClass();
   	// ...
       $this->ExtensionsApi->hkAnotherMethod($object, 'this is input param 1');
            	}
            	public function afterClassNameMethodName($param1, $param2) {
   	// ...
       $this->ExtensionsApi->hkAnotherMethod($this->baseObject);
            	}
}
 	
 	

Extensions can even have calls to themselves hooked. You could use the Extension’s ExtensionsApi to accomplish this just as with other objects; however, if you are not overriding the Extension __call() method, there is an easier way:

copy

class ExtensionName extends Extension {
            	public function afterClassNameMethodName($param1, $param2) {
   	// hook $this->performSomeAction()
   	$this->hkPerformSomeAction();
   }
   public function performSomeAction() {
   	// ...
   }
}
 	
 	

When it is a Extension that is being hooked, the base object is not changed. This is usually the desired functionality, but may not always be. However, if one Extension wants to access another Extension, it can do so through its Extension Api:

copy

class AnotherExtensionName extends Extension {
   public function onExtensionNamePerformSomeAction() {
   	$Extension = $this->ExtensionsApi->ExtensionName;
   	// ... do something with the other Extension now
   }
}

 	

Extension can be called on any hook method called. You need to use overload Hooks property for it

copy

class ExtensionDefaultExtensionLogger extends Extension {
            	public $overloadHooks = true;
            	protected $log = '';
            	protected function log($message) {
                           	$this->log .= '[' . date('Y-m-d H:i:s') . "] $message\n";
            	}

            	public function __call($method, $args) {
                           	$this->log("called Extension::$method()");
            	}
            	public function printLog() {
                           	echo '<strong>Extension Call Log</strong><pre>', $this->log, '</ pre>';
            	}
}
 	

Basic examples

You need to control Controller data property to change data that passed to template file . We will place hooks in two places

  • in the very beginning of method - to init some data
  • before call to $this->render() to update data
copy

// in the beginning of file
//use to init controller data
$this->extensions->hk_InitData($this);
.....
//some code
.....
//use to update controller data
$this->extensions->hk_UpdateData($this);

// an extension that use these hooks could look like this
<?php
class ExtensionName extends Extension {

   public function onClassNameMethodName_InitData()
   {
   	// add message variable
   	$this->baseObject->data['message'] = $message;
   }

   public function onClassNameMethodName_UpdateData()
   {
   	// update message variable
   	$this->baseObject->data['message'] = ucfirst($message);
   }
}
 	
 	

If you need to create a hook for some method of a class, for example you need to hook to Cart::getProducts method you can do following:

  • you can update all $this->cart->getProducts calls to $this->extensions->hkgetProducts($cart);
  • or you can change original function
copy

//first change original function
public function getProducts() {
   	$registry = Registry::getInstance();
   	// call extensions api
   	return $registry->get('extensions')->hk_getProducts($this);
   }

// rename the original function, now it will be called
// by extensions api if not overrided
public function _getProducts() {}

and call function as
$this->extensions->hk_getProducts();
 	

Hooks Template Variables

In order to add new data (HTML or just text) to template (.tpl) files from extension AbanteCart allows hook template variables that are placed in .tpl files as regular PHP variables.
Template Hook Variables look like this: <?php echo $this->getHookVar('hook_name'); ?>
These Template Hook Variables can be used in the extension. To add variable use $this->view>addHookVar() method. To locate appropriate template hook variable look into the template file, where you want to add values too.
Example:

copy

// an extension that access  and modify template hook variables:
<?php
class ExtensionName extends Extension {
   public function onClassNameMethodName_UpdateData()
   {
	// Set new value with appending  new data to $hook_message
	$this->baseObject->view->addHookVar('hook_message', 'Confirmation Massage');
   }

 }
 	

Help

Can not add or change values in the base template file or controller? No Problem. Contact us in a forum.abantecart.com and we will help to find solution. If you discover that you need to add something to base template files in AbanteCart storefront or admin, and there is no way to do this or no hook variable available let us know. We can add hooks to current development version and next release.

Extension Process flow:

Once AbanteCart page/response load starts (init.php), all enabled extensions are loaded. At this time system has created instances of all extensions classes that responsible for hooks processing.
Control passed to Action class which is trying to define controller and method based on route variable. It checks if first part of route is an extension name and if it is, tries to load it.
Controller is loaded, it's method is executed and it is ready to render template. System is looking for template file.

  • first it looks for an override file - it will be named as template.override.tpl - any extensions can override template file. But if several extensions tries to override one file, file from extension with higher priority will be used.
  • if override is not found it is looking for template.pre.tpl and template.post.tpl - their content added before and after original template - any extensions can use pre and post files. if several extensions contain pre or post files - they all will be rendered according to extension priority
  • at last it is looking for template.tpl


How to develop Extensions: Let us show how to develop extension based on the example that will apply discount on all products

Extension unique id name will be - discount

  • create directory ../extensions/discount
  • create config file ../extensions/discount/config.xml

At minimum we need to add following settings
1. discount_status - enabled/disabled 2. discount_percent - percent of discount 3. discount message - message that will be shown on cart page 4. discount message for every product - how much you save

copy

<?xml version="1.0"?>
<extension>
   <id>discount</id>
   <version>0.1.0</version>
   <cartversions>
        <item>0.9</item>
        <item>1.0</item>
    </cartversions>
   <dependencies></dependencies>
   <phpmodules></phpmodules>
   <layout></layout>
   <priority>10</priority>
   <type>extensions</type>
   < category>discount</ category>
   <settings>
 <item id="discount_status">
            <type>checkbox</type>
            <default_value>0</default_value>
   	</item>
       <item id="discount_percent">
           <type>input</type>
           <default_value>10</default_value>
       </item>
       <item id="discount_message">
           <type>input</type>
           <default_value>We are pleased to annouce september discounts</default_value>
       </item>
       <item id="discount_product_message">
           <type>input</type>
           <default_value>Old price: %s, You save: %s</default_value>
       </item>
   </settings>
</extension>
 	

- create main driver PHP file ../extensions/discount/main.php

copy

<?php
include('core/discount.php');
<?php
if ( !defined ( 'DIR_CORE' )) {
	header ( 'Location: static_pages/' );
}

$languages = array(
  	'admin' => array(
	  'discount/discount'
  	),
  	'storefront' => array(),
);

$templates = array(
  	'admin' => array(),
  	'storefront' => array(
        	'pages/checkout/cart.tpl'
  	),
);

 	

- create language setting file ../extensions/discount/admin/language/english/discount/discount.xml

copy

<?xml version="1.0"?>
<definitions>
 <definition>
   <key>discount_name</key>
   <value><![CDATA[Discount extension]]></value>
 </definition>
 <definition>
   <key>discount_description</key>
   <value><![CDATA[discount products extension]]></value>
 </definition>
 <definition>
   <key>discount_percent</key>
   <value><![CDATA[Discount percent]]></value>
 </definition>
 <definition>
   <key>discount_message</key>
   <value><![CDATA[Discount message]]></value>
 </definition>
 <definition>
   <key>discount_product_message</key>
   <value><![CDATA[Product message]]></value>
 </definition>
 <definition>
   <key>discount_status</key>
   <value><![CDATA[Status]]></value>
 </definition>
 <definition>
   <key>discount_status_0</key>
   <value><![CDATA[Disabled]]></value>
 </definition>
 <definition>
   <key>discount_status_1</key>
   <value><![CDATA[Enabled]]></value>
 </definition>
</definitions>
 	
 	

- create extension class to handle the work: ../extensions/discount/core/discount.php

copy

<?php
class ExtensionDiscount extends Extension {
   protected $registry;
   public function  __construct() {
   	$this->registry = Registry::getInstance();
   }

}

 	

- add a message about discounts to CheckoutCart step/page 1. Edit file add this function below:
../extensions/discount/core/discount.php

copy

public function onControllerPagesCheckoutCart_InitData() {
$discount_message = $this->registry->get('config')->get('discount_message');
	$this->baseObject->view->addHookVar('additional_message', $discount_message);
 } 	
 	

2. Extension will need to override default cart template to add discount message copy
storefront/view/default/template/pages/checkout/cart.tpl
to
extensions/discount/storefront/view/default/template/pages/checkout/

edit cart.tpl

find

copy

<?php if ($error_warning) { ?>
	<div class="warning"><?php echo $error_warning; ?></div>
<?php } ?>
 	

Insert after it

copy

<?php echo $this->getHookVar('additional_message'); ?>
 	

- now lets apply a discount to each product in cart 1. edit core\lib\cart.php

copy

// change original function to support hooks
public function getProducts() {
   $registry = Registry::getInstance();
   return $registry->get('extensions')->do_getProducts($this);
}

// rename original function
public function _getProducts() {

 	

2. edit extensions\discount\core\discount.php

copy


   // use hook added for getProducts method to apply discount
  	public function onACart_getProducts($products) {
       $discount = $this->registry->get('config')->get('discount_percent');
       foreach ( $products as $key => $p ) {
           $products[$key]['original_price'] = $p['price'];
           $products[$key]['original_total'] = $p['total'];
           $products[$key]['price'] = $p['price'] - $p['price'] * $discount / 100;
           $products[$key]['total'] = $p['total'] - $p['total'] * $discount / 100;
   	}
	}
	
 	

3. edit storefront\controller\checkout\cart.php add these lines

copy

//use to update product data before render
$this->extensions->do_UpdateProduct($this, $result);

after
$this->data['product'] = array(
.....
);
 	
 	

4. edit extensions\discount\core\discount.php

Use updateData hook. Each controller has this hook and can update its data before render output

copy

public function onControllerPagesCheckoutCart_UpdateData() {
   	$controller = $this->baseObject;
  	   $products = $this->registry->get('cart')->getProducts();
   	$data = $controller->view->getData();
  	   $currency = $this->registry->get('currency');
  	   $tax = $this->registry->get('tax');
  	   $config_tax = $this->registry->get('config')->get('config_tax');
  	 foreach ( $data['products'] as $key => $p ) {
       	$product = $products[$p['key']];
  	       $data['products'][$key]['discount_product_message'] = sprintf(
              	   $this->registry->get('config')->get('discount_product_message'),
              	   $currency->format($tax->calculate($product['original_price'], $product['tax_class_id'], $config_tax)),
              	   $currency->format($tax->calculate($product['original_price'] - $product['price'], $product['tax_class_id'], $config_tax))
        	   );
   	}

   	$controller->view->assign('products', $data['products']);

  	}
	
 	

5. edit extensions/discount/storefront/view/default/template/pages/checkout/cart.tpl

Find

copy

<?php foreach ($products as $product) { ?>
....
<?php } ?>
 	
 	


And insert before <?php } ?>

copy

<tr>
   <td colspan="7" align="center">
<?php echo $product['discount_product_message']; ?>
  </td>
</tr>
 	
 	

Additional scripts and styles for extensions

All styles and scripts loaded in head controller. It use document class. If your extension need to load style/script use document class

To add stylesheet use

copy

public function addStyle(array($href, $rel = 'stylesheet', $media = 'screen'))
 	

To add script use

copy

public function addScript($script)
 	
 	
copy

//For example you need to add css and js
// RDIR_TEMPLATE - points to template directory
$document = $registry->get(‘document’);
$document->addStyle(array(RDIR_TEMPLATE.'stylesheet/some.css'));
$document->addScript(RDIR_TEMPLATE.'javascript/some.js');

// in controller you can use this
$this->document->addStyle(array(RDIR_TEMPLATE.'stylesheet/some.css'));
$this->document->addScript(RDIR_TEMPLATE.'javascript/some.js');
 	

Resource Library

To manage resources use resource library. It is designed to

  • upload files
  • add resource code ( youtube html, image html etc )
  • manage resources
  • assign resources to different objects like products, manufacturers, categories, downloads etc

We have following resource types

  • image
  • audio
  • video
  • pdf
  • archive

Example of extension that use resource library

We will create slideshow extension. It will have following features:

  • manage slides
  • slides will use image resources from resource library
  • add block to layout
  • example of how to add styles/scripts that needed for extension

Here we will have a part of extension source. You can take a look at full code by downloading our free template. This extension is a part of template.

- create extensions/slideshow folder - create extensions/slideshow/config.xml

copy

<?xml version="1.0"?>
<extension>
  	<id>slideshow</id>
  	<version>1.0</version>
  	<cartversions>
        	<item>0.9</item>
        <item>1.0</item>
  	</cartversions>
  	<priority>30</priority>
  	<type>extension</type>
  	<category>blocks</category>
  	<settings>
        	<item id="slideshow_status">
              	<type>checkbox</type>
              	<default_value>0</default_value>
        	</item>
        	<item id="slideshow_x">
              	<type>input</type>
              	<default_value>544</default_value>
        	</item>
        	<item id="slideshow_y">
              	<type>input</type>
              	<default_value>305</default_value>
        	</item>
  	</settings>
  	<additional_settings><![CDATA[blocks/slideshow]]></additional_settings>
  	<note>true</note>
  	<install>
        	<sql>install.sql</sql>
        	<trigger>install.php</trigger>
  	</install>
  	<uninstall>
        <sql>uninstall.sql</sql>
        	<trigger>uninstall.php</trigger>
  	</uninstall>
</extension>

 	

In config we define size of slide ( slideshow_x, slideshow_y ) <additional_settings> tag define link for extension controller that will manage slides <note>true</note> - mean that will have description note for extension that will be shown on extension edit page. Note text is defined in language file with definition key = slideshow_note

- create install.sql. Table will keep slide img and slide url

copy

CREATE TABLE IF NOT EXISTS `ac_slides` (
  	`slide_id` int(11) NOT NULL AUTO_INCREMENT,
  	`slide_img` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '',
  	`slide_url` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '',
  	PRIMARY KEY (`slide_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1;

INSERT INTO `ac_slides` ( `slide_img`, `slide_url` ) VALUES ( 'slides/slide1.jpg', 'http://google.com' );
INSERT INTO `ac_slides` ( `slide_img`, `slide_url` ) VALUES ( 'slides/slide2.jpg', 'http://bing.com' );
INSERT INTO `ac_slides` ( `slide_img`, `slide_url` ) VALUES ( 'slides/slide3.jpg', 'http://msn.com' );
 	

- create uninstall.sql

copy

DROP TABLE IF EXISTS `ac_slides`;
 	

- create install.php. Here we will add block to layout. Container for our block will be content_top

copy

<?php
if ( !defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}

$layout = new ALayoutManager();
$block_data = array(
    'block_txt_id' => 'slideshow',
    'controller' => 'blocks/slideshow',
    'templates' => array(
        array(
            'parent_block_txt_id' => 'content_top',
            'template' => 'blocks/slideshow.tpl',
        ),
	),
);
$layout->saveBlock( $block_data );
 	
 	

- create uninstall.php

copy

<?php
if ( !defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}

// delete block
$layout = new ALayoutManager();
$layout->deleteBlock('slideshow');
 	
 	

- create image folder. Here we will have o icon.png – extension icon
o slides/slide1.jpg, slides/slide2.jpg, slides/slide3.jpg – default slides

- create main.php. Here we will define extension resources

copy

<?php
if ( !defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}

$controllers = array(
    'storefront' => array(
        'blocks/slideshow',
	),
    'admin' => array(
        'pages/blocks/slideshow',
	),
);

$models = array(
    'storefront' => array(
        'slideshow/slideshow',
	),
    'admin' => array(
        'slideshow/slideshow',
	),
);

$languages = array(
    'storefront' => array(),
    'admin' => array(
        'slideshow/slideshow',
	),
);

$templates = array(
    'storefront' => array(
        'blocks/slideshow.tpl',
	),
    'admin' => array(
        'pages/blocks/slideshow_list.tpl',
        'pages/blocks/slideshow_form.tpl',
	),
);
 	


Our extension will have

  • 2 controllers – one for admin to manage slides, one for storefront to prepare data for slideshow
  • One language file
  • A couple of templates
  • Two models. Model for storefront has only methods to retrieve data. Model for admin has methods to add/edit/delete items


  • Create language file – extensions/slideshow/admin/language/english/slideshow/slideshow.xml
copy

<?xml version="1.0"?>
<definitions>
  	<definition>
        	<key>slideshow_name</key>
        	<value><![CDATA[slideshow]]></value>
  	</definition>
    <definition>
        	<key>slides_title</key>
        	<value><![CDATA[Edit Slides]]></value>
  	</definition>
    <definition>
        	<key>button_remove</key>
        	<value><![CDATA[Remove]]></value>
  	</definition>
    <definition>
        	<key>button_edit</key>
        	<value><![CDATA[Edit]]></value>
  	</definition>
    <definition>
        	<key>button_add_slide</key>
        	<value><![CDATA[Add Slide]]></value>
  	</definition>
    <definition>
        	<key>text_slide</key>
        	<value><![CDATA[Slide]]></value>
  	</definition>
    <definition>
        	<key>text_success</key>
        	<value><![CDATA[You successfully modified slides]]></value>
  	</definition>
    <definition>
        	<key>entry_img</key>
        	<value><![CDATA[Image]]></value>
  	</definition>
    <definition>
        	<key>entry_url</key>
        	<value><![CDATA[Url]]></value>
  	</definition>
  	<definition>
        	<key>slideshow_status</key>
        	<value><![CDATA[Status]]></value>
  	</definition>
  	<definition>
        	<key>slideshow_status_0</key>
        	<value><![CDATA[Disabled]]></value>
  	</definition>
  	<definition>
        	<key>slideshow_status_1</key>
        	<value><![CDATA[Enabled]]></value>
  	</definition>
  	<definition>
        	<key>slideshow_x</key>
        	<value><![CDATA[Slide Width]]></value>
  	</definition>
  	<definition>
        	<key>slideshow_y</key>
        	<value><![CDATA[Slide Height]]></value>
  	</definition>
    <definition>
        	<key>text_additional_settings</key>
        	<value><![CDATA[ Edit Slides]]></value>
  	</definition>
  	<definition>
        	<key>slideshow_note</key>
        	<value><![CDATA[To add slideshow to your template : <br/>
        	- go to Design -> <a href="#admin#rt=design/layout" target="_layout">Layout</a> <br/>
        	- choose layout you want to add slideshow to<br/>
        	- add it to content_top section
        	<br/>
        	<br/>
        	<b>To access 'Edit slides' page you need to enable extension</b><br/>
        	To edit slides:
        	- click on 'Edit slides' button in top bar<br/>
        	- or <a href="#admin#rt=blocks/slideshow" target="_slideshow">click here</a>
        	]]></value>
  	</definition>
</definitions>
 	


Please note that we have definition key for every extension setting. We also override advanced settings button text and add note.
In note we use link -

copy

<a href="#admin#rt=design/layout" target="_layout">Layout</a>
 	

This link contain two parts
- #admin – application section
- #rt=design/layout – route path

- Now add models. First create model for storefront
extensions\slideshow\storefront\model\slideshow\slideshow.php

copy

<?php
if (! defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}
class ModelSlideshowSlideshow extends Model {

    protected $model_tool_image;
    protected $slide;
    protected $rm;

    public function __construct($registry) {
        parent::__construct($registry);
        $this->model_tool_image = $this->registry->get('model_tool_image');
  		$this->slide = array(
        		'x' => $this->registry->get('config')->get('slideshow_x'),
        		'y' => $this->registry->get('config')->get('slideshow_y'),
  		);
        $this->rm = new AResource('image');
  	}

    public function getSlides() {
        $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "slides");
  		$result = $query->rows;
  		foreach ( $result as $key => $img ) {
              	$result[$key]['slide_img'] = $this->_getSlideUrl($img['slide_img']);
        }

        	return $result;
	}

    public function getSlide($slide_id) {
        $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "slides WHERE slide_id = '".(int)$slide_id."' ");
  		$query->row['preview'] = $this->_getSlideUrl($query->row['slide_img']);
        	return $query->row;
	}

  	private function _getSlideUrl( $img ) {

        	if(!is_file(DIR_RESOURCE . $img)){
              	if(!is_file(DIR_EXT . 'slideshow/image/' . $img)){
                    	$result = $this->model_tool_image->resize('no_image.jpg', $this->slide['x'], $this->slide['y']);
              	}else{
                    	$result = HTTP_EXT.'slideshow/image/' . $img;
              	}
        	}else{

            $id = $this->rm->getIdFromHexPath( str_replace($this->rm->getTypeDir(), '', $img) );
            $result = $this->rm->getResourceThumb($id, $this->slide['x'], $this->slide['y']);
            if ( !$result )
              		$result = $this->model_tool_image->resize('no_image.jpg', $this->slide['x'], $this->slide['y']);
        	}

        	return $result;
  	}

}

 	


This model has two methods to fetch data
also note _getSlideUrl method. Since we have default images we should check if image passed in function is exist in resource directory. If so we try to get resource id from its path and create a thumbnail with width/height defined in extension settings

- Now add model for admin - extensions\slideshow\admin\model\slideshow\slideshow.php

copy

<?php
if (! defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}
class ModelSlideshowSlideshow extends Model {

    protected $model_tool_image;
    protected $slide;
    protected $rm;

    public function __construct($registry) {
        parent::__construct($registry);
        $this->model_tool_image = $this->registry->get('model_tool_image');
  		$this->slide = array(
        		'x' => $this->registry->get('config')->get('slideshow_x'),
        		'y' => $this->registry->get('config')->get('slideshow_y'),
  		);
        $this->rm = new AResource('image');
  	}

    public function getSlides() {
        $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "slides");
  		$result = $query->rows;
  		foreach ( $result as $key => $img ) {
              	$result[$key]['slide_img'] = $this->_getSlideUrl($img['slide_img']);
        }

        	return $result;
	}

    public function addSlide($data) {
        $this->db->query(
            "INSERT INTO " . DB_PREFIX . "slides
            SET slide_img = '".$this->db->escape( $data['slide_img'] )."',
                slide_url = '".$this->db->escape( $data['slide_url'] )."'  ");
	}

    public function editSlide($slide_id, $data) {
        $this->db->query(
            "UPDATE " . DB_PREFIX . "slides
            SET slide_img = '".$this->db->escape( $data['slide_img'] )."',
                slide_url = '".$this->db->escape( $data['slide_url'] )."'
            WHERE slide_id = '".(int)$slide_id."' ");
	}

    public function getSlide($slide_id) {
        $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "slides WHERE slide_id = '".(int)$slide_id."' ");
  		$query->row['preview'] = $this->_getSlideUrl($query->row['slide_img']);
        	return $query->row;
	}

    public function deleteSlide($slide_id) {
        $this->db->query("DELETE FROM " . DB_PREFIX . "slides WHERE slide_id = '".(int)$slide_id."' ");
	}

  	private function _getSlideUrl( $img ) {

        	if(!is_file(DIR_RESOURCE . $img)){
              	if(!is_file(DIR_EXT . 'slideshow/image/' . $img)){
        	        	$result = $this->model_tool_image->resize('no_image.jpg', $this->slide['x'], $this->slide['y']);
              	}else{
                    	$result = HTTP_EXT.'slideshow/image/' . $img;
              	}
        	}else{

            $id = $this->rm->getIdFromHexPath( str_replace($this->rm->getTypeDir(), '', $img) );
            $result = $this->rm->getResourceThumb($id, $this->slide['x'], $this->slide['y']);
            if ( !$result )
              		$result = $this->model_tool_image->resize('no_image.jpg', $this->slide['x'], $this->slide['y']);
        	}

        	return $result;
  	}

}

 	


This model has methods to manage slideshow items – add / edit / delete
Now all ready to create a controller to manage slides in admin
extensions\slideshow\admin\controller\pages\blocks\slideshow.php

copy

<?php
if ( !defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}

class ControllerPagesBlocksSlideshow extends AController {

  	private $error = array();
  	public $data = array();

  	public function main() {

          //init controller data
        $this->extensions->hk_InitData($this);

	  $this->loadLanguage('extension/extensions');
	  $this->loadLanguage('slideshow/slideshow');
	  $this->loadModel('slideshow/slideshow');
        $this->document->setTitle( $this->language->get('heading_title') );

        	$this->view->assign('error_warning', $this->error['warning']);
        	$this->view->assign('success', $this->session->data['success']);
        	if (isset($this->session->data['success'])) {
              	unset($this->session->data['success']);
        	}

	  $this->document->initBreadcrumb( array (
   	       	'href'  	=> $this->html->getSecureURL('index/home'),
   	       	'text'  	=> $this->language->get('text_home'),
  	        	'separator' => FALSE
        	 ));
        $this->document->addBreadcrumb(array(
            'href' => $this->html->getSecureURL('extension/extensions/'.$this->session->data['extension_filter']),
            'text' => $this->language->get('heading_title'),
            'separator' => ' :: '
        ));
        	$this->document->addBreadcrumb(array(
            'href' => $this->html->getSecureURL('extension/extensions/edit', '&extension=slideshow'),
   	     'text' => $this->language->get('slideshow_name'),
            'separator' => ' :: '
        ));
        	$this->document->addBreadcrumb( array (
   	       	'href'  	=> $this->html->getSecureURL('blocks/slideshow'),
   	       	'text'  	=> $this->language->get('slides_title'),
  	        	'separator' => ' :: '
        	 ));

        	$this->data['images'] = $this->model_slideshow_slideshow->getSlides();

        	$this->data['delete'] = $this->html->getSecureURL('blocks/slideshow/delete', '&slide_id=%ID%' );
        	$this->data['update'] = $this->html->getSecureURL('blocks/slideshow/update', '&slide_id=%ID%' );
        	$this->data['insert'] = $this->html->getSecureURL('blocks/slideshow/insert' );

        	$this->data['button_remove'] = $this->html->buildButton(array(
              	'text' => $this->language->get('button_remove'),
              	'style' => 'button2',
        	));
        	$this->data['button_edit'] = $this->html->buildButton(array(
              	'text' => $this->language->get('button_edit'),
              	'style' => 'button2',
        	));
        	$this->data['button_add_slide'] = $this->html->buildButton(array(
              	'text' => $this->language->get('button_add_slide'),
              	'style' => 'button1',
        	));

        $this->view->batchAssign(  $this->language->getASet() );
        	$this->view->batchAssign( $this->data );

        	$this->processTemplate('pages/blocks/slideshow_list.tpl' );

          //update controller data
        $this->extensions->hk_UpdateData($this);
  	}

  	public function insert() {

        //init controller data
        $this->extensions->hk_InitData($this);

	  $this->loadLanguage('slideshow/slideshow');
        $this->loadModel('slideshow/slideshow');
	  $this->document->setTitle($this->language->get('heading_title'));

	  if (($this->request->server['REQUEST_METHOD'] == 'POST')) {
    	    $this->request->post['slide_img'] = html_entity_decode($this->request->post['slide_img'], ENT_COMPAT, 'UTF-8');
            $this->model_slideshow_slideshow->addSlide($this->request->post);
            $this->session->data['success'] = $this->language->get('text_success');
              	$this->redirect( $this->html->getSecureURL('blocks/slideshow') );
	  }
	  $this->_getForm();

        //update controller data
        $this->extensions->hk_UpdateData($this);
  	}

  	public function update() {

        //init controller data
        $this->extensions->hk_InitData($this);

	  $this->loadLanguage('slideshow/slideshow');
        $this->loadModel('slideshow/slideshow');
	  $this->document->setTitle($this->language->get('heading_title'));
	  if (($this->request->server['REQUEST_METHOD'] == 'POST')) {
            $this->request->post['slide_img'] = html_entity_decode($this->request->post['slide_img'], ENT_COMPAT, 'UTF-8');
              	$this->model_slideshow_slideshow->editSlide($this->request->get['slide_id'], $this->request->post);
              	$this->session->data['success'] = $this->language->get('text_success');
              	$this->redirect( $this->html->getSecureURL('blocks/slideshow') );
        	}
	  $this->_getForm();

   	 //update controller data
        $this->extensions->hk_UpdateData($this);
  	}

  	public function delete() {

        //init controller data
        $this->extensions->hk_InitData($this);

	  $this->loadLanguage('slideshow/slideshow');
        $this->loadModel('slideshow/slideshow');
	  $this->model_slideshow_slideshow->deleteSlide($this->request->get['slide_id']);
        $this->session->data['success'] = $this->language->get('text_success');
        	$this->redirect( $this->html->getSecureURL('blocks/slideshow') );

        	//update controller data
        $this->extensions->hk_UpdateData($this);
  	}

  	private function _getForm() {

        	$this->view->assign('error_warning', $this->error['warning']);
        	$this->view->assign('success', $this->session->data['success']);
        	if (isset($this->session->data['success'])) {
              	unset($this->session->data['success']);
        	}
        	$this->view->batchAssign(  $this->language->getASet() );

	  $this->data = array();
        	$this->data['error'] = $this->error;
        	$this->data['cancel'] = $this->html->getSecureURL('blocks/slideshow');

        	$this->data['heading_title'] = $this->language->get('slides_title');

        	$this->document->initBreadcrumb( array (
   	       	'href'  	=> $this->html->getSecureURL('index/home'),
   	       	'text'  	=> $this->language->get('text_home'),
  	        	'separator' => FALSE
        	 ));
        $this->document->addBreadcrumb(array(
            'href' => $this->html->getSecureURL('extension/extensions/'.$this->session->data['extension_filter']),
            'text' => $this->language->get('heading_title'),
            'separator' => ' :: '
        ));
        	$this->document->addBreadcrumb(array(
            'href' => $this->html->getSecureURL('extension/extensions/edit', '&extension=slideshow'),
	        'text' => $this->language->get('slideshow_name'),
            'separator' => ' :: '
        ));
        	$this->document->addBreadcrumb( array (
   	       	'href'  	=> $this->html->getSecureURL('blocks/slideshow'),
   	       	'text'  	=> $this->language->get('slides_title'),
  	        	'separator' => ' :: '
        	 ));


        	if (isset($this->request->get['slide_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
  	        	$item_info = $this->model_slideshow_slideshow->getSlide($this->request->get['slide_id']);
	  }

        	$fields = array('slide_img', 'slide_url');
        	foreach ( $fields as $f ) {
              	if (isset ( $this->request->post [$f] )) {
                    	$this->data [$f] = $this->request->post [$f];
              	} elseif (isset($item_info[$f])) {
                    	$this->data[$f] = $item_info[$f];
              	} else {
                    	$this->data[$f] = '';
              	}
        	}

        if (!isset($this->request->get['slide_id'])) {
              	$this->data['action'] = $this->html->getSecureURL('blocks/slideshow/insert' );
              	$this->data['form_title'] = $this->language->get('text_insert') . $this->language->get('text_slide');
        	} else {
              	$this->data['action'] = $this->html->getSecureURL('blocks/slideshow/update', '&slide_id=' . $this->request->get['slide_id'] );
              	$this->data['form_title'] = $this->language->get('text_edit') . $this->language->get('text_slide');
        	}

        $form = new AForm('ST');

        	$this->document->addBreadcrumb( array (
              	'href'  	=> $this->data['action'],
              	'text'  	=> $this->data['form_title'],
              	'separator' => ' :: '
        	 ));

        	$form->setForm(array(
        		'form_name' => 'frm',
              	'update' => '',
  		));

        $this->data['form']['id'] = 'frm';
        $this->data['form']['form_open'] = $form->getFieldHtml(array(
        		'type' => 'form',
        		'name' => 'frm',
        		'action' => $this->data['action'],
  		));
        $this->data['form']['submit'] = $form->getFieldHtml(array(
        		'type' => 'button',
        		'name' => 'submit',
        		'text' => $this->language->get('button_save'),
        		'style' => 'button1',
  		));
        	$this->data['form']['cancel'] = $form->getFieldHtml(array(
        		'type' => 'button',
        		'name' => 'cancel',
        		'text' => $this->language->get('button_cancel'),
        		'style' => 'button2',
  		));

        $this->data['form']['fields']['img'] = $form->getFieldHtml(
            array(
                'type' => 'hidden',
                'name' => 'slide_img',
                'value' => htmlspecialchars($this->data['slide_img'], ENT_COMPAT, 'UTF-8'),
        		)
        );
        $this->data['form']['fields']['url'] = $form->getFieldHtml(
            array(
                'type' => 'input',
                'name' => 'slide_url',
                'value' => $this->data['slide_url'],
                'attr' => 'class="large-field"',
        		)
        );

        $resources_scripts = $this->dispatch(
            'responses/common/resource_library/get_resources_scripts',
            array(
                'object_name' => '',
                'object_id' => '',
                'types' => 'image',
                'mode' => 'url',
            )
        );
        $this->data['resources_scripts'] = $resources_scripts->dispatchGetOutput();
        if ( isset($this->request->get['slide_id']) && is_file( DIR_RESOURCE . $item_info['slide_img']) ) {
            $rm = new AResourceManager();
            $rm->setType('image');
            $id = $rm->getIdFromHexPath( str_replace($rm->getTypeDir(), '', $item_info['slide_img']) );
            $thumb = $rm->getResourceThumb(
                $id,
                $this->config->get('config_image_product_width'),
                $this->config->get('config_image_product_height')
            );
            $this->view->assign( 'thumb', $thumb);
        	}

        $this->view->batchAssign( $this->data );
        $this->processTemplate('pages/blocks/slideshow_form.tpl' );
  	}


}

 	

This controller is work as usual application controller
Main method – fetch all slides and give ability to add/edit/delete slides
Insert – insert new slide
Update – edit slide image/url
Delete – delete slide



Lets look in details how we include resource library scripts

copy

 $resources_scripts = $this->dispatch(
            'responses/common/resource_library/get_resources_scripts',
            array(
                'object_name' => '',
                'object_id' => '',
                'types' => 'image',
                'mode' => 'url',
            )
        );
        $this->data['resources_scripts'] = $resources_scripts->dispatchGetOutput();

 	

$this->dispatch – call controller with arguments and return dispatch object $resources_scripts->dispatchGetOutput(); - return controller output, which is passed to template later

This controller use two templates

  • slideshow_list.tpl – show all slides
  • slideshow_form.tpl – create/update form

Now lets create storefront controller
extensions\slideshow\storefront\controller\blocks\slideshow.php

copy

<?php
if (! defined ( 'DIR_CORE' )) {
  	header ( 'Location: static_pages/' );
}

class ControllerBlocksSlideshow extends AController {
  	public function main() {

        //init controller data
        $this->extensions->hk_InitData($this);
        $this->loadModel('slideshow/slideshow');

        if ( !$this->registry->has('jcycle') ) {
            $this->document->addScript( $this->view->templateResource('/javascript/jquery/jquery.cycle.js') );
            $this->registry->set('jcycle', true);
        }
        	$this->document->addStyle(
              	array(
                    	 'href' => $this->view->templateResource('/stylesheet/slideshow.css'),
                    	 'rel' => 'stylesheet',
                    	 'media' => 'screen',
              	)
        	);

        	$this->view->assign('images', $this->model_slideshow_slideshow->getSlides());

        $this->processTemplate('blocks/slideshow.tpl');

        //init controller data
        $this->extensions->hk_UpdateData($this);

  	}
}

 	
copy

$this->document->addScript & $this->document->addStyle</pre> methods add styles and scripts in document object and will be added in head tag
Please note that
<pre class="brush:php;">
            $this->registry->set('jcycle', true);

 	

Used to avoid loading multiple instances of scripts in order you will have several slideshows on one page

Storefront template include simple slideshow html.

You can take a look at full code by downloading our free template. This extension is a part of template.

Example of extension with listing block wrapper (appearance controller)

1. Load custom listing block settings to the layout. Best way to do this is via XML load.

2. Add a hook into your extension on ControllerPagesDesignBlocks (Control Panel side). This will add controller(wrapper) to selection in control panel and show listing on storefront using your templates. For details see Basic Examples

copy

public function onControllerPagesDesignBlocks_InitData() {
$this->baseObject->data[ 'block_wrappers' ]['blocks/extension_listing_controller'] = '[extension_name]';
	}
 	

3. In main.php file of your extension add list of templates which will be used in list view. Number of templates depends on your block listing extensions controller.

copy

$templates = array(
    'storefront' => array('blocks/some-template-name1.tpl',
 'blocks/some-template-name2.tpl',
 'blocks/some-template-name3.tpl',
 'blocks/some-template-name4.tpl',....));
 	

4. Place above template files into folder «[extension_name]/storefront/view/default/templates/blocks/»

5. Create listing block controller file in «[extension_name]/storefront/controller/blocks»

copy

<?php
if (! defined ( 'DIR_CORE' )) {
	header ( 'Location: static_pages/' );
}
class ControllerBlocksExtensionName extends AController {
	public $data;
	public function main() {

        //init controller data
        $this->extensions->hk_InitData($this);

		$block_data = func_get_args(1);
		$parent_block_txt_id = $block_data[0];
		$block_data = $block_data[1];

		if($block_data){
			$this->view->assign('content',$block_data['content']);
			$this->view->assign('heading_title', $block_data['title'] );
			switch($parent_block_txt_id){
				case 'content_top':
					$template = ''blocks/some-template-name1.tpl'';
					break;
				case 'column_left':
					$template = ''blocks/some-template-name3.tpl'';
					break;
				case 'column_right':
					$template = ''blocks/some-template-name4.tpl'';
					break;

				default:
					$template = ''blocks/some-template-name2.tpl'';
					break;
			}
			$this->processTemplate( $template );
		}

  	}

}
?>
 	

6. Add your controller into main.php to $controllers array:

copy

$controllers = array(
    'storefront' => array('blocks/[extension_name]' ));

 	

Package extension for distribution

In order to have smooth users experience with extension installation and management all extension developers need to comply and follow requirements for AbanteCart extension API and distribution.
Below is a Checklist with some suggestions and guidance to extension construction:

  • All code provided in the extension needs to be clean, well organized and free of bugs.
  • Avoid inclusion of unused or untested codes or files.
  • Avoid replacement of files and limit logical replacement of files.
  • If your extension is dependant on some other extension, make sure to include dependency in configuration. Installation process will check that for you.
  • Place all text in language files and include in the package
  • Include all configuration that is necessary to set up the extension
  • Include all necessary instruction for installation and configuration
  • Include a disclaimer or legal document for the extension. It will be shown before installation.
  • Include .htaccess and index.php in all directories under your extension to prevent unauthorised access to files.
  • Perform check in every PHP file for blocking direct access from the browser. Use code below or similar in all PHP files:
copy

if (! defined ( 'DIR_CORE' )) {
	header ( 'Location: static_pages/' );
}
 	
  • Do testing of the extension and it installation and uninstall process.

Distribution should include the following items. Items marked “required” needs to be included.

  • config.xml (required)
  • install and uninstall php and/or sql
  • image (directory to keep preview images and icon) (required)
  • main.php (required )
  • admin (code for control panel)
  • storefront (code for store front)
  • core (core code for the extension)
  • documentation ( any documentation needed for extension)

Details about most of extension components are covered in extension API development documentation.
In image directory include icon and preview images for your extension. Icon will be shown in market place and the extension menu and preview will be shown in extension details page before download and in the extension configuration section. Naming for icon and preview images:

       	icon image:            	image/icon.png (Size: 57x57 px)
       	main preview:       	image/preview.jpg (Size: 350 x 350 px)
       	additional preview: image/preview1.jpg (Size: 350 x 350 px)
                                          	image/preview2.jpg (Size: 350 x 350 px)
                                          	more images as needed ...

Extension naming and description is defined in language files
Extension name is defined with definition key = <extension>_name
Extension description is defined with definition key = <extension>_note . Please note that in order to show extension description you should enable it in extension config like <note>true</note>

Archiving instructions:
Final completed package needs to be archived to zip format with all files/directories hierarchy starting from root directory.

An example of directory structure for package:

<ext> - V1.0

  • code – contain extension code starting from application root. ( /extensions/<extention>…. )
  • manuals – help files
  • copyright.txt
  • license.txt
  • package.xml
copy

<?xml version="1.0"?>
<package>
    <id>myextention</id>
    <type>extension</type>
  	<version>1.0.0</version>
  	<minversion>1.0.0</minversion>
  	<cartversions>
        	<item>0.9</item>
        	<item>1.0</item>
  	</cartversions>
  	<package_content>
        	<extensions>
              	<extension>myextention</extension>
        	</extensions>
  	</package_content>
</package>

 	

Note

- current version of extension - start version which can be upgraded.) have to contain only two parts - major and minor versions.

If you intend to resale extension via AbanteCart market place, please make sure you are compliant with requirements as much as possible. This will help your extension to be approved faster and get better ranking.