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' => __( 'Global', 'woocommerce-advanced-ordernumbers' ), 'desc' => __( 'Counter Scope', 'woocommerce-advanced-ordernumbers' ), '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' ), 'desc' => __( 'View and modify the current counter values.', 'woocommerce-advanced-ordernumbers' ), 'type' => 'title', 'id' => 'ordernumber_counters' ), array( '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_counters', '1'); // 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_variables', array( $this, 'admin_field_variables' ) ); add_action( 'woocommerce_admin_field_ordernumber_counters', array( $this, 'admin_field_counters' ) ); 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'); } /** * 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 ); } /** * Render the Custom Variables configuration table */ public function admin_field_variables($settings) { echo "

TODO!

"; } /** * Render the Counter Values modification table */ public function admin_field_counters ($settings) { // First, get all counter names: // print "
All options: ".print_r(wp_load_alloptions(),1)."
"; $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, ); } } ?> ' class='wc-ordernumber-loading' style="display: none; position: absolute; top: 2px; left: 0px; z-index: 9999;"/> __( '', 'woocommerce-advanced-ordernumbers' ), 'name' => __( 'Counter name', 'woocommerce-advanced-ordernumbers' ), 'value' => __( 'Counter value', 'woocommerce-advanced-ordernumbers' ), 'settings' => '' ) ); ?> $column ) { echo ''; } ?> $column) { echo ''; } ?> '>
' . esc_html( $column ) . '
' class='ordernumber-counter-editbtn ordernumber-btn' onClick='ajaxEditCounter(this, , , )' />
' class='ordernumber-counter-deletebtn ordernumber-btn' onClick='ajaxDeleteCounter(this, , , )' />
' class='ordernumber-counter-addbtn' />
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=1) { $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); } /** * 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(); } }