From 4acc904cb025d2d92b894ac6bd23f0b02201df3b Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer <reinhold@kainhofer.com> Date: Fri, 11 Jan 2013 17:47:59 +0100 Subject: [PATCH] Version 1.6.2: Remove callback class and instead directly loop through all replacements Restructure code to get away from the helper class, instead create explicit list of replacements --- Makefile | 2 +- ordernumber.php | 173 ++++++++---------- ordernumber.script.php | 6 +- ordernumber.xml | 4 +- releases/plg_vmshopper_ordernumber_v1.6.2.zip | Bin 0 -> 9979 bytes 5 files changed, 87 insertions(+), 98 deletions(-) create mode 100644 releases/plg_vmshopper_ordernumber_v1.6.2.zip diff --git a/Makefile b/Makefile index 38addfd..c426d94 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BASE=ordernumber PLUGINTYPE=vmshopper -VERSION=1.6.1 +VERSION=1.6.2 PLUGINFILES=$(BASE).php $(BASE).script.php $(BASE).xml index.html # TRANSDIR=../../../administrator/language/ diff --git a/ordernumber.php b/ordernumber.php index ea08c77..a5b51ee 100644 --- a/ordernumber.php +++ b/ordernumber.php @@ -10,75 +10,6 @@ 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) { @@ -104,29 +35,85 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { // We don't need this function, but the parent class declares it abstract, so we need to overload function plgVmOnUpdateOrderBEShopper($_orderID) {} + + /* 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 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 + // 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"), + "[userid]" => $details->virtuemart_user_id, + "[vendorid]" => $details->virtuemart_vendor_id ); -// // 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); + if ($nrtype==0 or $nrtype == 1) { + $reps["[ipaddress]"] = $details->ip_address; + } + if ($nrtype==1) { + // Only for Invoice: + $reps["[ordernumber]"] = $details->order_number; + $reps["[orderid]"] = $details->virtuemart_order_id; + $reps["[lastname]"] = $details->last_name; + $reps["[firstname]"] = $details->first_name; + $reps["[company]"] = $details->company; + $reps["[city]"] = $details->city; + $reps["[zip]"] = $details->zip; + $reps["[orderstatus]"] = $details->order_status; + } + if ($nrtype==2) { + // Customer number: + print_r($details); + $reps["[username]"] = $details->username; + $reps["[name]"] = $details->name; + $reps["[email]"] = $details->email; + $reps["[company]"] = $details->company; + $reps["[title]"] = $details->title; + $reps["[firstname]"] = $details->first_name; + $reps["[middlename]"] = $details->middle_name; + $reps["[lastname]"] = $details->last_name; + $reps["[zip]"] = $details->zip; + $reps["[city]"] = $details->city; + $reps["[countryid]"] = $details->virtuemart_country_id; + $reps["[stateid]"] = $details->virtuemart_state_id; + $reps["[user_is_vendor]"] = $details->user_is_vendor; + } + return str_ireplace (array_keys($reps), array_values($reps), $fmt); } /* Type 0 means order number, type 1 means invoice number, type 2 means customer number */ @@ -154,7 +141,6 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { $db->query(); // return the format with the counter inserted - return str_replace ("#", sprintf('%0' . $padding . 's', $count), $nr); } @@ -162,7 +148,7 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { function plgVmOnUserOrder(&$orderDetails/*,&$data*/) { // Is order number customization enabled? if ($this->params->get('customize_order_number')) { - $nrtype=0; /*order-nr*/ + $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); @@ -171,8 +157,9 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { } // Is order password customization enabled? if ($this->params->get('customize_order_password')) { + $nrtype = 3; /* order password */ $fmt = $this->params->get ('order_password_format', "[randomHex8]"); - $passwd = $this->replace_fields ($fmt, 3, $orderDetails); + $passwd = $this->replace_fields ($fmt, $nrtype, $orderDetails); $orderDetails->order_pass = $passwd; } } @@ -186,7 +173,7 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { $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*/ + $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); @@ -202,7 +189,7 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { 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*/ + $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); diff --git a/ordernumber.script.php b/ordernumber.script.php index d7146b7..cbbd586 100644 --- a/ordernumber.script.php +++ b/ordernumber.script.php @@ -57,8 +57,10 @@ class plgVmShopperOrdernumberInstallerScript public function update(JAdapterInstance $adapter) { jimport( 'joomla.filesystem.file' ); - JFile::delete( JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "en-GB" . DS . "en-GB.plg_vmshopper_ordernumber.sys.ini"); - JFile::delete( JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "de-DE" . DS . "de-DE.plg_vmshopper_ordernumber.sys.ini"); + $file = JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "en-GB" . DS . "en-GB.plg_vmshopper_ordernumber.sys.ini"; + if (JFile::exists($file)) JFile::delete($file); + $file = JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "de-DE" . DS . "de-DE.plg_vmshopper_ordernumber.sys.ini"; + if (JFile::exists($file)) JFile::delete($file); return true; } diff --git a/ordernumber.xml b/ordernumber.xml index ca874ed..2f6128a 100644 --- a/ordernumber.xml +++ b/ordernumber.xml @@ -7,8 +7,8 @@ <authorUrl>http://www.kainhofer.com/</authorUrl> <copyright>Copyright (C) 2012-2013 Reinhold Kainhofer. All rights reserved.</copyright> <license>http://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3</license> - <version>1.6.1</version> - <releaseDate>2013-01-09</releaseDate> + <version>1.6.2</version> + <releaseDate>2013-01-11</releaseDate> <releaseType>Minor update</releaseType> <downloadUrl>http://www.kainhofer.com/virtuemart-2-extensions/vm2-ordernumber-plugin.html</downloadUrl> diff --git a/releases/plg_vmshopper_ordernumber_v1.6.2.zip b/releases/plg_vmshopper_ordernumber_v1.6.2.zip new file mode 100644 index 0000000000000000000000000000000000000000..6669f863eab81c38ad3d053fad0fff03622d5cce GIT binary patch literal 9979 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 literal 0 HcmV?d00001 -- GitLab