Skip to content
Snippets Groups Projects
Commit 0a856e10 authored by Reinhold Kainhofer's avatar Reinhold Kainhofer
Browse files

V4.0: Public release after the library factorization

parent 9784c4d4
Branches
Tags V4.0
No related merge requests found
BASE=ordernumber BASE=ordernumber
PLUGINTYPE=vmshopper PLUGINTYPE=vmshopper
VERSION=3.99a VERSION=4.0
PLUGINFILES=$(BASE).php ordernumber_helper_joomla.php $(BASE).script.php $(BASE).xml index.html library/ PLUGINFILES=$(BASE).php ordernumber_helper_joomla.php $(BASE).script.php $(BASE).xml index.html library/
......
...@@ -80,15 +80,10 @@ PLG_ORDERNUMBER_REPL_OP_CONTAINS="enthält" ...@@ -80,15 +80,10 @@ PLG_ORDERNUMBER_REPL_OP_CONTAINS="enthält"
PLG_ORDERNUMBER_REPL_OP_STARTS="beginnt mit" PLG_ORDERNUMBER_REPL_OP_STARTS="beginnt mit"
PLG_ORDERNUMBER_REPL_OP_ENDS="endet mit" PLG_ORDERNUMBER_REPL_OP_ENDS="endet mit"
PLG_ORDERNUMBER_FIELDSET_CREDENTIALS="Automatische Aktualisierung"
PLG_ORDERNUMBER_CREDENTIALS_DESC="Bitte geben Sie hier die Bestellungsnummer und -passwort ein, die Sie in der Bestellbestätigung (per Mail) von <a href="https://www.open-tools.net/">open-tools.net</a> erhalten haben. Wird keine gültige Kombination eingegeben, ist die Funktionalität der Erweiterung in keiner Weise eingeschränkt, jedoch stehen keine automatischen Aktualisierungen zur Verfügung."
PLG_ORDERNUMBER_ORDERNUMBER="Bestellungsnummer (lt. Rechnung):"
PLG_ORDERNUMBER_ORDERNUMBER_DESC="Bitte geben Sie hier die Bestellungsnummer Ihres Kaufs der Erweiterung auf open-tools.net an. Wenn keine gültige Kombination aus Bestellungsnummer und -passwort angegeben wird, funktioniert die Erweiterung dennoch, jedoch sind keine automatischen Aktualisierungen möglich."
PLG_ORDERNUMBER_ORDERPASS="Bestellungspassword (lt. Rechnung):"
PLG_ORDERNUMBER_ORDERPASS_DESC="Bitte geben Sie hier das Bestellungspasswort Ihres Kaufs der Erweiterung auf open-tools.net an. Wenn keine gültige Kombination aus Bestellungsnummer und -passwort angegeben wird, funktioniert die Erweiterung dennoch, jedoch sind keine automatischen Aktualisierungen möglich."
OPENTOOLS_FIELDSET_CREDENTIALS="Automatische Aktualisierung"
OPENTOOLS_CREDENTIALS_DESC="Bitte geben Sie hier die Bestellungsnummer und -passwort ein, die Sie in der Bestellbestätigung (per Mail) von <a href="https://www.open-tools.net/">open-tools.net</a> erhalten haben. Wird keine gültige Kombination eingegeben, ist die Funktionalität der Erweiterung in keiner Weise eingeschränkt, jedoch stehen keine automatischen Aktualisierungen zur Verfügung."
OPENTOOLS_ORDERNUMBER="Bestellungsnummer:"
OPENTOOLS_ORDERNUMBER_DESC="Bitte geben Sie hier die Bestellungsnummer Ihres Kaufs der Erweiterung auf open-tools.net an. Wenn keine gültige Kombination aus Bestellungsnummer und -passwort angegeben wird, funktioniert die Erweiterung dennoch, jedoch sind keine automatischen Aktualisierungen möglich."
OPENTOOLS_ORDERPASS="Bestellungspassword:"
OPENTOOLS_ORDERPASS_DESC="Bitte geben Sie hier das Bestellungspasswort Ihres Kaufs der Erweiterung auf open-tools.net an. Wenn keine gültige Kombination aus Bestellungsnummer und -passwort angegeben wird, funktioniert die Erweiterung dennoch, jedoch sind keine automatischen Aktualisierungen möglich."
OPENTOOLS_CHECK_CREDENTIALS="Zugangsdaten überprüfen und speichern"
OPENTOOLS_CHECK_CREDENTIALS_ERROR="Konnte Zugangsdaten nicht überprüfen. Bitte stellen Sie sicher, dass die Erweiterung in Joomla freigegeben ist!"
OPENTOOLS_XMLMANIFEST_ERROR="Konnte die XML-Manifest-Datei der Erweiterung nicht laden (%s)"
OPENTOOLS_UPDATESCRIPT_ERROR="Konnte die Aktualisierungsinformationen nicht laden (%s)"
OPENTOOLS_COMMERCIAL_UPDATES_J25="Automatische Aktualisierungen von kommerziellen Erweiterungen sind in Joomla 2.5 leider nicht möglich (erste ab Joomla 3.x). Bitte informieren Sie sich auf der Homepage des Entwicklers über mögliche Aktualisierungen und installieren Sie diese manuell."
...@@ -2,8 +2,24 @@ ...@@ -2,8 +2,24 @@
table.ordernumber-countertable { table.ordernumber-countertable {
border: 1px solid #888888; border: 1px solid #888888;
display: inline-table; display: inline-table;
width: inherit;
} }
table.ordernumber-countertable td, table.ordernumber-countertable th {
padding-bottom: 0px;
padding-top: 0px;
}
table.ordernumber-countertable thead th {
text-align: center;
width: auto;
}
table.ordernumber-countertable thead > tr:nth-child(odd) > th,
table.ordernumber-countertable tfoot > tr.addcounter_row > td {
background: #E0E0E0;
}
table.ordernumber-countertable tbody > tr:nth-child(even) > td {
background: #F0F0F0;
}
table.ordernumber-countertable.table-striped tbody > tr:nth-child(odd) > th { table.ordernumber-countertable.table-striped tbody > tr:nth-child(odd) > th {
background: #E0E0E0; background: #E0E0E0;
} }
...@@ -20,7 +36,7 @@ td.counter_value { ...@@ -20,7 +36,7 @@ td.counter_value {
fieldset table.ordernumber-countertable img { fieldset table.ordernumber-countertable img {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
div.ordernumber-ajax-loading, div.ordernumber-counter-addbtn { div.ordernumber-ajax-loading, div.ordernumber-counter-addbtn {
display: inline; display: inline;
...@@ -85,7 +101,7 @@ table.ordernumber_variables tbody tr td input { ...@@ -85,7 +101,7 @@ table.ordernumber_variables tbody tr td input {
table.ordernumber_variables img { table.ordernumber_variables img {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
tr.rowhidden { tr.rowhidden {
display: none; display: none;
......
...@@ -34,7 +34,7 @@ var handleJSONResponse = function (json, counter) { ...@@ -34,7 +34,7 @@ var handleJSONResponse = function (json, counter) {
if ('updateMessages' in ajax_ordernumber) { if ('updateMessages' in ajax_ordernumber) {
ajax_ordernumber.updateMessages(json['messages'], "ordernumber"); ajax_ordernumber.updateMessages(json['messages'], "ordernumber");
} }
if (!json.authorized) { if (!json.authorized && !json.success) {
alert(ajax_ordernumber.ORDERNUMBER_JS_NOT_AUTHORIZED); alert(ajax_ordernumber.ORDERNUMBER_JS_NOT_AUTHORIZED);
} else if (json.error) { } else if (json.error) {
alert(json.error); alert(json.error);
...@@ -210,14 +210,14 @@ var ordernumberVariablesAddRow = function (template, element) { ...@@ -210,14 +210,14 @@ var ordernumberVariablesAddRow = function (template, element) {
jQuery(document).ready (function () { jQuery(document).ready (function () {
jQuery('img.ordernumber-replacement-deletebtn').click( jQuery('img.ordernumber-replacement-deletebtn').click(
function () { function () {
jQuery(this).closest('tr').remove();
var count = jQuery(this).closest('table').find('tbody tr').length; var count = jQuery(this).closest('table').find('tbody tr').length;
if (count==0) { if (count<=1) {
jQuery("tr#ordernumber-replacements-empty-row") jQuery("tr#ordernumber-replacements-empty-row")
.removeClass("rowhidden") .removeClass("rowhidden")
.find('input,select,button,img') .find('input,select,button,img')
.removeAttr('disabled'); .removeAttr('disabled');
} }
jQuery(this).closest('tr').remove();
} }
); );
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
if ( !defined( 'ABSPATH' ) && !defined('_JEXEC') ) { if ( !defined( 'ABSPATH' ) && !defined('_JEXEC') ) {
die( 'Direct Access to ' . basename( __FILE__ ) . ' is not allowed.' ); die( 'Direct Access to ' . basename( __FILE__ ) . ' is not allowed.' );
} }
// NULL function to indicate translatable strings without actually doing any translation
function trl($string) {
return $string;
}
class OrdernumberHelper { class OrdernumberHelper {
static $_version = "0.1"; static $_version = "0.1";
...@@ -19,6 +23,9 @@ class OrdernumberHelper { ...@@ -19,6 +23,9 @@ class OrdernumberHelper {
'variable-table-class' => "", 'variable-table-class' => "",
'variable-table-style' => "", 'variable-table-style' => "",
); );
protected $flags = array(
'extract-counter-settings' => 1,
);
/** /**
* An array containing all language keys for the translations used in the JavaScript code. * An array containing all language keys for the translations used in the JavaScript code.
* Make sure to set those in the ajax_ordernumber JavaScript array! * Make sure to set those in the ajax_ordernumber JavaScript array!
...@@ -39,6 +46,28 @@ class OrdernumberHelper { ...@@ -39,6 +46,28 @@ class OrdernumberHelper {
$this->registerCallback ("setupDateTimeReplacements", array($this, "setupDateTimeReplacements")); $this->registerCallback ("setupDateTimeReplacements", array($this, "setupDateTimeReplacements"));
} }
static function getHelper() {
static $helper = null;
if (!$helper) {
$helper = new OrdernumberHelper();
}
return $helper;
}
public function setFlag($flag, $value) {
$this->flags[$flag] = $value;
}
public function getFlag($flag, $default) {
if (isset($this->flags[$flag])) {
return $this->flags[$flag];
} else {
return $default;
}
}
public function unsetFlat($flag) {
unset($this->flags[$flag]);
}
function getStyle($key) { function getStyle($key) {
if (isset($this->_styles[$key])) { if (isset($this->_styles[$key])) {
return $this->_styles[$key]; return $this->_styles[$key];
...@@ -198,7 +227,7 @@ class OrdernumberHelper { ...@@ -198,7 +227,7 @@ class OrdernumberHelper {
return self::randomString ($alphabet, $len); return self::randomString ($alphabet, $len);
} }
protected function setupDateTimeReplacements (&$reps, $details, $nrtype) { public function setupDateTimeReplacements (&$reps, $details, $nrtype) {
$utime = microtime(true); $utime = microtime(true);
$reps["[year]"] = date ("Y", $utime); $reps["[year]"] = date ("Y", $utime);
$reps["[year2]"] = date ("y", $utime); $reps["[year2]"] = date ("y", $utime);
...@@ -302,7 +331,7 @@ class OrdernumberHelper { ...@@ -302,7 +331,7 @@ class OrdernumberHelper {
} }
protected function doReplacements ($fmt, $reps) { protected function doReplacements ($fmt, $reps) {
// First, replace all random...[n] fields. This needs to be done with a regexp and a callback: // First, replace all random...[n] fields. This needs to be done with a regexp and a callback:
$fmt = preg_replace_callback ('/\[(random)(.*?)([0-9]*?)\]/', array($this, 'replaceRandom'), $fmt); $fmt = preg_replace_callback ('/\[(random)(.*?)([0-9]*?)\]/i', array($this, 'replaceRandom'), $fmt);
// Only use string-valued variables for replacement (array-valued variables can be used in custom variable definitions!) // Only use string-valued variables for replacement (array-valued variables can be used in custom variable definitions!)
$reps = array_filter($reps, array($this, "no_array") ); $reps = array_filter($reps, array($this, "no_array") );
return str_ireplace (array_keys($reps), array_values($reps), $fmt); return str_ireplace (array_keys($reps), array_values($reps), $fmt);
...@@ -313,42 +342,45 @@ class OrdernumberHelper { ...@@ -313,42 +342,45 @@ class OrdernumberHelper {
if ($ctrsettings["${type}_global"] == 'yes') { if ($ctrsettings["${type}_global"] == 'yes') {
$ctrsettings["${type}_global"] = 1; $ctrsettings["${type}_global"] = 1;
} }
// First, extract all counter settings, i.e. all strings of the form [#####:startval/increment] or [####/increment:startval]
$regexp = '%\[(#+)(/([0-9]+))?(:([0-9]+))?(/([0-9]+))?\]%';
if (preg_match($regexp, $fmt, $counters)) { $parts=array($fmt);
// $counters is an array of the form: if ($this->getFlag('extract-counter-settings', true)) {
// Array ( // First, extract all counter settings, i.e. all strings of the form [#####:startval/increment] or [####/increment:startval]
// [0] => [#####:100/3] $regexp = '%\[(#+)(/([0-9]+))?(:([0-9]+))?(/([0-9]+))?\]%';
// [1] => #####
// [2] => if (preg_match($regexp, $fmt, $counters)) {
// [3] => // $counters is an array of the form:
// [4] => :100 // Array (
// [5] => 100 // [0] => [#####:100/3]
// [6] => /3 // [1] => #####
// [7] => 3 // [2] =>
// ) // [3] =>
$ctrsettings["${type}_padding"] = strlen($counters[1]); // [4] => :100
if (!empty($counters[2])) { // [5] => 100
// $counters[2] contains the whole "/n" part, while $counters[3] contains just the step itself // [6] => /3
$ctrsettings["${type}_step"] = $counters[3]; // [7] => 3
} // )
if (!empty($counters[4])) { $ctrsettings["${type}_padding"] = strlen($counters[1]);
// $counters[4] contains the whole ":n" part, while $counters[5] contains just the start value itself if (!empty($counters[2])) {
$ctrsettings["${type}_start"] = $counters[5]; // $counters[2] contains the whole "/n" part, while $counters[3] contains just the step itself
} $ctrsettings["${type}_step"] = $counters[3];
}
if (!empty($counters[4])) {
// $counters[4] contains the whole ":n" part, while $counters[5] contains just the start value itself
$ctrsettings["${type}_start"] = $counters[5];
}
if (!empty($counters[6])) { if (!empty($counters[6])) {
// $counters[6] contains the whole ":n" part, while $counters[7] contains just the start value itself // $counters[6] contains the whole ":n" part, while $counters[7] contains just the start value itself
$ctrsettings["${type}_step"] = $counters[7]; $ctrsettings["${type}_step"] = $counters[7];
} }
$fmt = preg_replace($regexp, "#", $fmt); $fmt = preg_replace($regexp, "#", $fmt);
}
// Split at a | to get the number format and a possibly different counter increment format
// If a separate counter format is given after the |, use it, otherwise reuse the number format itself as counter format
$parts = explode ("|", $fmt);
} }
// Split at a | to get the number format and a possibly different counter increment format
// If a separate counter format is given after the |, use it, otherwise reuse the number format itself as counter format
$parts = explode ("|", $fmt);
$ctrsettings["${type}_format"] = $parts[0]; $ctrsettings["${type}_format"] = $parts[0];
$ctrsettings["${type}_counter"] = ($ctrsettings["${type}_global"]==1)?"":$parts[(count($parts)>1)?1:0]; $ctrsettings["${type}_counter"] = ($ctrsettings["${type}_global"]==1)?"":$parts[(count($parts)>1)?1:0];
...@@ -370,7 +402,7 @@ class OrdernumberHelper { ...@@ -370,7 +402,7 @@ class OrdernumberHelper {
$format = $this->setupNumberFormatString($fmt, $type, $order, $reps); $format = $this->setupNumberFormatString($fmt, $type, $order, $reps);
$format = $this->doReplacements($format, $reps); $format = $this->doReplacements($format, $reps);
$ctrsettings = $this->extractCounterSettings ($format, $type, $ctrsettings); $ctrsettings = $this->extractCounterSettings ($format, $type, $ctrsettings);
// JFactory::getApplication()->enqueueMessage("<pre>Counter Settings: ".print_r($ctrsettings,1)."</pre>", 'error');
// JFactory::getApplication()->enqueueMessage("<pre>Replacements for $type:".print_r($reps,1)."</pre>", 'error'); // JFactory::getApplication()->enqueueMessage("<pre>Replacements for $type:".print_r($reps,1)."</pre>", 'error');
// Increment the counter only if the format contains a placeholder for it! // Increment the counter only if the format contains a placeholder for it!
if (strpos($ctrsettings["${type}_format"], "#") !== false) { if (strpos($ctrsettings["${type}_format"], "#") !== false) {
...@@ -396,7 +428,7 @@ class OrdernumberHelper { ...@@ -396,7 +428,7 @@ class OrdernumberHelper {
public function counter_modification_create_table($type, $counters) { public function counter_modification_create_table($type, $counters) {
$html=array(); $html=array();
$html[] = "<img src='" . $this->urlPath ('images', 'loading.gif') . "' class='ordernumber-loading' />"; $html[] = "<img src='" . $this->urlPath ('images', 'loading.gif') . "' class='ordernumber-loading' />";
$html[] = "<table class=\"ordernumber-countertable " . $this->getStyle('counter-table-class') . "\" " . $this->getStyle('counter-table-style') . ">"; $html[] = "<table class=\"ordernumber-countertable " . $this->getStyle('counter-table-class') . "\" " . $this->getStyle('counter-table-style') . " id='ordernumber-countertable-" . $type . "'>";
$html[] = "<thead>"; $html[] = "<thead>";
$html[] = " <tr>"; $html[] = " <tr>";
$html[] = " <th class='counter_format'>" . $this->__ ('PLG_ORDERNUMBER_COUNTERLIST_HEADER_COUNTER')."</th>"; $html[] = " <th class='counter_format'>" . $this->__ ('PLG_ORDERNUMBER_COUNTERLIST_HEADER_COUNTER')."</th>";
...@@ -407,7 +439,8 @@ class OrdernumberHelper { ...@@ -407,7 +439,8 @@ class OrdernumberHelper {
$html[] = " <colgroup><col class='counter_type'><col style=\"text-align: center\" ><col ></colgroup>"; $html[] = " <colgroup><col class='counter_type'><col style=\"text-align: center\" ><col ></colgroup>";
$html[] = "<tbody>"; $html[] = "<tbody>";
foreach ($counters as $c) { foreach ($counters as $c) {
$html[] = $this->counter_modification_create_row ($type, $c->name, $c->value); $cc = (object)$c;
$html[] = $this->counter_modification_create_row ($type, $cc->name, $cc->value);
} }
$html[] = "</tbody>"; $html[] = "</tbody>";
$html[] = "<tfoot>"; $html[] = "<tfoot>";
...@@ -428,7 +461,7 @@ class OrdernumberHelper { ...@@ -428,7 +461,7 @@ class OrdernumberHelper {
public function counter_modification_create_row ($type, $counter, $value) { public function counter_modification_create_row ($type, $counter, $value) {
$html=array(); $html=array();
$html[] = " <tr class='counter_row counter_row_$type'>"; $html[] = " <tr class='counter_row counter_row_$type'>";
$html[] = " <td class='counter_format'>" . (($counter=="")?($this->__ ('PLG_ORDERNUMBER_COUNTERLIST_GLOBAL')):htmlentities($counter)) . "</td>"; $html[] = " <td class='counter_format'>" . htmlentities((string)(($counter=="")?($this->__ ('PLG_ORDERNUMBER_COUNTERLIST_GLOBAL')):$counter)) . "</td>";
$html[] = " <td class='counter_value'>" . htmlentities((string)$value) . "</td>"; $html[] = " <td class='counter_value'>" . htmlentities((string)$value) . "</td>";
$html[] = " <td class='counter_buttons'>"; $html[] = " <td class='counter_buttons'>";
$html[] = " <div class='ordernumber-ajax-loading'>"; $html[] = " <div class='ordernumber-ajax-loading'>";
...@@ -468,7 +501,7 @@ class OrdernumberHelper { ...@@ -468,7 +501,7 @@ class OrdernumberHelper {
'variables_ifvar' => $this->__('PLG_ORDERNUMBER_REPL_IFVAR'), 'variables_ifvar' => $this->__('PLG_ORDERNUMBER_REPL_IFVAR'),
'variables_ifop' => '', 'variables_ifop' => '',
'variables_ifval' => $this->__('PLG_ORDERNUMBER_REPL_IFVAL'), 'variables_ifval' => $this->__('PLG_ORDERNUMBER_REPL_IFVAL'),
'variables_then' => $this->__(''), 'variables_then' => '',
'variables_thenvar' => $this->__('PLG_ORDERNUMBER_REPL_SETVAR'), 'variables_thenvar' => $this->__('PLG_ORDERNUMBER_REPL_SETVAR'),
'variables_thenval' => $this->__('PLG_ORDERNUMBER_REPL_TOVAL'), 'variables_thenval' => $this->__('PLG_ORDERNUMBER_REPL_TOVAL'),
'sort' => '', 'sort' => '',
...@@ -567,4 +600,30 @@ var ajax_ordernumber = ' . json_encode($json) . '; ...@@ -567,4 +600,30 @@ var ajax_ordernumber = ' . json_encode($json) . ';
return $js; return $js;
} }
public function ajax_counter_delete($type, $counter) {
// TODO: Check if counter value has changed meanwhile
$json = array('action' => 'delete_counter', 'success' => 0);
$json['success'] = $this->deleteCounter($type, $counter);
return $json;
}
public function ajax_counter_add ($type, $counter, $value) {
// TODO: Check if counter value has changed meanwhile
$json = array('action' => 'add_counter', 'success' => 0);
if ($this->getCounter($type, $counter, -1) != -1) {
// Counter already exists => error message
$json['error'] = sprintf($this->__('Counter "%s" already exists, cannot create again.'), $counter);
} else {
$json['success'] = $this->setCounter($type, $counter, $value);
$json['row'] = $this->counter_modification_create_row($type, $counter, $value);
}
return $json;
}
public function ajax_counter_set ($type, $counter, $value) {
$json = array('action' => 'set_counter', 'success' => 0);
$json['success'] = $this->setCounter($type, $counter, $value);
$json['row'] = $this->counter_modification_create_row($type, $counter, $value);
return $json;
}
} }
...@@ -356,13 +356,13 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { ...@@ -356,13 +356,13 @@ class plgVmShopperOrdernumber extends vmShopperPlugin {
} }
$xml = simplexml_load_file($xmlfile); $xml = simplexml_load_file($xmlfile);
if (!$xml || !isset($xml->updateservers)) { if (!$xml || !isset($xml->updateservers)) {
JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $this->_xmlFile), 'error'); JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $xmlfile), 'error');
return $json; return $json;
} }
$updateservers = $xml->updateservers; $updateservers = $xml->updateservers;
foreach ($updateservers->children() as $server) { foreach ($updateservers->children() as $server) {
if ($server->getName()!='server') { if ($server->getName()!='server') {
JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $this->_xmlFile), 'error'); JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $xmlfile), 'error');
continue; continue;
} }
$updateurl = html_entity_decode((string)$server); $updateurl = html_entity_decode((string)$server);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<authorUrl>http://www.open-tools.net/</authorUrl> <authorUrl>http://www.open-tools.net/</authorUrl>
<copyright>Copyright (C) 2012-2015 Reinhold Kainhofer. All rights reserved.</copyright> <copyright>Copyright (C) 2012-2015 Reinhold Kainhofer. All rights reserved.</copyright>
<license>http://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3</license> <license>http://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3</license>
<version>3.99a</version> <version>4.0</version>
<releaseDate>2015-05-23</releaseDate> <releaseDate>2015-05-23</releaseDate>
<releaseType>Minor update</releaseType> <releaseType>Minor update</releaseType>
<downloadUrl>http://open-tools.net/virtuemart/advanced-ordernumbers.html</downloadUrl> <downloadUrl>http://open-tools.net/virtuemart/advanced-ordernumbers.html</downloadUrl>
......
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment