VirtueMart

Magento

WooCommerce

Others

Docs

Support

Demo

Blog

About

Tutorial: Upgrading a VM2 plugin to VM3

Tutorial: Upgrading a VM2 plugin to VM3

HowTo upgrade your VirtueMart2 plugin to VirtueMart3

HowTo upgrade your VirtueMart2 plugin to VirtueMart3

This is a quick overview of several necessary changes when upgrading a VirtueMart2 plugin to VirtueMart3. The following list is by no means meant to be complete, it is just a list of all changes I had to do to upgrade my plugins to VM3. Much of this material comes straight from Milbo's blog "Code adjustments for VirtueMart 3".
This page does NOT try to list all differences needed to upgrade the plugin(s) from Joomla 2.x to Joomla 3. For this, we refer e.g. to https://techjoomla.com/joomla-development/joomla-30-conversion-changes-needed-when-converting-a-common-installable-package-for-joomla-15x-a-25x-to-joomla-30.html

The largest changes to VirtueMart are:

  • VM3 uses Joomla 2 and 3's <config> tags in the plugin's xml file (VM2 used Joomla 1's <params> tags)
  • VM3 introduced some new triggers (mostly similar to those from VM2, just with changed function signature)
  • The custom fields sytem was completely changed internally
  • The cart system was completely changed (affects mainly templates)

Changes to plugins when upgrading from VM2 to VM3 (+backwards compatibility with VM2)

VM2

VM3 (+backwards compatibility with VM2)

All Plugins

  • In the .xml file, change <install ...> to <extension ...>
<install version="1.5" type="plugin" group="vmcustom" method="upgrade">
  ...
</install>
<extension version="2.5" type="plugin" group="vmcustom" method="upgrade">
  ...
</extension>
  • In the .xml file, change <params><param> to <vmconfig><fields><fieldset><field>  (for backwards compatibility, you will also have to keep the <params> to be used by VM2 -- VM3 will ignore the <params> and only use the <config> or <vmconfig> fields, while VM2 will use only the <params> section, so you can even have different configuration options in VM2 and VM3):
<params addpath="/administrator/components/com_virtuemart/elements">
  <param type="vmjpluginwarning" />
  <param type="label" default='VMSHIPMENT_RULES_PLUGIN_ADV' level="level1" />
  <param name="shipment_logos" type="vmfiles" ... /> </params>
<vmconfig>
  <fields name="params" addfieldpath="/administrator/components/com_virtuemart/fields">
    <fieldset name="shipment" label="VMSHIPMENT_RULES_PLUGIN_ADV" >
      <field type="spacer" default="VMSHIPMENT_RULES_PLUGIN_ADV" level="level1"/>
      ...
    </fieldset>
  </fields>
</vmconfig>
<params addpath="/administrator/components/com_virtuemart/elements">   <param type="vmjpluginwarning" />
  <param type="label" default='VMSHIPMENT_RULES_PLUGIN_ADV' level="level1" />
  <param name="shipment_logos" type="vmfiles" ... /> </params> 
  • In the .xml file, use <vmconfig> rather than Joomla's <config>.
    If present, <vmconfig> will be used by VM3 for the shipment/payment methods and the custom field configuration in VM, while <config> can/will be used in Joomla's plugin configuration for plugin-wide configuration (e.g. API keys or update keys, which are not specific to one shipment/payment method or customfield).
    If <vmconfig> is not present, VM3 will use <config> as a fallback. However, these params are also shown in Joomla's plugin configuration.
  • If you use <vmconfig>, it is not neccessary to add the vmjpluginwarning field.
  • All elements (child classes of the JElement baseclass) located in the elements/ subdirectory need to be converted to fields (child classes of JFormField) in the fields/subdirectory: 
if (!class_exists('ShopFunctions'))
  require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'shopfunctions.php');

class JElementVmLengthUnit extends JElement {
  var $_name = 'LengthUnit';

  function fetchElement($nm, $selected, &$node, $control_name) {
    $html = "<input>...</input>";
    return $html;
  }
}
defined('DS') or define('DS', DIRECTORY_SEPARATOR);
if (!class_exists('ShopFunctions'))
  require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'shopfunctions.php');

class JFormFieldVmLengthUnit extends JFormField {
  var $_name = 'vmLengthUnit';

  protected function getInput() {
    $html = "<input>...</input>";
    return $html;
  }
}
  • Make sure to install these fields (in the .xml file):  
<files>
  ...
  <folder>elements</folder>
</files>
<files>
  ...
  <folder>elements</folder>
  <folder>fields</folder>
</files>
  •  JRequest was removed from Joomla, use either JInput (need to create an instance!) or vRequest (static methods like JRequest)
$var = JRequest::getInt('your_param', 0);
$var = JFactory::getApplication()->input->getInt('your_param', 0);
/* or */
$input = new JInput();
$var = $input->getInt('your_param', 0);
/* or */
$var = vRequest::getInt('your_param', 0);
$field = $customfieldsModel->getProductCustomField ($virtuemart_customfield_id);
$this->parseCustomParams($field);
$field = customfieldsModel->getCustomEmbeddedProductCustomField($virtuemart_customfield_id);
// NO parseCustomParams needed any more

Renamed general Triggers, Functions, Methods and Members

