<?php /** * @package VirtueMart 2 OrderNumber plugin for Joomla! 2.5 * @version $Id: mod_XYZ.php 599 2010-03-20 23:26:33Z you $ * @author Reinhold Kainhofer, reinhold@kainhofer.com * @copyright (C) 2012 - Reinhold Kainhofer * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html **/ defined('_JEXEC') or die( 'Direct Access to ' . basename( __FILE__ ) . ' is not allowed.' ) ; if (!class_exists('vmShopperPlugin')) require(JPATH_VM_PLUGINS . DS . 'vmshopperplugin.php'); /* For php 5.2 we cannot use lambda functions or any other way to pass local variables to the callback. The only way to pass a local variable to a callback for preg_replace_callback is to create a class instance... */ class ReplacementCallback { private $details; private $nrtype; function __construct($nrtype, $details) { $this->details = $details; $this->nrtype = $nrtype; } /* 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 replace ($match) { $varname = strtolower($match[1]); switch ($varname) { case "year": return date ("Y"); case "year2": return date ("y"); case "month": return date("m"); case "day": return date("d"); case "hour": return date("H"); case "hour12": return date("h"); case "ampm": return date("a"); case "minute": return date("i"); case "second": return date("s"); case "random": /* the regexp matches (random)(Type)(Len) as match 1 to 3, 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); case "vendorid": return ($this->details->virtuemart_vendor_id); case "userid": return ($this->details->virtuemart_user_id); case "ipaddress": return ($this->details->ip_address); // Only for Invoice: case "ordernumber": return ($this->details->order_number); case "orderid": return ($this->details->virtuemart_order_id); case "lastname": return ($this->details->last_name); case "firstname": return ($this->details->first_name); case "company": return ($this->details->company); case "city": return ($this->details->city); case "zip": return ($this->details->zip); case "orderstatus": return ($this->details->order_status); } // No variable type matched, so don't replace, return the original string return $match[0]; } } class plgVmShopperOrdernumber extends vmShopperPlugin { function __construct(& $subject, $config) { parent::__construct($subject, $config); /* Create the database table */ $this->tableFields = array_keys ($this->getTableSQLFields ()); $this->onStoreInstallPluginTable($this->_psType); } public function getVmPluginCreateTableSQL () { return $this->createTableSQL ('VM Shopper plugin: custom order and invoice numbers'); } function getTableSQLFields () { $SQLfields = array( 'id' => 'int(11) UNSIGNED NOT NULL AUTO_INCREMENT', 'number_type' => 'int(1) UNSIGNED', 'number_format' => 'varchar(255)', 'count' => 'int(1)', ); return $SQLfields; } // We don't need this function, but the parent class declares it abstract, so we need to overload function plgVmOnUpdateOrderBEShopper($_orderID) {} /* Replace the format variables, match[1] is the variable name, match[2] and match[3] are only used for random fields */ /* Type 0 means order number, type 1 means invoice number, type 2 means customer number, 3 means order password */ function replace_fields ($fmt, $nrtype, $details) { // Match variables for the form random[type][count] and everything else. // This makes it possible to handle "random" just like any other type! $patterns = array ( '/\[(random)(.*?)([0-9]*?)\]/', // For randomTypeN, spit the three parts '/\[([^\]]+)\]/' // Everything else matches whole variable name ); // // TODO: in php 5.3 and 5.4 we can easier pass $orderDetails to the callback // function my_callback ($match) { // return self::replacementCallback ($match, $details); // }; // return preg_replace_callback ($patterns, // function ($match) use ($details) { // return self::replacementCallback ($match, $details); // }, $fmt); /* php 5.2 does not allow lambda functions and there is no other way to pass a local variable to the callback than a class instance! */ $callback = new ReplacementCallback ($nrtype, $details); return preg_replace_callback ($patterns, array($callback, 'replace'), $fmt); } /* Type 0 means order number, type 1 means invoice number, type 2 means customer number */ function format_number ($fmt, $details, $nrtype = 0, $global = 1, $padding = 1) { // First, replace all variables: $nr = $this->replace_fields ($fmt, $nrtype, $details); // Look up the current counter $db = JFactory::getDBO(); /* prevent sql injection attacks by escaping the user-entered format! */ $nr_escaped = $db->getEscaped ($nr); /* For global counting, simply read the empty number_format entries! */ $q = 'SELECT `count` FROM `'.$this->_tablename.'` WHERE `number_type`='.(int)$nrtype.' AND `number_format`="'.($global?"":$nr_escaped).'"'; $db->setQuery($q); $existing = $db->loadResult(); $count = $existing?($existing+1):1; // Insert new counter value into the db if ($existing) { $q = 'UPDATE `'.$this->_tablename.'` SET `count`= "'.$count.'" WHERE `number_type`='.(int)$nrtype.' AND `number_format`="'.($global?"":$nr_escaped).'"'; } else { $q = 'INSERT INTO `'.$this->_tablename.'` (`count`, `number_type`, `number_format`) VALUES ('.(int)$count.','.(int)$nrtype.', "'.($global?"":$nr_escaped).'")'; } $db->setQuery( $q ); $db->query(); // return the format with the counter inserted return str_replace ("#", sprintf('%0' . $padding . 's', $count), $nr); } function plgVmOnUserOrder(&$orderDetails/*,&$data*/) { // Is order number customization enabled? if ($this->params->get('customize_order_number')) { $nrtype=0; /*order-nr*/ $fmt = $this->params->get ('order_number_format', "#"); $global = $this->params->get ('order_number_global', 1); $padding = $this->params->get ('order_number_padding', 1); $ordernr = $this->format_number ($fmt, $orderDetails, $nrtype, $global, $padding); $orderDetails->order_number = $ordernr; } // Is order password customization enabled? if ($this->params->get('customize_order_password')) { $fmt = $this->params->get ('order_password_format', "[randomHex8]"); $passwd = $this->replace_fields ($fmt, 3, $orderDetails); $orderDetails->order_pass = $passwd; } } function plgVmOnUserInvoice($orderDetails,&$data) { // Is order number customization enabled? if ($this->params->get('customize_invoice_number')) { // check the default configuration $orderstatusForInvoice = VmConfig::get('inv_os',array()); if(!is_array($orderstatusForInvoice)) $orderstatusForInvoice = array($orderstatusForInvoice); //for backward compatibility 2.0.8e $pdfInvoice = (int)VmConfig::get('pdf_invoice', 0); // backwards compatible $force_create_invoice=JRequest::getInt('create_invoice', 0); if ( in_array($orderDetails['order_status'],$orderstatusForInvoice) or $pdfInvoice==1 or $force_create_invoice==1 ){ $nrtype=1; /*invoice-nr*/ $fmt = $this->params->get ('invoice_number_format', "#"); $global = $this->params->get ('invoice_number_global', 1); $padding = $this->params->get ('invoice_number_padding', 1); $invoicenr = $this->format_number ($fmt, (object)$orderDetails, $nrtype, $global, $padding); $data['invoice_number'] = $invoicenr; return $data; } } } // Customizing the customer numbers requires VM >= 2.0.15b, earlier versions // left out the & and thus didn't allow changing the user data function plgVmOnUserStore(&$data) { // Is order number customization enabled? if ($this->params->get('customize_customer_number') && isset($data['customer_number_bycore']) && $data['customer_number_bycore']==1) { $nrtype=2; /*customer-nr*/ $fmt = $this->params->get ('customer_number_format', "#"); $global = $this->params->get ('customer_number_global', 1); $padding = $this->params->get ('customer_number_padding', 1); $customernr = $this->format_number ($fmt, (object)$data, $nrtype, $global, $padding); $data['customer_number'] = $customernr; return $data; } } }