diff --git a/assets/css/ordernumber-counter.css b/assets/css/ordernumber-counter.css deleted file mode 100644 index 522485d9b4b370aecdfd8660a3657b7e76b8c669..0000000000000000000000000000000000000000 --- a/assets/css/ordernumber-counter.css +++ /dev/null @@ -1,47 +0,0 @@ - -table.wc_ordernumber_counters { - border: 1px solid #888888; - width: inherit; -} - -table.wc_ordernumber_counters td, table.wc_ordernumber_counters th { - padding-bottom: 0px; - padding-top: 0px; -} -table.wc_ordernumber_counters thead th { - text-align: center; - width: auto; -} -td.counter_value { - text-align: center; -} - -table.wc_ordernumber_counters thead > tr:nth-child(odd) > th, -table.wc_ordernumber_counters tfoot > tr.addcounter_row > td { - background: #E0E0E0; -} -table.wc_ordernumber_counters tbody > tr:nth-child(even) > td { - background: #F0F0F0; -} -.ordernumber-btn { - cursor: pointer; -} - -col.counter_type, th.counter_type, td.counter_type { - display:none; -} - -table.wc_ordernumber_counters 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; -} diff --git a/assets/css/ordernumber-variables.css b/assets/css/ordernumber-variables.css deleted file mode 100644 index df2fa261f18016ec9847277e8f841914cabf90a8..0000000000000000000000000000000000000000 --- a/assets/css/ordernumber-variables.css +++ /dev/null @@ -1,57 +0,0 @@ -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 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; -} diff --git a/assets/js/ordernumber-counter.js b/assets/js/ordernumber-counter.js deleted file mode 100644 index a098ba787e38d8cbd75d9601019f28a84385818b..0000000000000000000000000000000000000000 --- a/assets/js/ordernumber-counter.js +++ /dev/null @@ -1,138 +0,0 @@ - -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).parents ("tr.counter_row"); - return { row: row }; -} -var handleJSONResponse = function (json, counter) { -// updateMessages (json['messages'], "ordernumber"); - if (!json.authorized && !json.success) { - 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 (); - jQuery.ajax ({ - type: "POST", - url: ajax_ordernumber.ajax_url, - data: { - action: 'set_counter', - nrtype: counter.type, - counter: counter.counter, - value: value, - }, - success: function ( json ) { - try { - handleJSONResponse (json, counter); - } catch (e) { - alert (ajax_ordernumber.ORDERNUMBER_JS_JSONERROR + "\n" + e); - return; - } - if (json.success>0) { - jQuery (counter.row).children (".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 (); }, - }); - } -} -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 (); - jQuery.ajax ({ - type: "POST", - dataType: "json", - url: ajax_ordernumber.ajax_url, - data: { - action: 'delete_counter', - nrtype: counter.type, - counter: counter.counter, - }, - success: function ( json ) { - try { - handleJSONResponse (json, counter); - } catch (e) { - alert (ajax_ordernumber.ORDERNUMBER_JS_JSONERROR + "\n" + e); - return; - } - if (json.success>0) { - jQuery (counter.row).fadeOut (1200, 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 (); }, - }); - } -} -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 (); - jQuery.ajax ({ - type: "POST", - dataType: "json", - url: ajax_ordernumber.ajax_url, - data: { - action: 'add_counter', - nrtype: nrtype, - counter: countername, - value: 0, - }, - - success: function ( json ) { - try { - handleJSONResponse (json, null); - } catch (e) { - alert (ajax_ordernumber.ORDERNUMBER_JS_JSONERROR + "\n" + e); - return; - } - if (json.success>0) { - if (json.newrow) { - jQuery (row).before (jQuery (json.newrow)); - } - } 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 (); }, - }); - } -} diff --git a/assets/js/ordernumber-variables.js b/assets/js/ordernumber-variables.js deleted file mode 100644 index b9508ff3e8f7bcb5ac13ba6d02aeebaa9b1c8faf..0000000000000000000000000000000000000000 --- a/assets/js/ordernumber-variables.js +++ /dev/null @@ -1,25 +0,0 @@ -var ordernumberVariablesAddRow = function (template, element) { - jQuery("#" + template + " tr").clone(true) - .appendTo("table#" + element + " tbody") - .find('input,select,button,img') - .removeAttr('disabled'); - 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'); - } - } - ); -}); diff --git a/library/css/ordernumber.css b/library/css/ordernumber.css new file mode 100644 index 0000000000000000000000000000000000000000..17a098bbfa9e293d630ca4bdb3963cf984a86847 --- /dev/null +++ b/library/css/ordernumber.css @@ -0,0 +1,131 @@ + +table.ordernumber-countertable { + border: 1px solid #888888; + 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 { + 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/assets/images/icon-16-delete.png b/library/images/icon-16-delete.png similarity index 100% rename from assets/images/icon-16-delete.png rename to library/images/icon-16-delete.png diff --git a/assets/images/icon-16-edit.png b/library/images/icon-16-edit.png similarity index 100% rename from assets/images/icon-16-edit.png rename to library/images/icon-16-edit.png diff --git a/assets/images/icon-16-new.png b/library/images/icon-16-new.png similarity index 100% rename from assets/images/icon-16-new.png rename to library/images/icon-16-new.png diff --git a/assets/images/loading.gif b/library/images/loading.gif similarity index 100% rename from assets/images/loading.gif rename to library/images/loading.gif diff --git a/assets/images/loading.png b/library/images/loading.png similarity index 100% rename from assets/images/loading.png rename to library/images/loading.png diff --git a/library/js/ordernumber.js b/library/js/ordernumber.js new file mode 100644 index 0000000000000000000000000000000000000000..1e816b57a0816b04961c65398eed8801f5ececbf --- /dev/null +++ b/library/js/ordernumber.js @@ -0,0 +1,226 @@ +/********************************************************************************** + * 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 && !json.success) { + 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(); }, + }; +// console.log("Ajaxargs", ajaxargs); + 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 () { + var count = jQuery(this).closest('table').find('tbody tr').length; + if (count<=1) { + jQuery("tr#ordernumber-replacements-empty-row") + .removeClass("rowhidden") + .find('input,select,button,img') + .removeAttr('disabled'); + } + jQuery(this).closest('tr').remove(); + } + ); + + jQuery("#ordernumber_variables tbody").sortable(); +}); diff --git a/library/ordernumber_helper.php b/library/ordernumber_helper.php new file mode 100644 index 0000000000000000000000000000000000000000..ecfb0f99ec4f45c5e60760d2d656ed9fad7e4be2 --- /dev/null +++ b/library/ordernumber_helper.php @@ -0,0 +1,563 @@ +<?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) { + // 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"]=='yes')?"":$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>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) { + $cc = (object)$c; + $html[] = $this->counter_modification_create_row ($type, $cc->name, $cc->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'>" . htmlentities((string)(($counter=="")?($this->__ ('PLG_ORDERNUMBER_COUNTERLIST_GLOBAL')):$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/ordernumber_helper_woocommerce.php b/ordernumber_helper_woocommerce.php new file mode 100644 index 0000000000000000000000000000000000000000..a33ab2c389c7062d99c0e021343973f314ae62cd --- /dev/null +++ b/ordernumber_helper_woocommerce.php @@ -0,0 +1,115 @@ +<?php +/** + * Advanced Ordernumbers generic helper class for WooCommerce + * 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' ) ) { + die( 'Direct Access to ' . basename( __FILE__ ) . ' is not allowed.' ); +} +if (!class_exists( 'OrdernumberHelper' )) + require_once (dirname(__FILE__) . '/library/ordernumber_helper.php'); + +class OrdernumberHelperWooCommerce extends OrdernumberHelper { + public static $ordernumber_counter_prefix = '_ordernumber_counter_'; + + function __construct() { + parent::__construct(); + load_plugin_textdomain('opentools-ordernumbers', false, basename( dirname( __FILE__ ) ) . '/languages' ); + // WC-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 OrdernumberHelperWooCommerce(); + } + return $helper; + } + + /** + * HELPER FUNCTIONS, WooCommerce-specific + */ + public function __($string) { + $string = $this->readableString($string); + return __($string, 'opentools-advanced-ordernumbers'); + } + function urlPath($type, $file) { + 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'); + } +// print("<h1>$type Counter: $name with value $value encountered, prefix=".self::$ordernumber_counter_prefix.", parts=".print_r($parts,1)."</h1>"); + 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); + } + + +} diff --git a/ordernumbers_woocommerce.php b/ordernumbers_woocommerce.php new file mode 100644 index 0000000000000000000000000000000000000000..d6b2c6c8d392fb1b3216a451f9c8950081604a4f --- /dev/null +++ b/ordernumbers_woocommerce.php @@ -0,0 +1,487 @@ +<?php +/** + * This is the actual ordernumber plugin class for WooCommerce. + * Copyright (C) 2015 Reinhold Kainhofer, Open Tools + * Author: Open Tools, Reinhold Kainhofer + * Author URI: http://open-tools.net + * License: GPL2+ +*/ +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly +} +class OpenToolsOrdernumbers { + public $ordernumber_meta = "_oton_number_"; + public $ordernumber_new_placeholder = "[New Order]"; + + protected $helper = null; + protected $settings = array(); + + /** + * Construct the plugin object + */ + public function __construct() + { + $this->helper = OrdernumberHelperWooCommerce::getHelper(); + $this->initializeSettings(); + $this->initializeHooks(); + + $this->helper->registerCallback('setupStoreReplacements', array($this, 'setupStoreReplacements')); + $this->helper->registerCallback('setupOrderReplacements', array($this, 'setupOrderReplacements')); + $this->helper->registerCallback('setupUserReplacements', array($this, 'setupUserReplacements')); + $this->helper->registerCallback('setupShippingReplacements', array($this, 'setupShippingReplacements')); + $this->helper->registerCallback('setupThirdPartyReplacements', array($this, 'setupThirdPartyReplacements')); + } + + /** + * Install all neccessary filters and actions for this plugin + */ + protected function initializeHooks() { + $helper = OrdernumberHelperWooCommerce::getHelper(); + // CONFIGURATION SCREENS + add_filter( 'woocommerce_get_sections_checkout', array($this, 'add_admin_section')); + // The checkout settings page assumes all subpages are payment gateways, so we have to override this and manually pass our settings: + add_action( 'woocommerce_settings_checkout', array( $this, 'settings_output' ) ); + add_action( 'woocommerce_settings_save_checkout', array( $this, 'settings_save' ) ); + add_action( 'woocommerce_admin_field_ordernumber_counters', array( $this, 'admin_field_counters' ) ); + add_action( 'woocommerce_admin_field_ordernumber_variables', array( $this, 'admin_field_variables' ) ); + add_action( 'pre_update_option_ordernumber_variables', array( $this, 'update_option_variables')); + + add_action( 'woocommerce_order_actions', array( $this, 'add_order_action_new_number' ) ); + add_action( 'woocommerce_order_action_assign_new_ordernumber', array( $this, 'order_action_assign_new_ordernumber' ) ); + + add_action( 'admin_print_styles-woocommerce_page_wc-settings', array($helper, 'print_admin_styles')); + add_action( 'admin_print_scripts-woocommerce_page_wc-settings', array($this, 'print_admin_scripts')); + + // AJAX counter modifications + add_action( 'wp_ajax_setCounter', array($this, 'counter_set_callback') ); + add_action( 'wp_ajax_addCounter', array($this, 'counter_add_callback') ); + add_action( 'wp_ajax_deleteCounter', array($this, 'counter_delete_callback') ); + + // Add the ordernumber post meta to the search in the backend + add_filter( 'woocommerce_shop_order_search_fields', array($this, 'order_search_fields')); + // Sort the order list in the backend by order number rather than ID, make sure this is called LAST so we modify the defaults passed as arguments + add_filter( 'manage_edit-shop_order_sortable_columns', array( $this, 'modify_order_column_sortkey' ), 9999 ); + + // When a new order is created, we immediately assign the order number: + add_action( 'wp_insert_post', array(&$this, 'check_assignNumber'), 10, 3); +// add_action( 'save_post', array(&$this, 'check_assignNumber'), 10, 3); + // The filter to actually return the order number for the given order + add_filter ('woocommerce_order_number', array(&$this, 'get_ordernumber'), 10, 2/*<= Also get the order object! */); + } + + + /** + * Setup all options and the corresponding settings UI to configure this plugin, using the WP settings API + */ + protected function initializeSettings() { + // Init settings + $this->settings = array( + array( + 'name' => $this->helper->__( 'Advanced Order Numbers'), + 'desc' => $this->helper->__( 'Configure the format and the counters of the order numbers in WooCommerce.'), + 'type' => 'title', + 'id' => 'ordernumber_options' + ), + + array( + 'name' => $this->helper->__( 'Customize Order Numbers'), + 'desc' => $this->helper->__( 'Check to use custom order numbers rather than the default wordpress post ID.'), + 'id' => 'customize_ordernumber', + 'type' => 'checkbox', + 'default' => 'no' + ), + array( + 'title' => $this->helper->__( 'Order number format'), + 'desc' => $this->helper->__( 'The format for the order numbers (variables can be entered as [...], the counter is indicated by the #). To use a different counter name than displayed, put the custom counter name after a |, e.g. "[year]-[month]/#|[year]" to use the month in the order number, but reset the counter only yearly. Advanced settings for the counter can be added as [#####:start/step], e.g. [#:100] to start new counters at 100, or [#/5] to increment the counter by 5. The number of # in the format determine how many digits are shown at least, e.g. [########] will always show at least 8 digits for the counter, e.g. 00000014.'), + 'desc_tip' => true, + 'id' => 'ordernumber_format', + 'default' => '#', + 'type' => 'text', + 'css' => 'width: 100%', + ), + array( + 'title' => $this->helper->__( 'Use global counter'), + 'desc' => $this->helper->__( 'A global counter is independent of the number format and will never reset. A non-global counter runs within the number format and will start from the inital value whenever any of the variables used in the format changes (to be precise: a new counter will be used, so it is possible to have multiple counters running in parallel).'), + 'desc_tip' => true, + 'id' => 'ordernumber_global', + 'type' => 'checkbox', + 'default' => 'no', + ), + array( + 'name' => $this->helper->__( 'All order number counters'), + 'desc' => $this->helper->__( 'View and modify the current counter values. The counter value is the value used for the previous number. All changes are immediately applied!'), + 'desc_tip' => true, + 'id' => 'ordernumber_counters', + 'type' => 'ordernumber_counters', + 'nrtype' => 'ordernumber', + ), + array( 'type' => 'sectionend', 'id' => 'ordernumber_options' ), + + // TODO: customize order password, and other numbers! + + array( + 'name' => $this->helper->__( 'Custom Variables'), + 'desc' => $this->helper->__( 'Define your own (conditional) variables for use in the number formats'), + 'type' => 'title', + 'id' => 'ordernumber_variables' + ), + array( + 'id' => 'ordernumber_variables', + 'type' => 'ordernumber_variables', + ), + array( 'type' => 'sectionend', 'id' => 'ordernumber_variables' ), + + array( + 'name' => $this->helper->__( 'Current Counters'), + 'type' => 'title', + 'id' => 'ordernumber_counters' + ), + array( 'type' => 'sectionend', 'id' => 'ordernumber_counters' ), + + ); + // Default options + + add_option ('customize_ordernumber', 'no'); + add_option ('ordernumber_format', "#"); + add_option ('ordernumber_global', 'no'); + + add_option ('ordernumber_variables', array()); + } + + /** + * Insert our own section in the checkout setting page. Rearrange the sections array to make sure our settings + * come second place, directly after the default page with the '' key and before all the payment gateways + */ + function add_admin_section($sections) { + $newsections = array(); + foreach ($sections as $sec => $name ) { + $newsections[$sec] = $name; + if ($sec == '') { + $newsections['ordernumber'] = $this->helper->__('Order Numbers'); + } + } + return $newsections; + } + + public function settings_output() { + global $current_section; + if ($current_section == 'ordernumber') { + $settings = $this->settings; + WC_Admin_Settings::output_fields( $settings ); + } + } + + public function settings_save() { + global $current_section; + if ($current_section == 'ordernumber') { + $settings = $this->settings; + WC_Admin_Settings::save_fields( $settings ); + } + } + + /** + * Print the JS for the counter values and counter variables tables to the page header in the WC backend admin setting page + */ + public function print_admin_scripts() { + $this->helper->print_admin_scripts(); + wp_register_script( 'ordernumber-admin', plugins_url('assets/js/ordernumber-config.js', __FILE__), array('jquery')); + wp_enqueue_script( 'ordernumber-admin'); + } + + /** + * Render the Custom Variables configuration table + */ + public function admin_field_variables($settings) { + $variables = get_option( $settings['id'], array() ); + if (!is_array($variables)) { + $variables = array(); + } ?> + <tr valign="top"> + <td class="forminp forminp-<?php echo sanitize_title( $settings['type'] ) ?>" colspan="2"> + <?php + print $this->helper->custom_variables_create_table($settings['id'], $variables); + ?> + </td> + </tr> + <?php + } + + /** + * Store the variable replacements array into the options. Need to transpose the array before we can store it into the options... + * This filter is called directly before the option is saved. + */ + public function update_option_variables ($value) { + return OrdernumberHelper::transposeCustomVariables($value); + } + + + /** + * Render the Counter Values modification table + */ + public function admin_field_counters ($settings) { + // Description handling + $field_description = WC_Admin_Settings::get_field_description( $settings ); + extract( $field_description ); + ?> + + + <tr valign="top"> + <th scope="row" class="titledesc"> + <label for="<?php echo esc_attr( $settings['id'] ); ?>"><?php echo esc_html( $settings['title'] ); ?></label> + <?php echo $tooltip_html; ?> + </th> + <td class="forminp forminp-<?php echo sanitize_title( $settings['nrtype'] ) ?>"> + <?php + $counters = $this->helper->getAllCounters($settings['nrtype']); + echo $this->helper->counter_modification_create_table($settings['nrtype'], $counters); + ?> + </td> + </tr> + <?php + } + + /** + * Hook to add the order numer post meta field to the searchable field + * (so the admin can search for the order number in the backend) + */ + public function order_search_fields($fields) { + $fields[] = $this->ordernumber_meta; + return $fields; + } + + /** + * Sort the order list's "Order" column by our post meta rather than by ID + */ + public function modify_order_column_sortkey($columns) { + $columns['order_title'] = $this->ordernumber_meta; + return $columns; + } + + /** + * Add the "create new order number" action to the edit order page in the Backend + */ + public function add_order_action_new_number($actions) { + $actions['assign_new_ordernumber'] = $this->helper->__('Assign a new order number'); + return $actions; + } + /** + * Handle the "Assign a new order number" action from the edit order page in the backend + */ + public function order_action_assign_new_ordernumber( $order ) { + $number = $this->generateNumber($order->id, $order, 'ordernumber'); + } + + + /** + * Handle new posts created in the frontend. This action will be called for all posts, + * not only for orders, so we need to check explicitly. Also, this function will be called + * for order updates, so we need to check the update argument, too. + */ + public function check_assignNumber($post_id, $post, $update) { + // Is the post really an order? + // Order numbers are only assigned to orders on creation, not when updating! + if ($post->post_type != 'shop_order') { + return; + } else { + // Handle new admin-created orders, where the address is entered later on! + // Assign an order number: + $number = $this->assign_new_ordernumber($post_id, $post, $update); + } + } + + /** + * AJAX Counter handling (simple loading/storing counters), storing them as options + */ + + public function counter_delete_callback() { + $json = array('action' => 'delete_counter', 'success' => 0); + $json['success'] = $this->helper->deleteCounter($_POST['nrtype'], $_POST['counter']); + wp_send_json($json); + } + + public function counter_add_callback () { + $type = $_POST['nrtype']; + $format = $_POST['counter']; + $value = isset($_POST['value'])?$_POST['value']:"0"; + $json = array('action' => 'add_counter', 'success' => 0); + if ($this->helper->getCounter($type, $format, -1) != -1) { + // Counter already exists => error message + $json['error'] = sprintf($this->helper->__('Counter "%s" already exists, cannot create again.'), $format); + } else { + $json['success'] = $this->helper->setCounter($type, $format, $value); + $json['row'] = $this->helper->counter_modification_create_row($type, $format, $value); + } + wp_send_json($json); + } + + public function counter_set_callback () { + $json = array('action' => 'set_counter', 'success' => 0); + $json['success'] = $this->helper->setCounter($_POST['nrtype'], $_POST['counter'], $_POST['value']); + $json['row'] = $this->helper->counter_modification_create_row($_POST['nrtype'], $_POST['counter'], $_POST['value']); + wp_send_json($json); + } + + + + /** *********************************************************** + * + * REPLACEMENT FUNCTIONS + * + **************************************************************/ + + public function setupAddressReplacements(&$reps, $prefix, $address, $nrtype) { + $reps["[email]"] = $address->billing_email; + $reps["[firstname]"] = $address->billing_first_name; + $reps["[lastname]"] = $address->billing_last_name; + + $reps["[company]"] = $address->billing_company; + $reps["[zip]"] = $address->billing_postcode; + $reps["[postcode]"] = $address->billing_postcode; + $reps["[city]"] = $address->billing_city; + + $country = $address->billing_country; + $state = $address->billing_state; + $allcountries = WC()->countries->get_countries(); + $states = WC()->countries->get_states($country); + $reps["[country]"] = $country; + $reps["[countryname]"] = ( isset( $allcountries[ $country ] ) ) ? $allcountries[ $country ] : $country; + + $reps["[state]"] = $state; + $reps["[statename]"] = ( $country && $state && isset( $states[ $country ][ $state ] ) ) ? $states[ $country ][ $state ] : $state; + } + + public function setupStoreReplacements (&$reps, $order, $nrtype) { + } + + public function setupOrderReplacements (&$reps, $order, $nrtype) { + $reps["[orderid]"] = $order->id; + + if ($nrtype != 'ordernumber') { + $reps["[ordernumber]"] = $order->get_order_number(); + } + $reps["[orderstatus]"] = $order->get_status(); + $reps["[currency]"] = $order->get_order_currency(); + + $this->setupAddressReplacements($reps, "", $order, $nrtype); + + $reps["[articles]"] = $order->get_item_count(); +// $reps["[downloadpermitted]"] = $order->is_download_permitted(); +// $reps["[hasdownloads]"] = $order->has_downloadable_item(); +// $reps["[coupons]"] = $order->get_used_coupons(); + $reps["[ordertotal]"] = $order->get_total(); + $reps["[amount]"] = $order->get_total(); + $reps["[ordersubtotal]"] = $order->get_subtotal(); + $reps["[totaltax]"] = $order->get_total_tax(); + $reps["[totalshipping]"] = $order->get_total_shipping(); + + // List-valued properties for custom variable checks: + // TODO: Also implement variable for: + // - Shipping needed + // - Downloads available + $lineitems = $order->get_items(); + $skus = array(); + $categories = array(); + $tags = array(); + $shippingclasses = array(); + foreach ($lineitems as $l) { + $p = $order->get_product_from_item($l); + $skus[$p->get_sku()] = 1; + foreach (wc_get_product_terms( $p->id, 'product_cat') as $c) { + $categories[$c->slug] = 1; + } + foreach (wc_get_product_terms( $p->id, 'product_tag') as $c) { + $tags[$c->slug] = 1; + } + $shippingclasses[$p->get_shipping_class()] = 1; + } + $reps["[skus]"] = array_keys($skus); + $reps["[categories]"] = array_keys($categories); + $reps["[tags]"] = array_keys($tags); + $reps["[shippingclasses]"] = array_keys($shippingclasses); + } + + public function setupUserReplacements (&$reps, $details, $nrtype) { + $reps["[ipaddress]"] = $details->customer_ip_address; + $reps["[userid]"] = $details->get_user_id(); + } + + public function setupShippingReplacements(&$reps, $order, $nrtype) { +// $reps["[shippingmethod]"] = $order->getShippingMethod(); + } + + /*public function setupInvoiceReplacements (&$reps, $invoice, $order, $nrtype) { + $reps["[invoiceid]"] = $invoice->getId(); + }*/ + + public function setupThirdPartyReplacements (&$reps, $details, $nrtype) { + $reps = apply_filters( 'opentools_ordernumber_replacements', $reps, $details, $nrtype); + } + + + function generateNumber($orderid, $order, $type='ordernumber') { + if (get_option('customize_'.$type, 'no')!='no') { + $fmt = get_option ($type.'_format', "#"); + $ctrsettings = array( + "${type}_format" => '', + "${type}_counter" => '', + "${type}_global" => get_option ($type.'_global', 'no'), + "${type}_padding" => 1, + "${type}_step" => 1, + "${type}_start" => 1, + ); + $customvars = get_option ('ordernumber_variables', array()); + + $number = $this->helper->createNumber ($fmt, $type, $order, $customvars, $ctrsettings); + update_post_meta( $orderid, $this->ordernumber_meta.$type, $number ); + return $number; + } else { + return $orderid; + } + } + + /** + * The hook to assign a customized order number (unless the order already has one assigned) + */ + function assign_new_ordernumber($orderid, $order, $update=true) { + if ((!$update) /*&& ($order->post_status == 'auto-draft')*/) { + // New order => assign placeholder, which will later be overwritten the real order number + update_post_meta( $orderid, $this->ordernumber_meta.'ordernumber', $this->ordernumber_new_placeholder ); + } + // If we do not have an order (yet), we cannot proceed. But we probably have created the + // ordernumber placeholder for that post, so this function has done its job and we can return + if (!$order instanceof WC_Order) { + return; + } + $number = get_post_meta( $orderid, $this->ordernumber_meta.'ordernumber', 'true'); + if ($number == $this->ordernumber_new_placeholder && $order->post_status != 'auto-draft') { + $number = $this->generateNumber($orderid, $order, 'ordernumber'); + // Assign a new number + } + return $number; + } + + /** + * The generic function to retrieve a particular number + */ + function get_number($orderid, $order, $type = 'ordernumber') { + $stored_number = get_post_meta( $orderid, $this->ordernumber_meta.$type, 'true'); + if ($stored_number == $this->ordernumber_new_placeholder) { + // Check whether the order was now really created => create order number now + return $this->assign_new_ordernumber($orderid, $order, $type); + } elseif (!empty($stored_number)) { + // Order number already exists => simply return it + return $stored_number; + } else { + // No order number was created for this order, so simply use the orderid as default. + return $orderid; + } + } + + /** + * Callback function for Woocommerce to retrieve the ordernumber for an order + */ + function get_ordernumber($orderid, $order) { + return $this->get_number($orderid, $order); + } + +} diff --git a/woocommerce-advanced-ordernumbers.php b/woocommerce-advanced-ordernumbers.php index af39a82465d6ea65345f5348cd7f327ce7dbe458..942f529d83efe052cc1108419cccbe44857a723a 100644 --- a/woocommerce-advanced-ordernumbers.php +++ b/woocommerce-advanced-ordernumbers.php @@ -13,13 +13,6 @@ WC tested up to: 2.3 */ -/** - * The structure of this plugin originally followed this tutorial, although much of the plugin has been rewritten since then: - * http://www.yaconiello.com/blog/how-to-write-wordpress-plugin/ - * A lot of coding ideas were also taken directly from the way things are implemented in WooCommerce itself. - * The order number handling code itself comes straight from our general library, which is also used for VirtueMart and Magento. - */ - if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } @@ -28,923 +21,12 @@ if ( ! defined( 'ABSPATH' ) ) { **/ if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { + require_once( plugin_dir_path( __FILE__ ) . '/ordernumber_helper_woocommerce.php'); if (!class_exists("OpenToolsOrdernumbers")) { - class OpenToolsOrdernumbers { - public $ordernumber_meta = "oton_number_"; - public $ordernumber_new_placeholder = "[New Order]"; - public $ordernumber_counter_prefix = 'ordernumber-counter-'; - - - /** - * STATIC HELPER FUNCTIONS, WooCommerce-specific - */ - public static function __($string) { - return __($string, 'opentools-advanced-ordernumbers'); - } - public static function rel_url($file) { - return plugins_url($file, __FILE__); - } - public static function img_url($img) { - return self::rel_url('assets/images/'.$img ); - } - public static function css_url($css) { - return self::rel_url('assets/css/'.$css ); - } - public static function js_url($js) { - return self::rel_url('assets/js/'.$js ); - } - - - - - /** - * Construct the plugin object - */ - public function __construct() - { - $plugin = plugin_basename(__FILE__); - load_plugin_textdomain('opentools-ordernumbers', false, basename( dirname( __FILE__ ) ) . '/languages' ); - - // Init settings - $this->settings = array( - array( - 'name' => self::__( 'Advanced Order Numbers'), - 'desc' => self::__( 'Configure the format and the counters of the order numbers in WooCommerce.'), - 'type' => 'title', - 'id' => 'ordernumber_options' - ), - - array( - 'name' => self::__( 'Customize Order Numbers'), - 'desc' => self::__( 'Check to use custom order numbers rather than the default wordpress post ID.'), - 'id' => 'customize_ordernumber', - 'type' => 'checkbox', - 'default' => 'no' - ), - array( - 'title' => self::__( 'Order number format'), - 'desc' => self::__( 'The format for the order numbers (variables can be entered as [...], the counter is indicated by the #). To use a different counter name than displayed, put the custom counter name after a |, e.g. "[year]-[month]/#|[year]" to use the month in the order number, but reset the counter only yearly. Advanced settings for the counter can be added as [#####:start/step], e.g. [#:100] to start new counters at 100, or [#/5] to increment the counter by 5. The number of # in the format determine how many digits are shown at least, e.g. [########] will always show at least 8 digits for the counter, e.g. 00000014.'), - 'desc_tip' => true, - 'id' => 'ordernumber_format', - 'default' => '#', - 'type' => 'text', - 'css' => 'width: 100%', - ), - array( - 'title' => self::__( 'Use global counter'), - 'desc' => self::__( 'A global counter is independent of the number format and will never reset. A non-global counter runs within the number format and will start from the inital value whenever any of the variables used in the format changes (to be precise: a new counter will be used, so it is possible to have multiple counters running in parallel).'), - 'desc_tip' => true, - 'id' => 'ordernumber_global', - 'type' => 'checkbox', - 'default' => 'no', - ), - array( 'type' => 'sectionend', 'id' => 'ordernumber_options' ), - - // TODO: customize order password, and other numbers! - - array( - 'name' => self::__( 'Custom Variables'), - 'desc' => self::__( 'Define your own (conditional) variables for use in the number formats'), - 'type' => 'title', - 'id' => 'ordernumber_variables' - ), - array( - 'id' => 'ordernumber_variables', - 'type' => 'ordernumber_variables', - ), - array( 'type' => 'sectionend', 'id' => 'ordernumber_variables' ), - - array( - 'name' => self::__( 'Current Counters'), - 'type' => 'title', - 'id' => 'ordernumber_counters' - ), - array( - 'name' => self::__( 'All Ordernumber Counters'), - 'desc' => self::__( 'View and modify the current counter values. The counter value is the value used for the previous number. All changes are immediately applied!'), - 'desc_tip' => true, - 'id' => 'ordernumber_counters', - 'type' => 'ordernumber_counters', - ), - array( 'type' => 'sectionend', 'id' => 'ordernumber_counters' ), - - ); - // Default options - - add_option ('customize_ordernumber', 'no'); - add_option ('ordernumber_format', "#"); - add_option ('ordernumber_global', 'no'); - - add_option ('ordernumber_variables', array()); - - // register filters and actions - - // CONFIGURATION SCREENS - add_filter( 'woocommerce_get_sections_checkout', array($this, 'add_admin_section')); - // The checkout settings page assumes all subpages are payment gateways, so we have to override this and manually pass our settings: - add_action( 'woocommerce_settings_checkout', array( $this, 'settings_output' ) ); - add_action( 'woocommerce_settings_save_checkout', array( $this, 'settings_save' ) ); - add_action( 'woocommerce_admin_field_ordernumber_counters', array( $this, 'admin_field_counters' ) ); - add_action( 'woocommerce_admin_field_ordernumber_variables', array( $this, 'admin_field_variables' ) ); - add_action( 'pre_update_option_ordernumber_variables', array( $this, 'update_option_variables')); - - add_action( 'woocommerce_order_actions', array( $this, 'add_order_action_new_number' ) ); - add_action( 'woocommerce_order_action_assign_new_ordernumber', array( $this, 'order_action_assign_new_ordernumber' ) ); - - add_action( 'admin_print_styles-woocommerce_page_wc-settings', array($this, 'print_admin_styles')); - add_action( 'admin_print_scripts-woocommerce_page_wc-settings', array($this, 'print_admin_scripts')); - - // AJAX counter modifications - add_action( 'wp_ajax_set_counter', array($this, 'counter_set_callback') ); - add_action( 'wp_ajax_add_counter', array($this, 'counter_add_callback') ); - add_action( 'wp_ajax_delete_counter', array($this, 'counter_delete_callback') ); - - // Add the ordernumber post meta to the search in the backend - add_filter( 'woocommerce_shop_order_search_fields', array($this, 'order_search_fields')); - // Sort the order list in the backend by order number rather than ID, make sure this is called LAST so we modify the defaults passed as arguments - add_filter( 'manage_edit-shop_order_sortable_columns', array( $this, 'modify_order_column_sortkey' ), 9999 ); - - // When a new order is created, we immediately assign the order number: - add_action( 'wp_insert_post', array(&$this, 'check_assignNumber'), 10, 3); -// add_action( 'save_post', array(&$this, 'check_assignNumber'), 10, 3); - // The filter to actually return the order number for the given order - add_filter ('woocommerce_order_number', array(&$this, 'get_ordernumber'), 10, 2/*<= Also get the order object! */); - } - - // Activate the plugin - public static function activate() {} - - // Deactivate the plugin - public static function deactivate() {} - - /** - * Insert our own section in the checkout setting page. Rearrange the sections array to make sure our settings - * come second place, directly after the default page with the '' key and before all the payment gateways - */ - function add_admin_section($sections) { - $newsections = array(); - foreach ($sections as $sec => $name ) { - $newsections[$sec] = $name; - if ($sec == '') { - $newsections['ordernumber'] = self::__('Order Numbers'); - } - } - return $newsections; - } - - public function settings_output() { - global $current_section; - if ($current_section == 'ordernumber') { - $settings = $this->settings; - WC_Admin_Settings::output_fields( $settings ); - } - } - - public function settings_save() { - global $current_section; - if ($current_section == 'ordernumber') { - $settings = $this->settings; - WC_Admin_Settings::save_fields( $settings ); - } - } - - /** - * Print the CSS for the counter values and counter variables tables to the page header in the WC backend admin setting page - */ - public function print_admin_styles () { - wp_register_style ( 'ordernumber-counter-style', self::css_url('ordernumber-counter.css') ); - wp_enqueue_style('ordernumber-counter-style'); - - wp_register_style ( 'ordernumber-variables-style', self::css_url('ordernumber-variables.css') ); - wp_enqueue_style('ordernumber-variables-style'); - } - /** - * Print the JS for the counter values and counter variables tables to the page header in the WC backend admin setting page - */ - public function print_admin_scripts() { - - wp_register_script( 'ordernumber-counter-script', self::js_url( 'ordernumber-counter.js', __FILE__), array('jquery') ); - wp_enqueue_script( 'ordernumber-counter-script'); - - // Handle the translations: - $localizations = array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ); - - $localizations['ORDERNUMBER_JS_JSONERROR'] = self::__("Error reading response from server:"); - $localizations['ORDERNUMBER_JS_NOT_AUTHORIZED'] = self::__("You are not authorized to modify order number counters."); - $localizations['ORDERNUMBER_JS_NEWCOUNTER'] = self::__("Please enter the format/name of the new counter:"); - $localizations['ORDERNUMBER_JS_ADD_FAILED'] = self::__("Failed adding counter {0}"); - $localizations['ORDERNUMBER_JS_INVALID_COUNTERVALUE'] = self::__("You entered an invalid value for the counter.\n\n"); - - $localizations['ORDERNUMBER_JS_EDITCOUNTER'] = self::__("{0}Please enter the new value for the counter '{1}' (current value: {2}):"); - $localizations['ORDERNUMBER_JS_MODIFY_FAILED'] = self::__("Failed modifying counter {0}"); - $localizations['ORDERNUMBER_JS_DELETECOUNTER'] = self::__("Really delete counter '{0}' with value '{1}'?"); - $localizations['ORDERNUMBER_JS_DELETE_FAILED'] = self::__("Failed deleting counter {0}"); - - // in JavaScript, object properties are accessed as ajax_object.ajax_url, ajax_object.we_value - wp_localize_script( 'ordernumber-counter-script', 'ajax_ordernumber', $localizations ); - - - - wp_register_script( 'ordernumber-variables-script', self::js_url( 'ordernumber-variables.js'), array('jquery') ); - wp_enqueue_script( 'ordernumber-variables-script'); - - wp_register_script( 'ordernumber-admin', self::js_url( 'ordernumber-config.js'), array('jquery')); - wp_enqueue_script( 'ordernumber-admin'); - } - - /** - * Render the Custom Variables configuration table - */ - public function admin_field_variables($settings) { - $variables = get_option( $settings['id'], array() ); - if (!is_array($variables)) { - $variables = array(); - } ?> - <tr valign="top"> - <td class="forminp forminp-<?php echo sanitize_title( $settings['type'] ) ?>" colspan="2"> - <?php - print $this->customvar_admin_table($settings['id'], $variables); - ?> - </td> - </tr> - <?php - } - - protected function customvar_admin_table($id, $variables) { ?> - - <table id="ordernumber_variables_template" style="display:none"> - <?php echo $this->create_replacements_row_html($id, array(), 'disabled'); ?> - </table> - - <table id="ordernumber_variables" class="ordernumber_variables widefat wc_input_table sortable" cellspacing="0"> - <?php - $columns = array( - 'variables_ifvar' => self::__( 'If variable ...'), - 'variables_ifop' => '', - 'variables_ifval' => self::__( 'Value'), - 'variables_then' => self::__( ''), - 'variables_thenvar' => self::__( 'Set variable ...'), - 'variables_thenval' => self::__( 'to value ...'), - 'sort' => '', - 'variables_settings' => '', - ); - ?> - <thead> - <tr> - <?php - foreach ( $columns as $key => $column ) { - echo '<th class="' . esc_attr( $key ) . '">' . esc_html( $column ) . '</th>'; - } - ?> - </tr> - <tr id = "ordernumber-replacements-empty-row" class="oton-empty-row-notice <?php echo (empty($variables))?'':'rowhidden'; ?>"> - <td class="oton-empty-row-notice" colspan="8"> - <em> <?php echo self::__('No custom variables have been defined.'); ?></em> - <input type="hidden" name="<?php echo $id; ?>" value="" <?php echo (empty($variables))?'':'disabled'; ?>> - </td> - </tr> - </thead> - <colgroup> - <?php - foreach ($columns as $key => $column) { - echo '<col class="' . esc_attr($key) . '" />'; - } - ?> - </colgroup> - - <tbody> - <?php - foreach ($variables as $var) { - echo $this->create_replacements_row_html($id, $var); - } ?> - </tbody> - <tfoot> - <tr class='addreplacement_row'> - <td colspan=8 class='variable_add'> - <div class='ordernumber-variables-addbtn ordernumber-btn' onClick="ordernumberVariablesAddRow('ordernumber_variables_template', 'ordernumber_variables')"> - <div class='ordernumber-ajax-loading'><img src='<?php echo self::img_url( 'icon-16-new.png' ); ?>' class='ordernumber-counter-addbtn' /></div> - <?php _e('Add new custom variable'); ?> - </div> - </td> - </tr> - </tfoot> - </table> - <?php - } - - protected function create_replacements_row_html($name, $values = array(), $disabled = '') { - $escname = esc_attr($name); - $operator = (isset($values['conditionop'])?$values['conditionop']:''); - $operators = array( - 'equals' => '=', - 'contains' => self::__('contains'), - 'smaller' => '<', - 'smallerequal' => '<=', - 'larger' => '>', - 'largerequal' => '>=', - 'startswith' => self::__('starts with'), - 'endswith' => self::__('ends with'), - ); - // Wrap all output of the values with esc_html to prevent script injection attacks - $html = ' - <tr> - <td class="variables_ifvar"><input name="' . $escname . '[conditionvar][]" value="' . (isset($values['conditionvar'])?esc_html($values['conditionvar']):'') . '" ' . esc_html($disabled) . '/></td> - <td class="variables_ifop" ><select name="' . $escname . '[conditionop][]" ' . $disabled . '>'; - foreach ($operators as $op => $opname) { - $html .= ' <option value="' . esc_attr($op) . '" ' . (($op === $operator)?'selected':'') . '>' . esc_html($opname) . '</option>'; - } - $html .= '</select></td> - <td class="variables_ifval" ><input name="' . $escname . '[conditionval][]" value="' . (isset($values['conditionval'])?esc_html($values['conditionval']):'') . '" ' . esc_html($disabled) . '/></td> - <td class="variables_then">=></td> - <td class="variables_thenvar"><input name="' . $escname . '[newvar][]" value="' . (isset($values['newvar'])?esc_html($values['newvar']):'') . '" ' . esc_html($disabled) . '/></td> - <td class="variables_thenval"><input name="' . $escname . '[newval][]" value="' . (isset($values['newval'])?esc_html($values['newval']):'') . '" ' . esc_html($disabled) . '/></td> - <td class="sort"></td> - <td class="variables_settings"><img src="' . self::img_url( 'icon-16-delete.png' ) . '" class="ordernumber-replacement-deletebtn ordernumber-btn"></td> - </tr>'; - return $html; - } - - /** - * Store the variable replacements array into the options. Need to transpose the array before we can store it into the options... - * This filter is called directly before the option is saved. - */ - public function update_option_variables ($value) { - if (!is_array($value)) - return array(); - $keys = array_keys($value); - $vallist = array(); - - foreach (array_keys($value[$keys[0]]) as $i) { - $entry = array(); - foreach ($keys as $k) { - $entry[$k] = $value[$k][$i]; - } - $vallist[] = $entry; - } - return $vallist; - } - - - /** - * Render the Counter Values modification table - */ - public function admin_field_counters ($settings) { - // Description handling - $field_description = WC_Admin_Settings::get_field_description( $settings ); - extract( $field_description ); - - // First, get all counter names: - $counters = array(); - $pfxlen = strlen($this->ordernumber_counter_prefix ); - foreach (wp_load_alloptions() as $name => $value) { - if (substr($name, 0, $pfxlen) == $this->ordernumber_counter_prefix) { - $parts = explode('-', substr($name, $pfxlen), 2); - if (sizeof($parts)==1) { - array_unshift($parts, 'ordernumber'); - } - $counters[] = array( - 'type' => $parts[0], - 'name' => $parts[1], - 'value' => $value, - ); - } - } - ?> - - - <tr valign="top"> - <th scope="row" class="titledesc"> - <label for="<?php echo esc_attr( $settings['id'] ); ?>"><?php echo esc_html( $settings['title'] ); ?></label> - <?php echo $tooltip_html; ?> - </th> - <td class="forminp forminp-<?php echo sanitize_title( $settings['type'] ) ?>"> - <img src='<?php echo self::img_url( 'loading.gif' ); ?>' class='wc-ordernumber-loading' style="display: none; position: absolute; top: 2px; left: 0px; z-index: 9999;"/> - <table class="wc_ordernumber_counters widefat" cellspacing="0"> - <?php - $columns = apply_filters( 'woocommerce_ordernumber_counters_columns', array( - 'type' => self::__( ''), - 'name' => self::__( 'Counter name'), - 'value' => self::__( 'Counter value'), - 'settings' => '' - ) ); - ?> - <thead> - <tr> - <?php - foreach ( $columns as $key => $column ) { - echo '<th class="counter_' . esc_attr( $key ) . '">' . esc_html( $column ) . '</th>'; - } - ?> - </tr> - </thead> - <colgroup> - <?php - foreach ($columns as $key => $column) { - echo '<col class="counter_' . esc_attr($key) . '" />'; - } - ?> - </colgroup> - - <tbody> - <?php - foreach ($counters as $counter) { - echo $this->create_admin_counter_row($counter['type'], $counter['name'], $counter['value']); - } - ?> - </tbody> - <tfoot> - <tr class='addcounter_row'> - <td class="counter_type"></td> - <td colspan=3 class='counter_add'> - <div class='ordernumber-counter-addbtn ordernumber-btn' onClick="ajaxAddCounter(this, 'ordernumber')"> - <div class='ordernumber-ajax-loading'><img src='<?php echo self::img_url( 'icon-16-new.png' ); ?>' class='ordernumber-counter-addbtn' /></div> - <?php _e('Add new counter'); ?> - </div> - </td> - </tr> - </tfoot> - </table> - </td> - </tr> - <?php - } - - public function create_admin_counter_row ($type, $format, $value=0) { - $html = " - <tr class='counter_row counter_type_" . $type . "'> - <td class='counter_type'>" . self::__($type, 'wooocommerce-advanced-ordernumbers' ) . "</td> - <td class='counter_format'>" . (empty($format)?("<i>".self::__("[Global]")."</i>"):esc_html($format)) . "</td> - <td class='counter_value'>" . esc_html($value) . "</td> - <td class='counter_buttons'> - <div class='ordernumber-ajax-loading'> - <img src='" . self::img_url( 'icon-16-edit.png' ) . "' - class='ordernumber-counter-editbtn ordernumber-btn' - onClick='ajaxEditCounter(this, " . json_encode($type) . ", " . json_encode($format) . ", " . json_encode($value) . ")' /> - </div> - <div class='ordernumber-ajax-loading'> - <img src='" . self::img_url( 'icon-16-delete.png' ) . "' - class='ordernumber-counter-deletebtn ordernumber-btn' - onClick='ajaxDeleteCounter(this, " . json_encode($type) . ", " . json_encode($format) . ", " . json_encode($value) . ")' /> - </div> - </td> - </tr>"; - return $html; - } - - - /** - * Hook to add the order numer post meta field to the searchable field - * (so the admin can search for the order number in the backend) - */ - public function order_search_fields($fields) { - $fields[] = $this->ordernumber_meta; - return $fields; - } - - /** - * Sort the order list's "Order" column by our post meta rather than by ID - */ - public function modify_order_column_sortkey($columns) { - $custom = array( - 'order_title' => $this->ordernumber_meta, - ); - // Use the passed columns as "default", so effectively, only the order_title will be changed: - return wp_parse_args ($custom, $columns); - } - - /** - * Add the "create new order number" action to the edit order page in the Backend - */ - public function add_order_action_new_number($actions) { - $actions['assign_new_ordernumber'] = self::__('Assign a new order number'); - return $actions; - } - /** - * Handle the "Assign a new order number" action from the edit order page in the backend - */ - public function order_action_assign_new_ordernumber( $order ) { - $number = $this->assignNumber($order->id, $order, 'ordernumber'); - } - - - /** - * Handle new posts created in the frontend. This action will be called for all posts, - * not only for orders, so we need to check explicitly. Also, this function will be called - * for order updates, so we need to check the update argument, too. - */ - public function check_assignNumber($post_id, $post, $update) { - // Is the post really an order? - // Order numbers are only assigned to orders on creation, not when updating! - if ($post->post_type != 'shop_order') { - return; - } else { - // Handle new admin-created orders, where the address is entered later on! - // Assign an order number: - $number = $this->assign_new_ordernumber($post_id, $post, $update); - } - } - - /** - * Counter handling (simple loading/storing counters), storing them as options - */ - function _getCounter($type, $format, $start=0) { - $count = get_option ($this->ordernumber_counter_prefix.$type.'-'.$format, $start); - return $count; - } - // Insert new counter value into the db or update existing one - function _setCounter($type, $format, $value) { - return update_option($this->ordernumber_counter_prefix.$type.'-'.$format, $value); - } - function _deleteCounter($type, $format) { - return delete_option($this->ordernumber_counter_prefix.$type.'-'.$format); - } - - public function counter_delete_callback() { - $json = array('action' => 'delete_counter', 'success' => 0); - $json['success'] = $this->_deleteCounter($_POST['nrtype'], $_POST['counter']); - wp_send_json($json); - } - - public function counter_add_callback () { - $type = $_POST['nrtype']; - $format = $_POST['counter']; - $value = isset($_POST['value'])?$_POST['value']:"0"; - $json = array('action' => 'add_counter', 'success' => 0); - if ($this->_getCounter($type, $format, -1) != -1) { - // Counter already exists => error message - $json['error'] = sprintf(self::__('Counter "%s" already exists, cannot create again.'), $format); - } else { - $json['success'] = $this->_setCounter($type, $format, $value); - $json['newrow'] = $this->create_admin_counter_row($type, $format, $value); - } - wp_send_json($json); - } - - public function counter_set_callback () { - $json = array('action' => 'set_counter', 'success' => 0); - $json['success'] = $this->_setCounter($_POST['nrtype'], $_POST['counter'], $_POST['value']); - wp_send_json($json); - } - - - - /** *********************************************************** - * - * REPLACEMENT FUNCTIONS - * - **************************************************************/ - - /* Return a random "string" of the given length taken from the given alphabet */ - static function randomString($alphabet, $len) { - $alen = strlen($alphabet); - $r = ""; - for ($n=0; $n<$len; $n++) { - $r .= $alphabet[mt_rand(0, $alen-1)]; - } - return $r; - } - - 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, $order, $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 setupAddressReplacements(&$reps, $prefix, $address, $nrtype) { - $reps["[email]"] = $address->billing_email; - $reps["[firstname]"] = $address->billing_first_name; - $reps["[lastname]"] = $address->billing_last_name; - - $reps["[company]"] = $address->billing_company; - $reps["[zip]"] = $address->billing_postcode; - $reps["[postcode]"] = $address->billing_postcode; - $reps["[city]"] = $address->billing_city; - - $country = $address->billing_country; - $state = $address->billing_state; - $allcountries = WC()->countries->get_countries(); - $states = WC()->countries->get_states($country); - $reps["[country]"] = $country; - $reps["[countryname]"] = ( isset( $allcountries[ $country ] ) ) ? $allcountries[ $country ] : $country; - - $reps["[state]"] = $state; - $reps["[statename]"] = ( $country && $state && isset( $states[ $country ][ $state ] ) ) ? $states[ $country ][ $state ] : $state; - } - - protected function setupStoreReplacements (&$reps, $order, $nrtype) { - } - - protected function setupOrderReplacements (&$reps, $order, $nrtype) { - $reps["[orderid]"] = $order->id; - - if ($nrtype != 'ordernumber') { - $reps["[ordernumber]"] = $order->get_order_number(); - } - $reps["[orderstatus]"] = $order->get_status(); - $reps["[currency]"] = $order->get_order_currency(); - - $this->setupAddressReplacements($reps, "", $order, $nrtype); - - $reps["[articles]"] = $order->get_item_count(); -// $reps["[downloadpermitted]"] = $order->is_download_permitted(); -// $reps["[hasdownloads]"] = $order->has_downloadable_item(); -// $reps["[coupons]"] = $order->get_used_coupons(); - $reps["[ordertotal]"] = $order->get_total(); - $reps["[amount]"] = $order->get_total(); - $reps["[ordersubtotal]"] = $order->get_subtotal(); - $reps["[totaltax]"] = $order->get_total_tax(); - $reps["[totalshipping]"] = $order->get_total_shipping(); - - // List-valued properties for custom variable checks: - // TODO: Also implement variable for: - // - Shipping needed - // - Downloads available - $lineitems = $order->get_items(); - $skus = array(); - $categories = array(); - $tags = array(); - $shippingclasses = array(); - foreach ($lineitems as $l) { - $p = $order->get_product_from_item($l); - $skus[$p->get_sku()] = 1; - foreach (wc_get_product_terms( $p->id, 'product_cat') as $c) { - $categories[$c->slug] = 1; - } - foreach (wc_get_product_terms( $p->id, 'product_tag') as $c) { - $tags[$c->slug] = 1; - } - $shippingclasses[$p->get_shipping_class()] = 1; - } - $reps["[skus]"] = array_keys($skus); - $reps["[categories]"] = array_keys($categories); - $reps["[tags]"] = array_keys($tags); - $reps["[shippingclasses]"] = array_keys($shippingclasses); - } - - protected function setupUserReplacements (&$reps, $details, $nrtype) { - $reps["[ipaddress]"] = $details->customer_ip_address; - $reps["[userid]"] = $details->get_user_id(); - } - - protected function setupShippingReplacements(&$reps, $order, $nrtype) { -// $reps["[shippingmethod]"] = $order->getShippingMethod(); - } - - /*protected function setupInvoiceReplacements (&$reps, $invoice, $order, $nrtype) { - $reps["[invoiceid]"] = $invoice->getId(); - }*/ - - protected function setupThirdPartyReplacements (&$reps, $details, $nrtype) { - $reps = apply_filters( 'opentools_ordernumber_replacements', $reps, $details, $nrtype); - } - - protected function setupReplacements($nrtype, $details) { - $reps = array(); - $this->setupDateTimeReplacements($reps, $details, $nrtype); - $this->setupStoreReplacements($reps, $details, $nrtype); - $this->setupOrderReplacements($reps, $details, $nrtype); - $this->setupUserReplacements($reps, $details, $nrtype); - $this->setupShippingReplacements($reps, $details, $nrtype); - $this->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) { - // First, extract all counter settings, i.e. all strings of the form [#####:startval/increment] or [####/increment:startval] - $regexp = '%\[(#+)(/([0-9]+))?(:([0-9]+))?(/([0-9]+))?\]%'; -// $counters = array(); - - 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]; - } - -// print("<pre>Counters regexp matches: ".print_r($counters,1)."</pre>"); - $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"]=='yes')?"":$parts[(count($parts)>1)?1:0]; - -// print("<pre>Counter settings are: ".print_r($ctrsettings,1)."</pre>"); - return $ctrsettings; - } - - /* replace the variables in the given format. $type indicates the type of number, currently only 'ordernumber', because WooCommerce does not support invoices or customer numbers. We might allow the shop owner to customize the order password, though. */ - 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); - - $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"]); - return $number; - } - - function assignNumber($orderid, $order, $type='ordernumber') { - if (get_option('customize_'.$type, 'no')!='no') { - $fmt = get_option ($type.'_format', "#"); - $ctrsettings = array( - "${type}_format" => '', - "${type}_counter" => '', - "${type}_global" => get_option ($type.'_global', 'no'), - "${type}_padding" => 1, - "${type}_step" => 1, - "${type}_start" => 1, - ); - $customvars = get_option ('ordernumber_variables', array()); - - $number = $this->createNumber ($fmt, $type, $order, $customvars, $ctrsettings); - update_post_meta( $orderid, $this->ordernumber_meta.$type, $number ); - return $number; - } else { - return $orderid; - } - } - - /** - * The hook to assign a customized order number (unless the order already has one assigned) - */ - function assign_new_ordernumber($orderid, $order, $update=true) { - if ((!$update) /*&& ($order->post_status == 'auto-draft')*/) { - // New order => assign placeholder, which will later be overwritten the real order number - update_post_meta( $orderid, $this->ordernumber_meta.'ordernumber', $this->ordernumber_new_placeholder ); - } - // If we do not have an order (yet), we cannot proceed. But we probably have created the - // ordernumber placeholder for that post, so this function has done its job and we can return - if (!$order instanceof WC_Order) { - return; - } - $number = get_post_meta( $orderid, $this->ordernumber_meta.'ordernumber', 'true'); - if ($number == $this->ordernumber_new_placeholder && $order->post_status != 'auto-draft') { - $number = $this->assignNumber($orderid, $order, 'ordernumber'); - // Assign a new number - } - return $number; - } - - /** - * The generic function to retrieve a particular number - */ - function get_number($orderid, $order, $type = 'ordernumber') { - $stored_number = get_post_meta( $orderid, $this->ordernumber_meta.$type, 'true'); - if ($stored_number == $this->ordernumber_new_placeholder) { - // Check whether the order was now really created => create order number now - return $this->assign_new_ordernumber($orderid, $order, $type); - } elseif (!empty($stored_number)) { - // Order number already exists => simply return it - return $stored_number; - } else { - // No order number was created for this order, so simply use the orderid as default. - return $orderid; - } - } - - } + require_once( plugin_dir_path( __FILE__ ) . '/ordernumbers_woocommerce.php'); } if (class_exists("OpenToolsOrdernumbers")) { - // Installation and uninstallation hooks - register_activation_hook(__FILE__, array('OpenToolsOrdernumbers', 'activate')); - register_deactivation_hook(__FILE__, array('OpenToolsOrdernumbers', 'deactivate')); - // instantiate the plugin class $ordernumber_plugin = new OpenToolsOrdernumbers(); }