function plgVmOnUpdateOrder($data, $old_order_status)
function plgVmOnUpdateOrderPayment($data, $old_order_status)
/* AND */
function plgVmOnUpdateOrderShipment($data, $old_order_status)

Shipping plugins

  • Add a new trigger plgVmDeclarePluginParamsShipmentVM3 (in addition to plgVmSetOnTablePluginParamsShipment for VM2):
 -
function plgVmDeclarePluginParamsShipmentVM3 (&$data) {
  return $this->declarePluginParams ('shipment', $data);
}
  •   The cart as a new dedicated function getST() to retrieve the correct shipping address. Also note that most shipping plugin so far did not evaluate the STSameAsBT flag in VM2:
$address = (($cart->ST == 0) ? $cart->BT : $cart->ST);
if (method_exists($cart, 'getST')) {
$address = $cart->getST();
} else {
$address = (($cart->ST == 0 || $cart->STSameAsBT == 1) ? $cart->BT : $cart->ST);
}

Custom field plugins

 Some database fields have changed. In the constructor, change "custom_params" to "customfield_params":
$this->setConfigParameterable ('custom_params', $varsToPush); 
if(!defined('VM_VERSION') or VM_VERSION < 3){
  $this->setConfigParameterable ('custom_params', $varsToPush);
} else {
  $this->setConfigParameterable ('customfield_params', $varsToPush);
}
In plgVmOnProductEdit change the HTML controls' names from custom_param to customfield_params:
$this->parseCustomParams($field);
...
'custom_param['.$row.'][your_param_name][]'
if (!defined('VM_VERSION') or VM_VERSION < 3) {
  $this->parseCustomParams ($field); // Not needed in VM3!
  $paramName = 'custom_param';
} else {
  $paramName = 'customfield_params';
}
...
$paramName.'['.$row.'][your_param_name][]'
Several new triggers:
function plgVmDeclarePluginParamsCustom($psType,$name,$id, &$data){
  return $this->declarePluginParams('custom', $name, $id, $data);
}
function plgVmDeclarePluginParamsCustomVM3(&$data){
  return $this->declarePluginParams('custom', $data);
}
-
function plgVmGetTablePluginParams($psType, $name, $id, &$xParams, &$varsToPush){
  return $this->getTablePluginParams($psType, $name, $id, $xParams, $varsToPush);
}
function plgVmOnDisplayProductFE( $product, &$idx,&$field){
  ...
}
function plgVmOnDisplayProductVariantFE($field,&$row,&$group) {
  ...
}
function plgVmOnDisplayProductFEVM3(&$product,&$group) {
  // NO $this->parseCustomParams($field); needed any more!
  ..
}
$field = $customfieldsModel->getProductCustomField ($virtuemart_customfield_id);
$this->parseCustomParams($field);
if (!defined('VM_VERSION') or VM_VERSION < 3) { // VM2
$field = $customfieldsModel->getProductCustomField ($virtuemart_customfield_id);
$this->parseCustomParams($field);
} else { // VM3
$field = $customfieldsModel->getCustomEmbeddedProductCustomField($virtuemart_customfield_id);
// NO parseCustomParams needed any more
}
function plgVmDisplayInOrderFE($product, $row, &$html) {
...
}
function plgVmDisplayInOrderFEVM3(&$product, &$productCustom, &$html) {
...
}
$customs = $customModel->getproductCustomslist ($item->virtuemart_product_id);
if (method_exists($customModel, 'getCustomEmbeddedProductCustomFields')) { // V3.x
  $customs = $customModel->getCustomEmbeddedProductCustomFields (array ($item->virtuemart_product_id));
} else {
  $customs = $customModel->getproductCustomslist ($item->virtuemart_product_id); // V2.x
}
To modify the price of a product with a custom field, you now need to use the plgVmPrepareCartProduct trigger and change the modificatorSum variable:  
public function plgVmCalculateCustomVariant($product, &$productCustomsPrice, $selected){
if ($productCustomsPrice->custom_element !==$this->_name) return ;
$customVariant = $this->getCustomVariant($product, $productCustomsPrice, $selected);
// Access input fields from the product page as: $customVariant['your-field-name']
// Access custom field configuration values as: $productCustomsPrice->your-param-name
$productCustomsPrice->custom_price = 1234;
return true;
}
 public function plgVmPrepareCartProduct(&$product, &$customfield,$selected,&$modificatorSum){
if ($customfield->custom_element !==$this->_name) return ;
// Access input fields from the product page as: $selected['your-field-name']
// Access custom field configuration values as: $customfield->your-param-name
$modificatorSum += 1234;
return true;
}
Input controls (on the product page to let the customer enter specific values, like a custom text) have a different form control name customProductData instead of customPlugin (typically this needs to be changed in the template called by renderByLayout, e.g. in /plugins/vmcustom/yourplugin/yourplugin/tmpl/default.php) and have a different array structure:  
$name='customPlugin[' . $viewData[0]->virtuemart_customfield_id . '][' . $this->_name . ']'.
'[customprice]";
if (!defined('VM_VERSION') or VM_VERSION < 3) { // VM2:
$name = 'customPlugin['.$field->virtuemart_customfield_id.']['.$field->custom_element.'][customprice]';
} else {
$name = 'customProductData['.$field->virtuemart_product_id.']['.$field->virtuemart_custom_id.']'.
'['.$field->virtuemart_customfield_id .'][customprice]';
}