diff --git a/app/code/community/OpenTools/Ordernumber/Helper/Data.php b/app/code/community/OpenTools/Ordernumber/Helper/Data.php
index 026b99720f95572099ae5610facfc053fddc5096..b059a830f7505050c84b8c7845bdaa5e10235842 100644
--- a/app/code/community/OpenTools/Ordernumber/Helper/Data.php
+++ b/app/code/community/OpenTools/Ordernumber/Helper/Data.php
@@ -1,8 +1,13 @@
 <?php
+require_once(Mage::getBaseDir('lib') . '/OpenTools/Ordernumber/ordernumber_helper_magento.php');
+
 class OpenTools_Ordernumber_Helper_Data extends Mage_Core_Helper_Abstract
 {
-    public function logitem($label, $item)
+	protected $helper = null;
+	
+    function __construct($label, $item)
     {
+        $this->helper = OrdernumberHelperMagento::getHelper();
         Mage::Log($label . " " . get_class($item) . "\n", null, 'ordernumber.log');
         Mage::Log(is_array($item)?$item:$item->debug(), null, 'ordernumber.log');
         Mage::Log(get_class_methods(get_class($item)), null, 'ordernumber.log');
diff --git a/app/code/community/OpenTools/Ordernumber/Model/Observer.php b/app/code/community/OpenTools/Ordernumber/Model/Observer.php
index 295d6371fe2315383d6e0884e8b645ff26acc151..165cb3f83d85e812572fbae3d8c2f4bf2dceaf42 100644
--- a/app/code/community/OpenTools/Ordernumber/Model/Observer.php
+++ b/app/code/community/OpenTools/Ordernumber/Model/Observer.php
@@ -20,9 +20,11 @@
  * @copyright  Copyright (c) 2010 Fooman Limited (http://www.fooman.co.nz)
  * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
  */
+
 class OpenTools_Ordernumber_Model_Observer extends Mage_Core_Model_Abstract
 {
     protected $_dbModel = null;
+
     protected function _getModel()
     {
         return Mage::getModel('opentools_ordernumber/ordernumber');
diff --git a/app/code/community/OpenTools/Ordernumber/etc/config.xml b/app/code/community/OpenTools/Ordernumber/etc/config.xml
index ec634d353d647e438bddeba9cfb27545bb67473a..4513c1dae8ace0e3c9d30c9deabcaf423abcf6d6 100644
--- a/app/code/community/OpenTools/Ordernumber/etc/config.xml
+++ b/app/code/community/OpenTools/Ordernumber/etc/config.xml
@@ -4,7 +4,7 @@
 <!-- declare module's version information -->
         <OpenTools_Ordernumber>
 <!-- this version number will be used for database upgrades -->
-            <version>0.1.0</version>
+            <version>1.0.0</version>
             <depends>
                 <Mage_Sales />
             </depends>
diff --git a/app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/install-0.1.0.php b/app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/install-1.0.0.php
similarity index 100%
rename from app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/install-0.1.0.php
rename to app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/install-1.0.0.php
diff --git a/app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/upgrade-1.0.0-1.1.0.php b/app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/upgrade-1.0.0-1.1.0.php
new file mode 100644
index 0000000000000000000000000000000000000000..d00a87b1c0cb3dbaf7d00920ab77c79a45257bb7
--- /dev/null
+++ b/app/code/community/OpenTools/Ordernumber/sql/opentools_ordernumber_setup/upgrade-1.0.0-1.1.0.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Ordernmber upgrade script
+ * @author OpenTools
+ */
+/**
+ * @var $installer Mage_Core_Model_Resource_Setup
+ */
+$installer = $this;
+
+/**
+ * Create table opentools_ordernumber
+ */
+$connection = $installer->getConnection();
+$tablename = $installer->getTable('opentools_ordernumber/ordernumber');
+
+if (!$connection->isTableExists($tablename)) {
+
+	$table = $connection
+		->newTable($tablename)
+		->addColumn('ordernumber_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
+			'unsigned' => true,
+			'identity' => true,
+			'nullable' => false,
+			'primary'  => true,
+		), 'Ordernumber id')
+		->addColumn('number_type',   Varien_Db_Ddl_Table::TYPE_TEXT,      63, array('nullable'=> false),                  'Number Type')
+		->addColumn('number_scope',  Varien_Db_Ddl_Table::TYPE_TEXT,      20, array('nullable'=> true, 'default'=>''),    'Number Scope')
+		->addColumn('number_format', Varien_Db_Ddl_Table::TYPE_TEXT,     255, array('nullable'=> true, 'default'=>''),    'Number Format')
+		->addColumn('count',         Varien_Db_Ddl_Table::TYPE_INTEGER, null, array('unsigned'=>true, 'nullable'=>false), 'Counter')
+		->addIndex($installer->getIdxName(
+				$tablename,
+				array('number_type', 'number_scope', 'number_format'),
+				Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
+			),
+			array('number_type', 'number_scope', 'number_format'),
+			array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)
+		)
+		->setComment('Ordernumber Counter Table');
+	// TODO: drop table if exists!
+	$installer->getConnection()->createTable($table);
+} else {
+	// Table already exists, don't do anything (but also don't try to create the table again)
+}
+die()
\ No newline at end of file
diff --git a/lib/OpenTools/Ordernumber/library/css/index.html b/lib/OpenTools/Ordernumber/library/css/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/css/index.html
@@ -0,0 +1 @@
+<!DOCTYPE html><title></title>
diff --git a/lib/OpenTools/Ordernumber/library/css/ordernumber.css b/lib/OpenTools/Ordernumber/library/css/ordernumber.css
new file mode 100644
index 0000000000000000000000000000000000000000..f6e0862417aedcc44203d0ba0491be01031211de
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/css/ordernumber.css
@@ -0,0 +1,110 @@
+
+table.ordernumber-countertable {
+    border: 1px solid #888888;
+    display: inline-table;
+}
+
+table.ordernumber-countertable.table-striped tbody > tr:nth-child(odd) > th {
+    background: #E0E0E0;
+}
+.ordernumber-btn {
+    cursor: pointer;
+}
+
+col.counter_type, th.counter_type, td.counter_type {
+    display:none;
+}
+td.counter_value {
+    text-align: center;
+}
+
+fieldset table.ordernumber-countertable img {
+    padding: 0;
+    margin: 0;    
+}
+div.ordernumber-ajax-loading, div.ordernumber-counter-addbtn {
+    display: inline;
+}
+div.ordernumber-ajax-loading, div.ordernumber-ajax-loading img.ordernumber-btn {
+    position: relative;
+    top: 0; left: 0;
+}
+div.ordernumber-ajax-loading img {
+    z-index:0;
+}
+
+img.ordernumber-loading {
+	display: none; 
+	position: absolute; 
+	top: 2px; 
+	left: 0px; 
+	z-index: 9999;
+}
+
+
+
+/*  Counter custom variable replacements */
+table.ordernumber_variables {
+    border: 1px solid #888888;
+	width: inherit;
+}
+
+table.ordernumber_variables td, table.ordernumber_variables th {
+    padding: 0px;
+	vertical-align: middle;
+}
+/* table.ordernumber_variables td.sort:before { */
+/*     float: none; */
+/* 	display: inline-block; */
+/* } */
+
+table.ordernumber_variables thead th {
+    text-align: center;
+	width: auto;
+}
+td.counter_value {
+    text-align: center;
+}
+table.ordernumber_variables input {
+	background-color: rgba(255,255,255,0.75);
+}
+
+table.ordernumber_variables thead > tr:nth-child(odd) > th,
+table.ordernumber_variables tfoot > tr.addreplacement_row > td {
+    background: #E0E0E0;
+}
+table.ordernumber_variables tbody > tr:nth-child(even) > td {
+    background: #F0F0F0;
+}
+table.ordernumber_variables tbody tr td input {
+	width: 100%;
+}
+.ordernumber-btn {
+    cursor: pointer;
+}
+
+table.ordernumber_variables img {
+    padding: 0;
+    margin: 0;    
+}
+tr.rowhidden {
+	display: none;
+}
+
+/* Adjust the columns of the replacements table */
+col.variables_ifvar, col.variables_ifval {
+	width: 15%;
+}
+col.variables_ifop {
+	width: 10%;
+}
+col.variables_thenvar, col.variables_thenval {
+	width: 25%;
+}
+.variables_then, .variables_settings {
+	text-align: center;
+	width: 20px;
+}
+
+
+
diff --git a/lib/OpenTools/Ordernumber/library/images/icon-16-delete.png b/lib/OpenTools/Ordernumber/library/images/icon-16-delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..1573413858f535fc32cb0428d3275f639a5f74cc
Binary files /dev/null and b/lib/OpenTools/Ordernumber/library/images/icon-16-delete.png differ
diff --git a/lib/OpenTools/Ordernumber/library/images/icon-16-edit.png b/lib/OpenTools/Ordernumber/library/images/icon-16-edit.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1bcf04bb12fe3186243c8becb73bb0b24795117
Binary files /dev/null and b/lib/OpenTools/Ordernumber/library/images/icon-16-edit.png differ
diff --git a/lib/OpenTools/Ordernumber/library/images/icon-16-new.png b/lib/OpenTools/Ordernumber/library/images/icon-16-new.png
new file mode 100644
index 0000000000000000000000000000000000000000..fcd6a5a695d6efadc571362896612e17440e9cc1
Binary files /dev/null and b/lib/OpenTools/Ordernumber/library/images/icon-16-new.png differ
diff --git a/lib/OpenTools/Ordernumber/library/images/index.html b/lib/OpenTools/Ordernumber/library/images/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/images/index.html
@@ -0,0 +1 @@
+<!DOCTYPE html><title></title>
diff --git a/lib/OpenTools/Ordernumber/library/images/loading.gif b/lib/OpenTools/Ordernumber/library/images/loading.gif
new file mode 100644
index 0000000000000000000000000000000000000000..94da6d80e05ef96d9d7551013d59c22551c0ae1a
Binary files /dev/null and b/lib/OpenTools/Ordernumber/library/images/loading.gif differ
diff --git a/lib/OpenTools/Ordernumber/library/images/loading.png b/lib/OpenTools/Ordernumber/library/images/loading.png
new file mode 100644
index 0000000000000000000000000000000000000000..ac0aa90cbc6fde315828934a2494ec1324b8af56
Binary files /dev/null and b/lib/OpenTools/Ordernumber/library/images/loading.png differ
diff --git a/lib/OpenTools/Ordernumber/library/index.html b/lib/OpenTools/Ordernumber/library/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/index.html
@@ -0,0 +1 @@
+<!DOCTYPE html><title></title>
diff --git a/lib/OpenTools/Ordernumber/library/js/index.html b/lib/OpenTools/Ordernumber/library/js/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/js/index.html
@@ -0,0 +1 @@
+<!DOCTYPE html><title></title>
diff --git a/lib/OpenTools/Ordernumber/library/js/ordernumber.js b/lib/OpenTools/Ordernumber/library/js/ordernumber.js
new file mode 100644
index 0000000000000000000000000000000000000000..70c92e4bc4590a51b9a5ccb8d474a789ecb055a9
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/js/ordernumber.js
@@ -0,0 +1,225 @@
+/**********************************************************************************
+ * The global ajax_ordernumber object should have the following entries:
+ *   - Translations: 
+ *     ORDERNUMBER_JS_NOT_AUTHORIZED, ORDERNUMBER_JS_INVALID_COUNTERVALUE, ORDERNUMBER_JS_JSONERROR
+ *     ORDERNUMBER_JS_NEWCOUNTER, ORDERNUMBER_JS_EDITCOUNTER, ORDERNUMBER_JS_DELETECOUNTER
+ *     ORDERNUMBER_JS_ADD_FAILED, ORDERNUMBER_JS_MODIFY_FAILED, ORDERNUMBER_JS_DELETE_FAILED
+ *   - ajax_url: The URL for all AJAX calls
+
+ * Optional entries (callback functions) are:
+ *  - updateMessages(messages, cssidentifier)
+ *  - parseAjaxResponse(response) => return json
+ *  - modifyAjaxArgs(ajaxargs)    => return ajaxargs with modified arguments for jquery.ajax calls
+ */
+ 
+/**********************************************************************************
+ * 
+ *  Javascript for the counter modification table
+ * 
+ **********************************************************************************/
+String.Format = function() {
+  var s = arguments[0];
+  for (var i = 0; i < arguments.length - 1; i++) {       
+    var reg = new RegExp("\\{" + i + "\\}", "gm");             
+    s = s.replace(reg, arguments[i + 1]);
+  }
+  return s;
+}
+
+var getCounterData = function (btn) {
+    var row = jQuery(btn).closest("tr.counter_row");
+    return { row: row };
+}
+var handleJSONResponse = function (json, counter) {
+	if ('updateMessages' in ajax_ordernumber) { 
+		ajax_ordernumber.updateMessages(json['messages'], "ordernumber");
+	}
+	if (!json.authorized) {
+		alert(ajax_ordernumber.ORDERNUMBER_JS_NOT_AUTHORIZED);
+	} else if (json.error) {
+		alert(json.error);
+	} else {
+		// TODO: Which other error checks can we do?
+	}
+}
+var ajaxEditCounter = function (btn, nrtype, ctr, value) {
+    var counter = getCounterData(btn);
+    counter.type=nrtype;
+    counter.counter=ctr;
+    counter.value=value;
+    var value = NaN;
+    var msgprefix = "";
+    while (isNaN(value) && (value != null)) {
+        value = prompt (String.Format(ajax_ordernumber.ORDERNUMBER_JS_EDITCOUNTER, msgprefix, counter.counter, counter.value), counter.value);
+        if (value != null)
+            value = parseInt(value);
+        if (isNaN(value)) 
+            msgprefix = ajax_ordernumber.ORDERNUMBER_JS_INVALID_COUNTERVALUE;
+    }
+    if (value != null) {
+        var loading = jQuery("img.ordernumber-loading").first().clone().insertAfter(btn).show();
+        var ajaxargs = {
+            type: "POST",
+            url: ajax_ordernumber.ajax_url,
+            data: { 
+				action: 'setCounter',
+				nrtype: counter.type, 
+				counter: counter.counter, 
+				value: value 
+			},
+			success: function ( json ) {
+                try {
+					if ('parseAjaxResponse' in ajax_ordernumber) { 
+						json = ajax_ordernumber.parseAjaxResponse(json);
+					}
+                    handleJSONResponse(json, counter);
+                } catch (e) {
+                    alert(ajax_ordernumber.ORDERNUMBER_JS_JSONERROR+"\n"+e);
+                    return;
+                }
+                if (json.success>0) {
+					// replace the whole row with the html returned by the AJAX call:
+					jQuery(counter.row).replaceWith(json.row);
+//                     jQuery(counter.row).find(".counter_value").text(value);
+                } else {
+                    alert (String.Format(ajax_ordernumber.ORDERNUMBER_JS_MODIFY_FAILED, counter.counter));
+                }
+            },
+            error: function() { alert (String.Format(ajax_ordernumber.ORDERNUMBER_JS_MODIFY_FAILED, counter.counter)); },
+            complete: function() { jQuery(loading).remove(); },
+        };
+		if ('modifyAjaxArgs' in ajax_ordernumber) { 
+			ajaxargs = ajax_ordernumber.modifyAjaxArgs(ajaxargs);
+		}
+		jQuery.ajax(ajaxargs);
+    }
+}
+var ajaxDeleteCounter = function (btn, nrtype, ctr, value) {
+    var counter = getCounterData(btn);
+    counter.type=nrtype;
+    counter.counter=ctr;
+    counter.value=value;
+    var proceed = confirm (String.Format(ajax_ordernumber.ORDERNUMBER_JS_DELETECOUNTER, counter.counter, counter.value));
+    if (proceed == true) {
+        var loading = jQuery("img.ordernumber-loading").first().clone().insertAfter(btn).show();
+        var ajaxargs = {
+            type: "POST",
+			dataType: "json",
+            url: ajax_ordernumber.ajax_url,
+            data: { 
+				action: 'deleteCounter',
+				nrtype: counter.type, 
+				counter: counter.counter 
+			},
+			success: function ( json ) {
+                try {
+					if ('parseAjaxResponse' in ajax_ordernumber) { 
+						json = ajax_ordernumber.parseAjaxResponse(json);
+					}
+                    handleJSONResponse(json, counter);
+                } catch (e) {
+                    alert(ajax_ordernumber.ORDERNUMBER_JS_JSONERROR+"\n"+e);
+                    return;
+                }
+                if (json.success>0) {
+                    jQuery(counter.row).fadeOut(1500, function() { jQuery(counter.row).remove(); });
+                } else {
+                    alert (String.Format(ajax_ordernumber.ORDERNUMBER_JS_DELETE_FAILED, counter.counter));
+                }
+            },
+            error: function() { alert (String.Format(ajax_ordernumber.ORDERNUMBER_JS_DELETE_FAILED, counter.counter)); },
+            complete: function() { jQuery(loading).remove(); },
+        };
+		if ('modifyAjaxArgs' in ajax_ordernumber) { 
+			ajaxargs = ajax_ordernumber.modifyAjaxArgs(ajaxargs);
+		}
+		jQuery.ajax(ajaxargs);
+    }
+}
+var ajaxAddCounter = function (btn, nrtype) {
+    var row = jQuery(btn).parents("tr.addcounter_row");
+    var countername = prompt (ajax_ordernumber.ORDERNUMBER_JS_NEWCOUNTER);
+    if (countername != null) {
+        var loading = jQuery("img.ordernumber-loading").first().clone().insertAfter(jQuery(btn).find("img.ordernumber-counter-addbtn")).show();
+        var ajaxargs = {
+            type: "POST",
+			dataType: "json",
+            url: ajax_ordernumber.ajax_url,
+            data: { 
+				action: "addCounter",
+				nrtype: nrtype, 
+				counter: countername 
+			},
+			
+			success: function ( json ) {
+                try {
+					if ('parseAjaxResponse' in ajax_ordernumber) { 
+						json = ajax_ordernumber.parseAjaxResponse(json);
+					}
+                    handleJSONResponse(json, null);
+                } catch (e) {
+                    alert(ajax_ordernumber.ORDERNUMBER_JS_JSONERROR+"\n"+e);
+                    return;
+                }
+                if (json.success>0) {
+                    if (json.row) {
+                        jQuery(row).before(jQuery(json.row));
+                    }
+                } else {
+                    alert (String.Format(ajax_ordernumber.ORDERNUMBER_JS_ADD_FAILED, countername));
+                }
+            },
+            error: function() { alert (String.Format(ajax_ordernumber.ORDERNUMBER_JS_ADD_FAILED, countername)); },
+            complete: function() { jQuery(loading).remove(); },
+        };
+		if ('modifyAjaxArgs' in ajax_ordernumber) { 
+			ajaxargs = ajax_ordernumber.modifyAjaxArgs(ajaxargs);
+		}
+		jQuery.ajax(ajaxargs);
+    }
+}
+
+
+
+
+/**********************************************************************************
+ * 
+ *  Javascript for the Custom Variables table
+ * 
+ **********************************************************************************/
+
+var ordernumberVariablesAddRow = function (template, element) {
+	var cl = jQuery("#" + template + " tr").clone(true);
+	// Enable all form controls
+	jQuery(cl).find('input,select,button,img').removeAttr('disabled');
+
+	if (jQuery.fn.chosen) {
+		// select boxes handled by the chosen juery plugin cannot simply be cloned,
+		// instead we need to re-initialize chosen!
+		jQuery(cl).find('select').removeClass("chzn-done").removeAttr("id").css("display", "block").next().remove();
+		jQuery(cl).find('select').chosen({width: "50px"});
+	}
+	// Now insert this new row into the table
+	jQuery(cl).appendTo("table#" + element + " tbody");
+	jQuery("tr#ordernumber-replacements-empty-row")
+		.addClass("rowhidden")
+		.find('input')
+		.attr('disabled', 'disabled');
+}
+
+jQuery(document).ready (function () {
+	jQuery('img.ordernumber-replacement-deletebtn').click(
+		function () {
+			jQuery(this).closest('tr').remove();
+			var count = jQuery(this).closest('table').find('tbody tr').length;
+			if (count==0) {
+				jQuery("tr#ordernumber-replacements-empty-row")
+					.removeClass("rowhidden")
+					.find('input,select,button,img')
+					.removeAttr('disabled');
+			}
+		}
+	);
+
+	jQuery("#ordernumber_variables tbody").sortable();
+});
diff --git a/lib/OpenTools/Ordernumber/library/ordernumber_helper.php b/lib/OpenTools/Ordernumber/library/ordernumber_helper.php
new file mode 100644
index 0000000000000000000000000000000000000000..2b504bc7188d0738e66ab80aa4d08f42bc1f68e1
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/library/ordernumber_helper.php
@@ -0,0 +1,567 @@
+<?php
+/**
+ * Advanced Ordernumbers generic helper class (e-commerce system agnostic)
+ * Reinhold Kainhofer, Open Tools, office@open-tools.net
+ * @copyright (C) 2012-2015 - Reinhold Kainhofer
+ * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
+**/
+
+if ( !defined( 'ABSPATH' ) && !defined('_JEXEC') ) { 
+	die( 'Direct Access to ' . basename( __FILE__ ) . ' is not allowed.' );
+}
+
+class OrdernumberHelper {
+	static $_version = "0.1";
+	protected $_callbacks = array();
+	public $_styles = array(
+		'counter-table-class' => "table-striped",
+		'counter-table-style' => "",
+		'variable-table-class' => "",
+		'variable-table-style' => "",
+	);
+	/**
+	 * 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!
+	 */
+	public $jstranslations = array(
+		"ORDERNUMBER_JS_NOT_AUTHORIZED", "ORDERNUMBER_JS_INVALID_COUNTERVALUE", "ORDERNUMBER_JS_JSONERROR", 
+		"ORDERNUMBER_JS_NEWCOUNTER", "ORDERNUMBER_JS_EDITCOUNTER", "ORDERNUMBER_JS_DELETECOUNTER",
+		"ORDERNUMBER_JS_ADD_FAILED", "ORDERNUMBER_JS_MODIFY_FAILED", "ORDERNUMBER_JS_DELETE_FAILED",
+		);
+		
+	/**
+	 * The URL to call for AJAX calls
+	 */
+	public $ajax_url = "";
+   
+	function __construct() {
+		// Set up 
+		$this->registerCallback ("setupDateTimeReplacements", array($this, "setupDateTimeReplacements"));
+	}
+	
+	function getStyle($key) {
+		if (isset($this->_styles[$key])) {
+			return $this->_styles[$key];
+		} else {
+			return '';
+		}
+	}
+	/* Callback handling */
+	
+	/**
+	 * Register a callback for one of the known callback hooks. 
+	 * Valid callbacks are (together with their arguments):
+	 *   - translate($string)
+	 *   - getCounter($type, $countername, $default)
+	 *   - setCounter($type, $countername, $value)
+	 *   - setupDateTimeReplacements(&$reps, $details, $nrtype);
+	 *   - setupStoreReplacements(&$reps, $details, $nrtype);
+	 *   - setupOrderReplacements(&$reps, $details, $nrtype);
+	 *   - setupUserReplacements(&$reps, $details, $nrtype);
+	 *   - setupShippingReplacements(&$reps, $details, $nrtype);
+	 *   - setupThirdPartyReplacements(&$reps, $details, $nrtype);
+	 
+	 *   - urlPath($path, $type)
+	 *  @param string $callback 
+	 *     The name of the callback hook (string)
+	 *  @param function $func 
+	 *     The function (usually a member of the plugin object) for the callback
+	 *  @return none
+	 */
+	public function registerCallback($callback, $func) {
+		$this->callbacks[$callback] = $func;
+	}
+	
+	public function __($string) {
+		if (isset($this->callbacks["translate"])) {
+			return $this->callbacks["translate"]($string);
+		} else {
+			return $string;
+		}
+	}
+
+	/**
+	 * Provide human-readable default values for the translatable strings.
+	 * Some systems use the translation key as fallback if no translation is found,
+	 * so we need to convert it to a human-readable value.
+	 */
+	public function readableString($string) {
+		static $readable_strings = array(
+			"PLG_ORDERNUMBER_COUNTERLIST_HEADER_VALUE"   => 'Counter value',
+			"PLG_ORDERNUMBER_COUNTERLIST_HEADER_COUNTER" => 'Counter name',
+			"PLG_ORDERNUMBER_COUNTERLIST_ADD"            => 'Add new counter',
+			"PLG_ORDERNUMBER_COUNTERLIST_GLOBAL"         => '[Global]',
+			"PLG_ORDERNUMBER_REPL_IFVAR"                 => 'If variable ...',
+			"PLG_ORDERNUMBER_REPL_IFVAL"                 => 'Value',
+			"PLG_ORDERNUMBER_REPL_SETVAR"                => 'Set variable ...',
+			"PLG_ORDERNUMBER_REPL_TOVAL"                 => 'to value ...',
+			"PLG_ORDERNUMBER_REPL_NOCUSTOMVARS"          => 'No custom variables have been defined.',
+			"PLG_ORDERNUMBER_REPL_ADDVAR"                => 'Add new custom variable',
+			"PLG_ORDERNUMBER_REPL_OP_NOCOND"             => 'No condition',
+			"PLG_ORDERNUMBER_REPL_OP_CONTAINS"           => 'contains',
+			"PLG_ORDERNUMBER_REPL_OP_STARTS"             => 'starts with',
+			"PLG_ORDERNUMBER_REPL_OP_ENDS"               => 'ends with'
+		);
+		// Use the human-readable text as default rather than the generic identifier.
+		// Otherwise, one always has to create a language file for every language, as 
+		// the fallback would be the identifier.
+		if (isset($readable_strings[$string]))
+			return $readable_strings[$string];
+		else
+			return $string;
+	}
+
+	public function urlPath($type, $file) {
+		if (isset($this->callbacks['urlPath'])) {
+			return $this->callbacks['urlPath']($type, $file);
+		} else {
+			throw new Exception('No callback defined for urlPath(type, file)!');
+		}
+	}
+	
+	protected function replacementsCallback ($func, &$reps, $details, $nrtype) {
+		if (isset($this->callbacks[$func])) {
+			return $this->callbacks[$func]($reps, $details, $nrtype);
+		}
+	}
+
+	protected function getCounter($type, $countername, $default) {
+		if (isset($this->callbacks['getCounter'])) {
+			return $this->callbacks['getCounter']($type, $countername, $default);
+		} else {
+			throw new Exception('No callback defined for getCounter(type, countername, default)!');
+		}
+	}
+	
+	protected function setCounter($type, $countername, $value) {
+		if (isset($this->callbacks['getCounter'])) {
+			return $this->callbacks['getCounter']($type, $countername, $value);
+		} else {
+			throw new Exception('No callback defined for setCounter(type, countername, value)!');
+		}
+	}
+	
+	public function getAllCounters($type) {
+		if (isset($this->callbacks['getCounter'])) {
+			return $this->callbacks['getCounter']($type);
+		} else {
+			throw new Exception ('No callback defined for getAllCounters(type)!');
+		}
+	}
+	
+	public static function transposeCustomVariables($cvar) {
+		if (is_object($cvar)) 
+			$cvar = (array)$cvar;
+		if (!is_array($cvar))
+			$cvar = array();
+		// The customvars are stored in transposed form (for technical reasons, since there is no trigger 
+		// called when the corresponding form field from the plugin param is saved)
+		$customvars = array();
+        
+		if (!empty($cvar)) {
+			$keys = array_keys($cvar);
+			foreach (array_keys($cvar[$keys[0]]) as $i) {
+				$entry = array();
+				foreach ($keys as $k) {
+					$entry[$k] = $cvar[$k][$i];
+				}
+				$customvars[] = $entry;
+			}
+		}
+		return $customvars;
+	}
+	
+	/* Return a random "string" of the given length taken from the given alphabet */
+	protected static function randomString($alphabet, $len) {
+		$alen = strlen($alphabet);
+		$r = "";
+		for ($n=0; $n<$len; $n++) {
+			$r .= $alphabet[mt_rand(0, $alen-1)];
+		}
+		return $r;
+	}
+
+	protected function replaceRandom ($match) {
+		/* the regexp matches (random)(Type)(Len) as match, Type and Len is optional */
+		$len = ($match[3]?$match[3]:1);
+		// Fallback: If no Type is given, use Digit
+		$alphabet = "0123456789";
+		// Select the correct alphabet depending on Type
+		switch (strtolower($match[2])) {
+			case "digit": $alphabet = "0123456789"; break;
+			case "hex": $alphabet = "0123456789abcdef"; break;
+			case "letter": $alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; break;
+			case "uletter": $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; break;
+			case "lletter": $alphabet = "abcdefghijklmnopqrstuvwxyz"; break;
+			case "alphanum": $alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; break;
+		}
+		return self::randomString ($alphabet, $len);
+	}
+
+	protected function setupDateTimeReplacements (&$reps, $details, $nrtype) {
+		$utime = microtime(true);
+		$reps["[year]"] = date ("Y", $utime);
+		$reps["[year2]"] = date ("y", $utime);
+		$reps["[month]"] = date("m", $utime);
+		$reps["[day]"] = date("d", $utime);
+		$reps["[hour]"] = date("H", $utime);
+		$reps["[hour12]"] = date("h", $utime);
+		$reps["[ampm]"] = date("a", $utime);
+		$reps["[minute]"] = date("i", $utime);
+		$reps["[second]"] = date("s", $utime);
+		$milliseconds = (int)(1000*($utime - (int)$utime));
+		$millisecondsstring = sprintf('%03d', $milliseconds);
+		$reps["[decisecond]"] = $millisecondsstring[0];
+		$reps["[centisecond]"] = substr($millisecondsstring, 0, 2);
+		$reps["[millisecond]"] = $millisecondsstring;
+	}
+
+	protected function setupReplacements($nrtype, $details) {
+		$reps = array();
+		// The following callbacks directly modify the replacements!
+		$this->replacementsCallback("setupDateTimeReplacements", $reps, $details, $nrtype);
+		$this->replacementsCallback("setupStoreReplacements", $reps, $details, $nrtype);
+		$this->replacementsCallback("setupOrderReplacements", $reps, $details, $nrtype);
+		$this->replacementsCallback("setupUserReplacements", $reps, $details, $nrtype);
+		$this->replacementsCallback("setupShippingReplacements", $reps, $details, $nrtype);
+		$this->replacementsCallback("setupThirdPartyReplacements", $reps, $details, $nrtype);
+		return $reps;
+	}
+
+	protected function setupCustomVariables ($nrtype, $order, $reps, $customvars) {
+		foreach ($customvars as $c) {
+			$conditionvar = strtolower($c['conditionvar']);
+			$op = $c['conditionop'];
+			
+			$found = false;
+			$match = false;
+			$compareval = null;
+			
+			if (isset($reps[$conditionvar])) {
+				$found = true;
+				$compareval = $reps[$conditionvar];
+			} elseif (isset($reps['['.$conditionvar.']'])) {
+				$found = true;
+				$compareval = $reps['['.$conditionvar.']'];
+			}/* elseif ($order && $compareval = $order->getData($conditionvar)) {
+				// TODO: Handle order property
+				$found = true;
+			}*/ else {
+				// TODO: Handly other possible properties!
+				// TODO: Print out warning that variable could not be found.
+			}
+			if ($found) {
+				$condval = $c['conditionval'];
+				switch ($op) {
+					case 'nocondition':
+							$match = true; break;
+					case 'equals': 
+							$match = ($compareval == $condval); break;
+					case 'contains':
+							if (is_array($compareval)) {
+								$match = in_array($condval, $compareval);
+							} else {
+								$match = strpos ($compareval, $condval);
+							}
+							break;
+					case 'smaller':
+							$match = ($compareval<$condval); break;
+					case 'smallerequal':
+							$match = ($compareval<=$condval); break;
+					case 'larger':
+							$match = ($compareval>$condval); break;
+					case 'largerequal':
+							$match = ($compareval>=$condval); break;
+					case 'startswith':
+							$match = (substr("$compareval", 0, strlen("$condval")) === "$condval"); break;
+					case 'endswith':
+							$match = (substr("$compareval", -strlen("$condval")) === "$condval"); break;
+				}
+			} elseif (empty($conditionvar)) {
+				$match = true;
+			}
+			if ($match) {
+				$varname = '['.strtolower($c['newvar']).']';
+				$reps[$varname] = $c['newval'];
+			}
+		}
+		return $reps;
+	}
+
+	// Allow the user to override the format like any other custom variable:
+	protected function setupNumberFormatString($fmt, $type, $order, $reps) {
+		if (isset($reps['['.$type.'_format]'])) {
+			return $reps['['.$type.'_format]'];
+		} else {
+			return $fmt;
+		}
+	}
+	
+	protected function doReplacements ($fmt, $reps) {
+		// 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);
+		// Only use string-valued variables for replacement (array-valued variables can be used in custom variable definitions!)
+		$reps = array_filter($reps, function($v) { return !is_array($v);} );
+		return str_ireplace (array_keys($reps), array_values($reps), $fmt);
+	}
+	
+	protected function extractCounterSettings ($fmt, $type, $ctrsettings) {
+		// Some e-Commerce systems use 'yes' for true, others use 1 => correct everything to 1
+		if ($ctrsettings["${type}_global"] == 'yes') {
+			$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)) {
+			// $counters is an array of the form:
+			// Array (
+			// 		[0] => [#####:100/3]
+			// 		[1] => #####
+			// 		[2] => 
+			// 		[3] => 
+			// 		[4] => :100
+			// 		[5] => 100
+			// 		[6] => /3
+			// 		[7] => 3
+			// )
+			$ctrsettings["${type}_padding"] = strlen($counters[1]);
+			if (!empty($counters[2])) {
+				// $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])) {
+				// $counters[6] contains the whole ":n" part, while $counters[7] contains just the start value itself
+				$ctrsettings["${type}_step"] = $counters[7]; 
+			}
+			
+			$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);
+		$ctrsettings["${type}_format"] = $parts[0];
+		$ctrsettings["${type}_counter"] = ($ctrsettings["${type}_global"]==1)?"":$parts[(count($parts)>1)?1:0];
+		
+		return $ctrsettings;
+	}
+
+	/**
+	 * Create a number of given type for the given format. Optionally, custom variable definitions and counter formatting can be passed.
+	 *   @param fmt The Format of the number (containing variables as [variable] and the counter as # or [####:initial/step])
+	 *   @param type The type of the number format, typically order_number, invoice_number, etc. (depending on the e-commerce suite)
+	 *   @param order The e-commerce-suite specific object describing the order. This will simply be passed on to the replacement hooks function for further data extraction during variable setup
+	 *   @param customvars Definitions (conditions and values) for custom variables. An array of arrays with keys conditionvar, conditionop, conditionval, newvar, newval
+	 *   @param ctrsettings Counter formatting defaults (will be overridden by an explicit counter formating variable of [####:initial/step] in the format). Array keys are: $type_format, $type_counter, $type_global, $type_padding, $type_step, $type_start
+	 *   @return A new number for the given format. The incremented counter has been properly stored.
+	 */
+	public function createNumber ($fmt, $type, $order, $customvars, $ctrsettings) {
+		$reps   = $this->setupReplacements ($type, $order);
+		$reps   = $this->setupCustomVariables ($type, $order, $reps, $customvars);
+		$format = $this->setupNumberFormatString($fmt, $type, $order, $reps);
+		$format = $this->doReplacements($format, $reps);
+		$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');
+		// Increment the counter only if the format contains a placeholder for it!
+		if (strpos($ctrsettings["${type}_format"], "#") !== false) {
+			$countername = $ctrsettings["${type}_counter"];
+			// Look up the current counter
+			$count = $this->getCounter($type, $countername, $ctrsettings["${type}_start"] - $ctrsettings["${type}_step"]) + $ctrsettings["${type}_step"];
+			$this->setCounter($type, $countername, $count);
+			// return the format with the counter inserted
+			$number = str_replace ("#", sprintf('%0' . $ctrsettings["${type}_padding"] . 's', $count), $ctrsettings["${type}_format"]);
+		} else {
+			$number = $ctrsettings["${type}_format"];
+		}
+		return $number;
+	}
+	
+	
+	/**
+	 * Create the counter modification HTML table
+	 *     @param $type string
+	 *          
+	 *     @param
+	 */
+	public function counter_modification_create_table($type, $counters) {
+        $html=array();
+        $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[] = "<thead>";
+        $html[] = "	<tr>";
+        $html[] = "		<th class='counter_format'>" . $this->__ ('PLG_ORDERNUMBER_COUNTERLIST_HEADER_COUNTER')."</th>";
+        $html[] = "		<th class='counter_value'>" . $this->__ ('PLG_ORDERNUMBER_COUNTERLIST_HEADER_VALUE'). "</th>";
+        $html[] = "		<th class='counter_buttons'></th>";
+        $html[] = "	</tr>";
+        $html[] = "</thead>";
+        $html[] = "	<colgroup><col class='counter_type'><col style=\"text-align: center\" ><col ></colgroup>";
+        $html[] = "<tbody>";
+        foreach ($counters as $c) {
+            $html[] = $this->counter_modification_create_row ($type, $c->name, $c->value);
+        }
+        $html[] = "</tbody>";
+        $html[] = "<tfoot>";
+        $html[] = "	<tr class='addcounter_row'>";
+        $html[] = "		<td colspan=3 class='counter_add'>";
+        $html[] = "			<div class='ordernumber-counter-addbtn ordernumber-btn' onClick='ajaxAddCounter(this, " . json_encode($type).")'>";
+        $html[] = "				<div class='ordernumber-ajax-loading'>";
+        $html[] = "					<img src='" . $this->urlPath('images', 'icon-16-new.png') . "' class='ordernumber-counter-addbtn' />";
+        $html[] = "				</div>" . $this->__('PLG_ORDERNUMBER_COUNTERLIST_ADD');
+        $html[] = "			</div>";
+        $html[] = "		</td>";
+        $html[] = "  </tr>";
+        $html[] = "</tfoot>";
+        $html[] = "</table>";
+        return implode("\n", $html);
+    }
+
+    public function counter_modification_create_row ($type, $counter, $value) {
+		$html=array();
+		$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_value'>" . htmlentities((string)$value) . "</td>";
+		$html[] = "		<td class='counter_buttons'>";
+		$html[] = "			<div class='ordernumber-ajax-loading'>";
+		$html[] = "				<img src='" . $this->urlPath('images', 'icon-16-edit.png') . "' class='ordernumber-counter-editbtn ordernumber-btn' ";
+		$html[] = "					onClick='ajaxEditCounter(this, " . json_encode($type) . ", ".json_encode($counter).", " . json_encode($value). ")' />";
+		$html[] = "			</div>";
+		$html[] = "			<div class='ordernumber-ajax-loading'>";
+		$html[] = "				<img src='" . $this->urlPath ('images', 'icon-16-delete.png') . "' class='ordernumber-counter-deletebtn ordernumber-btn' ";
+		$html[] = "					onClick='ajaxDeleteCounter(this, ".json_encode($type).", ".json_encode($counter).", " . json_encode($value) . ")' />";
+		$html[] = "			</div>";
+		$html[] = "		</td>";
+		$html[] = "	</tr>";
+		return implode("\n", $html);
+	}
+	
+	/**
+	 * Create the html table (with AJAX) to define and modify custom variable definitions.
+	 * The returned HTML code assumes that the caller properly adds the ordernumber.css 
+	 * and ordernumber.js to the page and loads the jQuery library.
+	 *    @param name string
+	 *        The HTML post/request variable name for the control.
+	 *    @param variables array
+	 *        The current list of custom variable replacements
+	 *
+	 *    @retval string
+	 *        The HTML code for the custom variable definition table.
+	 */
+    public function custom_variables_create_table($name, $variables) {
+        $html=array();
+//         $html[] = "<pre>Variables: ".print_r($variables,1)."</pre>";
+        $html[] = '<table id="ordernumber_variables_template" style="display:none">';
+        $html[] = $this->custom_variables_create_row($name, array(), 'disabled');
+        $html[] = '</table>';
+        
+        $html[] = '<table id="ordernumber_variables" class="ordernumber_variables ' . $this->getStyle('variable-table-class') . '" cellspacing="0" ' . $this->getStyle('variable-table-style') . '>';
+        $columns = array(
+            'variables_ifvar'    => $this->__('PLG_ORDERNUMBER_REPL_IFVAR'),
+            'variables_ifop'     => '',
+            'variables_ifval'    => $this->__('PLG_ORDERNUMBER_REPL_IFVAL'),
+            'variables_then'     => $this->__(''),
+            'variables_thenvar'  => $this->__('PLG_ORDERNUMBER_REPL_SETVAR'),
+            'variables_thenval'  => $this->__('PLG_ORDERNUMBER_REPL_TOVAL'),
+            'sort'     => '',
+            'variables_settings' => '',
+        );
+        $html[] = '	<thead>';
+        $html[] = '		<tr class="ordernumber_variables_header">';
+        foreach ( $columns as $key => $column ) {
+        	$html[] = '<th class="' . $key . '">' . htmlspecialchars( $column ) . '</th>';
+        }
+        $html[] = '		</tr>';
+        $html[] = '		<tr id="ordernumber-replacements-empty-row" class="oton-empty-row-notice ' . (empty($variables)?"":"rowhidden") . '">';
+        $html[] = '			<td class="oton-empty-row-notice" colspan="8">';
+        $html[] = '				<em>' . $this->__('PLG_ORDERNUMBER_REPL_NOCUSTOMVARS') . '</em>';
+        $html[] = '				<input type="hidden" name="' . $name . '" value="" ' . (empty($variables))?'':'disabled' . '>';
+        $html[] = '			</td>';
+        $html[] = '		</tr>';
+        $html[] = '	</thead>';
+        $html[] = '	<colgroup>';
+        foreach ($columns as $key => $column) {
+        	$html[] = '<col class="' . $key . '" />';
+        }
+        $html[] = '	</colgroup>';
+        $html[] = '';
+        $html[] = '	<tbody>';
+        foreach ($variables as $var) {
+        	$html[] = $this->custom_variables_create_row($name, $var);
+        }
+        $html[] = '	</tbody>';
+        $html[] = '	<tfoot>';
+        $html[] = '		<tr class="addreplacement_row">';
+        $html[] = '			<td colspan=8 class="variable_add">';
+        $html[] = '				<div class="ordernumber-variables-addbtn ordernumber-btn" onClick="ordernumberVariablesAddRow(\'ordernumber_variables_template\', \'ordernumber_variables\')">';
+        $html[] = '					<div class="ordernumber-ajax-loading"><img src="' . $this->urlPath('images', 'icon-16-new.png' ) . '" class="ordernumber-counter-addbtn" /></div>';
+        $html[] = $this->__('PLG_ORDERNUMBER_REPL_ADDVAR');
+        $html[] = '				</div>';
+        $html[] = '			</td>';
+        $html[] = '		</tr>';
+        $html[] = '	</tfoot>';
+        $html[] = '</table>';
+        return implode("\n", $html);
+    }
+    
+    public function custom_variables_create_row($name, $values = array(), $disabled = '') {
+        $operator = (isset($values['conditionop'])?$values['conditionop']:'');
+        $operators = array(
+            'nocondition'  => $this->__('PLG_ORDERNUMBER_REPL_OP_NOCOND'),
+            'equals'       => '=', 
+            'contains'     => $this->__('PLG_ORDERNUMBER_REPL_OP_CONTAINS'), 
+            'smaller'      => '<',
+            'smallerequal' => '<=',
+            'larger'       => '>',
+            'largerequal'  => '>=', 
+            'startswith'   => $this->__('PLG_ORDERNUMBER_REPL_OP_STARTS'),
+            'endswith'     => $this->__('PLG_ORDERNUMBER_REPL_OP_ENDS'),
+        );
+        $html  = '
+        <tr>
+        	<td class="variables_ifvar"><input name="' . $name . '[conditionvar][]" value="' . (isset($values['conditionvar'])?htmlentities($values['conditionvar']):'') . '" ' . htmlentities($disabled) . '/></td>
+        	<td class="variables_ifop"      ><select name="' . $name . '[conditionop][]" ' . htmlentities($disabled) . ' style="width: 100px">';
+        foreach ($operators as $op => $opname) {
+        	$html .= '		<option value="' . $op . '" ' . (($op === $operator)?'selected':'') . '>' . htmlspecialchars($opname) . '</option>';
+        }
+        $html .= '</select></td>
+        	<td class="variables_ifval"   ><input name="' . $name . '[conditionval][]" value="' . (isset($values['conditionval'])?$values['conditionval']:'') . '" ' . htmlentities($disabled) . '/></td>
+        	<td class="variables_then">=></td>
+        	<td class="variables_thenvar"><input name="' . $name . '[newvar][]"       value="' . (isset($values['newvar'])?$values['newvar']:'') .       '" ' . htmlentities($disabled) . '/></td>
+        	<td class="variables_thenval"><input name="' . $name . '[newval][]"       value="' . (isset($values['newval'])?$values['newval']:'') .       '" ' . htmlentities($disabled) . '/></td>
+        	<td class="sort"></td>
+        	<td class="variables_settings"><img src="' . $this->urlPath('images', 'icon-16-delete.png' ) . '" class="ordernumber-replacement-deletebtn ordernumber-btn"></td>
+        </tr>';
+        return $html;
+    }
+
+    /**
+     * Modify the json that contains JavaScript setup code to be used by ordernumber.js
+     */
+	function addCustomJS(&$json) {}
+	function appendJS() {}
+	function createJSSetup() {
+		static $done = 0; // <= prevent double js code
+		$json = array();
+		$json['ajax_url'] = $this->ajax_url;
+		
+		foreach ($this->jstranslations as $key) {
+			$json[$key] = $this->__($key);
+		}
+		$this->addCustomJS($json);
+		$js='/* <![CDATA[ */
+var ajax_ordernumber = ' . json_encode($json) . ';
+';
+		$js .= $this->appendJS();
+		$js .= '/* ]]> */
+';
+		$done = 1;
+		return $js;
+	}
+	
+}
diff --git a/lib/OpenTools/Ordernumber/ordernumber_helper_magento.php b/lib/OpenTools/Ordernumber/ordernumber_helper_magento.php
new file mode 100644
index 0000000000000000000000000000000000000000..6803e29910e3d751477fc16df8a05bad8ec5e651
--- /dev/null
+++ b/lib/OpenTools/Ordernumber/ordernumber_helper_magento.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Advanced Ordernumbers magento-specific helper class
+ * Reinhold Kainhofer, Open Tools, office@open-tools.net
+ * @copyright (C) 2012-2015 - Reinhold Kainhofer
+ * @license AFL
+**/
+
+if (!class_exists( 'OrdernumberHelper' )) 
+	require_once (dirname(__FILE__) . '/library/ordernumber_helper.php');
+
+class OrdernumberHelperMagento extends OrdernumberHelper {
+	protected $maghelper = null;
+
+	function __construct($helper) {
+		parent::__construct();
+		$this->maghelper = $helper;
+		// TODO: Load translations
+// 		load_plugin_textdomain('opentools-ordernumbers', false, basename( dirname( __FILE__ ) ) . '/languages' );
+		// Magento-specific Defaults for the HTML tables
+// 		$this->_styles['counter-table-class']  = "widefat";
+// 		$this->_styles['variable-table-class'] = "widefat wc_input_table sortable";
+	}
+
+	static function getHelper() {
+		static $helper = null;
+		if (!$helper) {
+			$helper = new OrdernumberHelperMagento();
+		}
+		return $helper;
+    }
+	
+	/**
+	 * HELPER FUNCTIONS, Magento-specific
+	 */
+	public function __($string) {
+		$string = $this->readableString($string);
+		return $this->maghelper->__($string);
+	}
+	function urlPath($type, $file) {
+		// TODO
+// 		return plugins_url('library/' . $type . '/' . $file, __FILE__);
+    }
+    
+    public function print_admin_styles() {
+// 		wp_register_style('ordernumber-styles',  $this->urlPath('css', 'ordernumber.css'));
+// 		wp_enqueue_style('ordernumber-styles');
+	}
+	
+	public function print_admin_scripts() {
+// 		wp_register_script( 'ordernumber-script', $this->urlPath('js', 'ordernumber.js',  __FILE__), array('jquery') );
+// 		wp_enqueue_script( 'ordernumber-script');
+		
+		// Handle the translations:
+// 		$localizations = array( 'ajax_url' => admin_url( 'admin-ajax.php' ) );
+		
+// 		$localizations['ORDERNUMBER_JS_JSONERROR'] = $this->__("Error reading response from server:");
+// 		$localizations['ORDERNUMBER_JS_NOT_AUTHORIZED'] = $this->__("You are not authorized to modify order number counters.");
+// 		$localizations['ORDERNUMBER_JS_NEWCOUNTER'] = $this->__("Please enter the format/name of the new counter:");
+// 		$localizations['ORDERNUMBER_JS_ADD_FAILED'] = $this->__("Failed adding counter {0}");
+// 		$localizations['ORDERNUMBER_JS_INVALID_COUNTERVALUE'] = $this->__("You entered an invalid value for the counter.\n\n");
+		
+// 		$localizations['ORDERNUMBER_JS_EDITCOUNTER'] = $this->__("{0}Please enter the new value for the counter '{1}' (current value: {2}):");
+// 		$localizations['ORDERNUMBER_JS_MODIFY_FAILED'] = $this->__("Failed modifying counter {0}");
+// 		$localizations['ORDERNUMBER_JS_DELETECOUNTER'] = $this->__("Really delete counter '{0}' with value '{1}'?");
+// 		$localizations['ORDERNUMBER_JS_DELETE_FAILED'] = $this->__("Failed deleting counter {0}");
+
+		// in JavaScript, object properties are accessed as ajax_object.ajax_url, ajax_object.we_value
+// 		wp_localize_script( 'ordernumber-script', 'ajax_ordernumber', $localizations );
+	}
+
+
+
+
+ 	function getAllCounters($type) {
+		$counters = array();
+// 		$pfxlen = strlen(self::$ordernumber_counter_prefix );
+// 		foreach (wp_load_alloptions() as $name => $value) {
+// 			if (substr($name, 0, $pfxlen) == self::$ordernumber_counter_prefix) {
+// 				$parts = explode('-', substr($name, $pfxlen), 2);
+// 				if (sizeof($parts)==1) {
+// 					array_unshift($parts, 'ordernumber');
+// 				}
+// 				if ($parts[0]==$type) {
+// 					$counters[] = array(
+// 						'type'  => $parts[0],
+// 						'name'  => $parts[1],
+// 						'value' => $value,
+// 					);
+// 				}
+// 			}
+// 		}
+		return $counters;
+	}
+
+    function getCounter($type, $format, $default=0) {
+		return get_option (self::$ordernumber_counter_prefix.$type.'-'.$format, $default);
+	}
+    
+	function addCounter($type, $format, $value) {
+		return $this->setCounter($type, $format, $value);
+	}
+
+	function setCounter($type, $format, $value) {
+		return update_option(self::$ordernumber_counter_prefix.$type.'-'.$format, $value);
+	}
+
+	function deleteCounter($type, $format) {
+		return delete_option(self::$ordernumber_counter_prefix.$type.'-'.$format);
+	}
+
+
+}