settings = array(
array(
'name' => __( 'Advanced Order Numbers', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'Configure the format and the counters of the order numbers in WooCommerce.', 'woocommerce-advanced-ordernumbers' ),
'type' => 'title',
'id' => 'ordernumber_options'
),
array(
'name' => __( 'Customize Order Numbers', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'Check to use custom order numbers rather than the default wordpress post ID.', 'woocommerce-advanced-ordernumbers' ),
'id' => 'customize_ordernumber',
'type' => 'checkbox',
'default' => 'no'
),
array(
'title' => __( 'Order number format', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( '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.', 'woocommerce-advanced-ordernumbers' ),
'desc_tip' => true,
'id' => 'ordernumber_format',
'default' => '#',
'type' => 'text',
),
array(
'title' => __( 'Use global counter', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( '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).', 'woocommerce-advanced-ordernumbers' ),
'desc_tip' => true,
'id' => 'ordernumber_global',
'type' => 'checkbox',
'default' => 'no'
),
array(
'title' => __( 'Counter Digits', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'Minimum number of digits for the number', 'woocommerce-advanced-ordernumbers' ),
'desc_tip' => true,
'id' => 'ordernumber_padding',
'type' => 'number',
'default' => '0'
),
array(
'title' => __( 'Counter Start', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'Start value for each new counter', 'woocommerce-advanced-ordernumbers' ),
'desc_tip' => true,
'id' => 'ordernumber_start',
'type' => 'number',
'default' => '1'
),
array(
'title' => __( 'Counter step', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'By how much the counter will be increased after each order. Typically 1.', 'woocommerce-advanced-ordernumbers' ),
'desc_tip' => true,
'id' => 'ordernumber_step',
'type' => 'number',
'default' => '1'
),
array( 'type' => 'sectionend', 'id' => 'ordernumber_options' ),
// TODO: customize order password, and other numbers!
array(
'name' => __( 'Custom Variables', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'Define your own (conditional) variables for use in the number formats', 'woocommerce-advanced-ordernumbers' ),
'type' => 'title',
'id' => 'ordernumber_variables'
),
array(
// 'title' => __( 'Custom ', 'woocommerce-advanced-ordernumbers' ),
// 'desc' => __( '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.', 'woocommerce-advanced-ordernumbers' ),
'id' => 'ordernumber_variables',
'type' => 'ordernumber_variables',
),
array( 'type' => 'sectionend', 'id' => 'ordernumber_variables' ),
array(
'name' => __( 'Current Counters', 'woocommerce-advanced-ordernumbers' ),
'type' => 'title',
'id' => 'ordernumber_counters'
),
array(
'name' => __( 'All Ordernumber Counters', 'woocommerce-advanced-ordernumbers' ),
'desc' => __( 'View and modify the current counter values. The counter value is the value used for the previous number. All changes are immediately applied!', 'woocommerce-advanced-ordernumbers' ),
'desc_tip' => true,
'id' => 'ordernumber_counters',
'type' => 'ordernumber_counters',
),
array( 'type' => 'sectionend', 'id' => 'ordernumber_counters' ),
);
// Default options
add_option ('customize_ordernumber', '0');
add_option ('ordernumber_format', "#");
add_option ('ordernumber_global', '0');
add_option ('ordernumber_padding', '1');
add_option ('ordernumber_start', '1');
add_option ('ordernumber_step', '1');
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( '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_create_ordernumber'), 10, 3);
add_action( 'save_post', array(&$this, 'check_create_ordernumber'), 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'] = __( 'Order Numbers', 'woocommerce-advanced-ordernumbers');
}
}
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', plugins_url('assets/css/ordernumber-counter.css', __FILE__) );
wp_enqueue_style('ordernumber-counter-style');
wp_register_style ( 'ordernumber-variables-style', plugins_url('assets/css/ordernumber-variables.css', __FILE__) );
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', plugins_url( 'assets/js/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'] = __("Error reading response from server:");
$localizations['ORDERNUMBER_JS_NOT_AUTHORIZED'] = __("You are not authorized to modify order number counters.");
$localizations['ORDERNUMBER_JS_NEWCOUNTER'] = __("Please enter the format/name of the new counter:");
$localizations['ORDERNUMBER_JS_ADD_FAILED'] = __("Failed adding counter {0}");
$localizations['ORDERNUMBER_JS_INVALID_COUNTERVALUE'] = __("You entered an invalid value for the counter.\n\n");
$localizations['ORDERNUMBER_JS_EDITCOUNTER'] = __("{0}Please enter the new value for the counter '{1}' (current value: {2}):");
$localizations['ORDERNUMBER_JS_MODIFY_FAILED'] = __("Failed modifying counter {0}");
$localizations['ORDERNUMBER_JS_DELETECOUNTER'] = __("Really delete counter '{0}' with value '{1}'?");
$localizations['ORDERNUMBER_JS_DELETE_FAILED'] = __("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', plugins_url( 'assets/js/ordernumber-variables.js', __FILE__), array('jquery') );
wp_enqueue_script( 'ordernumber-variables-script');
}
/**
* Render the Custom Variables configuration table
*/
public function admin_field_variables($settings) {
$variables = get_option( $settings['id'], array() );
if (!is_array($variables)) {
$variables = array();
}
// echo "
Admin field " . print_r($settings['id'],1) . " has value: " . print_r($variables, 1) . "
';
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,
);
}
}
?>
";
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);
}
/**
* 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_create_ordernumber($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(__('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);
}
/**
* Variable replacements:
* - Random strings/digits
* - Order properties
* - User properties
*/
/* 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);
}
/* 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 replace_fields ($fmt, $type, $order) {
// First, replace all randomXXX[n] fields. This needs to be done with a regexp and a callback:
$fmt = preg_replace_callback ('/\[(random)(.*?)([0-9]*?)\]/', array($this, 'replaceRandom'), $fmt);
$reps = array (
"[year]" => date ("Y"),
"[year2]" => date ("y"),
"[month]" => date("m"),
"[day]" => date("d"),
"[hour]" => date("H"),
"[hour12]" => date("h"),
"[ampm]" => date("a"),
"[minute]" => date("i"),
"[second]" => date("s"),
"[orderid]" => $order->id,
);
$reps["[userid]"] = $order->get_user_id();
$reps["[ipaddress]"] = $order->customer_ip_address;
$reps["[orderstatus]"] = $order->get_status();
$reps["[email]"] = $order->billing_email;
$reps["[firstname]"] = $order->billing_first_name;
$reps["[lastname]"] = $order->billing_last_name;
$reps["[company]"] = $order->billing_company;
$reps["[zip]"] = $order->billing_postcode;
$reps["[postcode]"] = $order->billing_postcode;
$reps["[city]"] = $order->billing_city;
$country = $order->billing_country;
$state = $order->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;
$reps["[articles]"] = $order->get_item_count();
$reps["[currency]"] = $order->get_order_currency();
// $reps["[downloadpermitted]"] = $order->is_download_permitted();
// $reps["[hasdownloads]"] = $order->has_downloadable_item();
// $reps["[coupons]"] = $order->get_used_coupons();
if ($type != 'ordernumber') {
$reps["[ordernumber]"] = $order->get_order_number();
}
$user = $order->get_user();
if ($user) {
// TODO: Shall we supply a variable for the user's login / display name?
// $reps["[user]"] = print_r($user,1);
// if (isset($order->username)) $reps["[username]"] = $order->username;
}
// Allow customization via plugins: filter function($reps, $order, $type, $fmt)
$reps = apply_filters( 'opentools_ordernumber_replacements', $reps, $order, $type, $fmt);
return str_ireplace (array_keys($reps), array_values($reps), $fmt);
}
function create_ordernumber($orderid, $order, $type='ordernumber') {
if (get_option('customize_'.$type, 'false')) {
$fmt = get_option ($type.'_format', "#");
$global = get_option ($type.'_global', 1);
$padding = get_option ($type.'_padding', 1);
$step = get_option ($type.'_step', 1);
$start = get_option ($type.'_start', 1)-$step; // The counter contains the PREVIOUS number!
$nr = $this->replace_fields ($fmt, $type, $order);
// 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 ("|", $nr);
$format = $parts[0];
$counterfmt = ($global==1)?"":$parts[(count($parts)>1)?1:0];
// Look up the current counter
$count = $this->_getCounter($type, $counterfmt, $start) + $step;
$this->_setCounter($type, $counterfmt, $count);
// return the format with the counter inserted
$number = str_replace ("#", sprintf('%0' . $padding . 's', $count), $format);
update_post_meta( $orderid, $this->ordernumber_meta, $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, $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, 'true');
if ($number == $this->ordernumber_new_placeholder && $order->post_status != 'auto-draft') {
$number = $this->create_ordernumber($orderid, $order, 'ordernumber');
// Assign a new number
}
return $number;
}
/**
* The hook to customize order numbers (requests the order number from the database; creates a new ordernumber if no entry exists in the database)
*/
function get_ordernumber($orderid, $order) {
$stored_number = get_post_meta( $orderid, $this->ordernumber_meta, '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);
} 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;
}
}
}
}
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();
}
}