Custom Plugin Fields in Virtuemart 2: Part 2
Part 2. Programming Custom Plugin Fields
In part 1 of this survey we looked at how to install and use custom plugin fields in Virtuemart 2. We will now look at programming custom plugins. This article is mainly aimed at programmers, however others may find it useful as an overview of how custom plugins work. This article is not a basic 'how-to' guide for creating a custom plugin, because it assumes that you are already familiar with the basics of creating Joomla plugins. If not, you can find a useful introduction to Joomla plugins on the Joomla documentation site. What we will do is survey the main issues involved in programming a Virtuemart custom plugin.
One of the big advances in Virtuemart 2 over its predecessors is its support for a plugin framework. This framework includes potential support for a wide variety of plugin groups - I say 'potential' because some of these groups do not seem to be developed yet. As well as payment and shipping plugin groups, the Virtuemart framework includes currency, coupon, calculation, shopper, extended (to add extra features into the Virtuemart core), and the custom plugin group that we will be discussing.
If you want to explore these other plugin groups there are abstract classes defined for all these in the folder administrator/components/com_virtuemart/plugins. To see examples of the custom, payment and shipping plugins you can look at the Virtuemart all-in-one installer component, in the admin/plugins folder, which contains examples of all three groups. There is also some developer documentation available on the Virtuemart site here.
Custom Plugins
Custom plugins (also known as product plugins) are used to add extra functionality to the Virtuemart product. There are three custom plugins that are distributed with Virtuemart: the specification plugin, which allows you to create searchable custom specifications for a product; the stockable variants plugin, which allows you to display child products as variants that are selectable in the parent product add-to-cart button; and the text input plugin, which allows the customer to add their own customized text when ordering a product. If you want to create your own custom plugin for Virtuemart you will probably find it very helpful to study the code in these three plugins in addition to reading this article. We will also draw some examples from our free download field plugin available here (for free of course).
Plugin Basics
The first thing to note about Virtuemart 2 plugins is that they are Joomla plugins, meaning that they can be installed using the Joomla installer, and require an xml installation manifest. However this manifest is unusual because for Joomla 1.6+ it uses the Joomla 1.5 <param> tag to define the plugin parameters, rather than normal the <field> tag. This is because the parameters are set through the Virtuemart admin, not the Joomla plugin manager, so the <field> tag is not required. For this reason you should include the following in your xml manifest:-
<params addpath="/administrator/components/com_virtuemart/elements"> <param type="vmjpluginwarning" /> </params>
This will output a message warning the administrator that parameters should be set through Virtuemart.
As with general Joomla plugins, at a minimum the plugin should consist of the xml manifest plugin plus a PHP code file, which should be defined as normal in the manifest. The plugin will belong to the to vmcustom plugin group, so this should also be set in the <extension> tag:-
<extension version="2.5" type="plugin" group="vmcustom" method="upgrade">
PHP Code
A custom field plugin should extend the vmCustomPlugin class, you will need to include this at the start of your plugin php file:-
if (!class_exists('vmCustomPlugin')) require(JPATH_VM_PLUGINS . DS . 'vmcustomplugin.php');
This class defines various methods which we use to create the plugin functionality.
Database Tables
When a user creates an instance of the plugin field the definition is stored in the virtuemart_customs database table. You will need to define to the plugin parameters that must be stored in the table, through the following code in the constructor:-
function __construct(& $subject, $config) { parent::__construct($subject, $config); $this->_tablepkey = 'id'; $this->tableFields = array_keys($this->getTableSQLFields()); $this->varsToPush = array( /* array of parameter definitions here */ ); $this->setConfigParameterable('custom_params',$this->varsToPush); }
For example our free download plugin includes the following definition for the parameters:
$varsToPush = array( 'media_id'=>array(0,'int'), 'requires_registration'=>array(0,'int'), 'shopper_groups'=>array(array(),'array'), 'redirect_url'=>array('','char'), 'title'=>array('','char'), 'description'=>array('','char'), 'loadStylesheet'=>array('','char') );
Each definition is an array consisting of the default value and the parameter type.
Data Table
Some plugins also require their own data tables, which should be named after the plugin. For example the specification plugin uses a database table called 'virtuemart_product_custom_plg_specification'. In order to use a data table the plugin should include calls to the methods getVmPluginCreateTableSQL() and getTableSQLFields(), which defines the SQL for the table creation. for example the specification plugin includes the following:-
public function getVmPluginCreateTableSQL() { return $this->createTableSQL('Product Specification Table'); } function getTableSQLFields() { $SQLfields = array( 'id' => 'int(11) unsigned NOT NULL AUTO_INCREMENT', 'virtuemart_product_id' => 'int(11) UNSIGNED DEFAULT NULL', 'virtuemart_custom_id' => 'int(11) UNSIGNED DEFAULT NULL', 'custom_specification_default1' => 'varchar(1024) NOT NULL DEFAULT \'\' ', 'custom_specification_default2' => 'varchar(1024) NOT NULL DEFAULT \'\' ' ); return $SQLfields; }
However we found that in order to ensure that the table was actually created we also needed to include the following call in the plugin constructor:-
$this->onStoreInstallPluginTable($this->_psType);
Alternatively you can explicitly create the table through running a custom installation script (which is possible with plugins for Joomla 2.5 but not 1.5).
Creating the Backend Administration
The HTML to generate the parameter form should be returned by the method plgVmOnProductEdit($field, $product_id, &$row,&$retValue). This method should define a set of form fields for the parameter input by attaching them to the end of the $retValue parameter, and return a boolean value of true. I suggest studying the versions of this method in the specification, stockable and textinput plugins for ideas on how to define these. The method output can be complex and include javascript as well as HTML.
There are a couple of things to note, firstly you gain access to the current values of the parameters with a call to
$this->parseCustomParams($field);
The values can then be accessed as properties of the $field object, eg $field->param_name , where param_name is replaced by the actual parameter name. The input fields are defined as a two-dimensional array indexed by $row and the parameter name, eg custom_param['.$row.'][param_name].
There is also a method to generate output when an order for a product is viewed:-
function plgVmDisplayInOrderBE($item, $row, &$html)
This is only relevant if the plugin is defined as a cart attribute (see below), and can be used to display custom information about the order, and even to allow some modification of the order.
Frontend Output
Depending on whether the plugin custom field type is or is not a cart attribute, there are two methods which are used to generate the front-end output. Only one of these methods will be appropriate for a particular plugin, and it should be made clear to the user whether or not the plugin should be a cart attribute
Non-cart Attributes
If a plugin is not defined as a cart attribute then it just generates some extra output which is displayed on the product page. It does not affect the add-to-cart operations. An example is our free download field, which simply displays a download link on the product page.
In this case the appropriate method to generate the output is:-
function plgVmOnDisplayProductFE($product,&$idx,&$field)
Cart Attributes
If a plugin is defined as a cart attribute then in some way it will affect the cart functionality. An example is the stockable variants plugin.
In this case the method to generate the output is
function plgVmOnDisplayProductVariantFE($field,&$idx,&$group)
In either case the method should include the following code to stop execution when it is not appropriate.
if ($field->custom_element != $this->_name) return '';
As in the backend a call to
$this->parseCustomParams($field);
will provide access to the parameter values.
Other Methods
The plugin should also call the following methods to ensure its correct functioning:-
protected function plgVmOnStoreInstallPluginTable($psType) { return $this->onStoreInstallPluginTable($psType); }
function plgVmDeclarePluginParamsCustom($psType,$name,$id, &$data){ return $this->declarePluginParams($psType, $name, $id, $data); }
function plgVmSetOnTablePluginParamsCustom($name, $id, &$table){ return $this->setOnTablePluginParams($name, $id, $table); }
function plgVmOnDisplayEdit($virtuemart_custom_id,&$customPlugin){ return $this->onDisplayEditBECustom($virtuemart_custom_id,$customPlugin); }