From c103f51c3ec9c4469786b0c1ab9147c1b868791b Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer <reinhold@kainhofer.com> Date: Tue, 21 Apr 2015 19:41:21 +0200 Subject: [PATCH] Properly implement custom variable replacements --- Makefile | 2 +- fields/vmordernumberreplacements.php | 191 ++++++++ .../en-GB/en-GB.plg_vmshopper_ordernumber.ini | 3 + .../en-GB.plg_vmshopper_ordernumber.sys.ini | 3 + ordernumber.php | 463 ++++++++++++++---- ordernumber.script.php | 15 + ordernumber.xml | 15 +- ordernumber/assets/css/ordernumber.css | 65 +++ ordernumber/assets/js/ordernumber.js | 48 ++ 9 files changed, 693 insertions(+), 112 deletions(-) create mode 100644 fields/vmordernumberreplacements.php diff --git a/Makefile b/Makefile index 43021a8..c2839b0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BASE=ordernumber PLUGINTYPE=vmshopper -VERSION=2.2 +VERSION=3.0 PLUGINFILES=$(BASE).php $(BASE).script.php $(BASE).xml index.html $(BASE)/ diff --git a/fields/vmordernumberreplacements.php b/fields/vmordernumberreplacements.php new file mode 100644 index 0000000..b1506cd --- /dev/null +++ b/fields/vmordernumberreplacements.php @@ -0,0 +1,191 @@ +<?php +defined('_JEXEC') or die(); +/** + * + * @package VirtueMart + * @subpackage Plugins - Fields + * @author Reinhold Kainhofer, Open Tools + * @link http://www.open-tools.net + * @copyright Copyright (c) 2014 Reinhold Kainhofer. All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.txt + * VirtueMart is free software. This version may have been modified pursuant + * to the GNU General Public License, and as distributed it includes or + * is derivative of works licensed under the GNU General Public License or + * other free or open source software licenses. + */ + +defined('DS') or define('DS', DIRECTORY_SEPARATOR); +if (!class_exists( 'VmConfig' )) + require(JPATH_ROOT.DS.'administrator'.DS.'components'.DS.'com_virtuemart'.DS.'helpers'.DS.'config.php'); +VmConfig::loadConfig(); + +class JFormFieldVmOrdernumberReplacements extends JFormField { + var $_name = 'vmOrdernumberReplacements'; + static $pluginpath = '/plugins/vmshopper/ordernumber/ordernumber/'; + + + protected $countertype; + + // VM2 on J2 works, VM3 on J3 works out of the box, but + // VM3 on J2 does NOT work by simply calling vmJsApi::jQuery, because + // the JS is never added to the page header, so we have to add this manually + protected function loadjQuery() { + vmJsApi::jQuery(); + // TODO: jquery::ui available only in J3: + JHtml::_('jquery.ui', array('core', 'sortable')); + // If we are on Joomla 2.5 and VM 3, manually add the script declarations + // cached in vmJsApi to the document header: + if (version_compare(JVERSION, '3.0', 'lt') && defined('VM_VERSION') && VM_VERSION>=3) { + $document = JFactory::getDocument(); + $scripts = vmJsApi::getJScripts(); + foreach ($scripts as $name => $jsToAdd) { + if($jsToAdd['written']) continue; + $file = $jsToAdd['script'] ? $jsToAdd['script'] : $name; + + if(strpos($file,'/')!==0){ + $file = vmJsApi::setPath($file,false,''); + } else if(strpos($file,'//')!==0){ + $file = JURI::root(true).$file; + } + + $ver = ''; + if(!empty($jsToAdd['ver'])) $ver = '?vmver='.$jsToAdd['ver']; + $document->addScript( $file .$ver,"text/javascript",$jsToAdd['defer'],$jsToAdd['async'] ); + vmJsApi::removeJScript($name); + } + } + } + + static function img_url($file) { + return JURI::root(true) . self::$pluginpath . 'assets/images/' . $file; + } + static function css_url($file) { + return JURI::root(true) . self::$pluginpath . 'assets/css/' . $file; + } + static function js_url($file) { + return JURI::root(true) . self::$pluginpath . 'assets/js/' . $file; + } + static function __($string) { + return JText::_($string); + } + + protected function makeJSTranslationsAvailable() { +// JText::script('PLG_ORDERNUMBER_JS_JSONERROR'); + } + protected function getInput() { + + $html=array(); + $doc = JFactory::getDocument()->addStyleSheet(self::css_url('ordernumber.css')); + $doc->addScript( self::js_url('ordernumber.js')); + $this->makeJSTranslationsAvailable(); + $this->loadjQuery(); + + + $value = $this->value; +// $html[] = "<pre> value: ".print_r($value,1)."</pre>"; +// $html[] = "<pre>Form Field: ".print_r($this,1)."</pre>"; + $variables = array(); + if (!is_array($value)) + $value = array(); + + if (!empty($value)) { + $keys = array_keys($value); + foreach (array_keys($value[$keys[0]]) as $i) { + $entry = array(); + foreach ($keys as $k) { + $entry[$k] = $value[$k][$i]; + } + $variables[] = $entry; + } + } + + $id = $this->id; + $name = $this->name; + + +// $html[] = "<pre>Variables: ".print_r($variables,1)."</pre>"; + $html[] = '<table id="ordernumber_variables_template" style="display:none">'; + $html[] = $this->create_replacements_row_html($name, array(), 'disabled'); + $html[] = '</table>'; + + $html[] = '<table id="ordernumber_variables" class="ordernumber_variables widefat wc_input_table sortable" cellspacing="0">'; + $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' => '', + ); + $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>a' . self::__('No custom variables have been defined.') . '</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->create_replacements_row_html($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="' . self::img_url( 'icon-16-new.png' ) . '" class="ordernumber-counter-addbtn" /></div>'; + $html[] = self::__('Add new custom variable'); + $html[] = ' </div>'; + $html[] = ' </td>'; + $html[] = ' </tr>'; + $html[] = ' </tfoot>'; + $html[] = '</table>'; + return implode("\n", $html); + } + + protected function create_replacements_row_html($name, $values = array(), $disabled = '') { + $operator = (isset($values['conditionop'])?$values['conditionop']:''); + $operators = array( + 'equals' => '=', + 'contains' => self::__('contains'), + 'smaller' => '<', + 'smallerequal' => '<=', + 'larger' => '>', + 'largerequal' => '>=', + 'startswith' => self::__('starts with'), + 'endswith' => self::__('ends with'), + ); + $html = ' + <tr> + <td class="variables_ifvar"><input name="' . $name . '[conditionvar][]" value="' . (isset($values['conditionvar'])?$values['conditionvar']:'') . '" ' . $disabled . '/></td> + <td class="variables_ifop" ><select name="' . $name . '[conditionop][]" ' . $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']:'') . '" ' . $disabled . '/></td> + <td class="variables_then">=></td> + <td class="variables_thenvar"><input name="' . $name . '[newvar][]" value="' . (isset($values['newvar'])?$values['newvar']:'') . '" ' . $disabled . '/></td> + <td class="variables_thenval"><input name="' . $name . '[newval][]" value="' . (isset($values['newval'])?$values['newval']:'') . '" ' . $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; + } + + +} \ No newline at end of file diff --git a/language/en-GB/en-GB.plg_vmshopper_ordernumber.ini b/language/en-GB/en-GB.plg_vmshopper_ordernumber.ini index 27b1e03..17d801d 100644 --- a/language/en-GB/en-GB.plg_vmshopper_ordernumber.ini +++ b/language/en-GB/en-GB.plg_vmshopper_ordernumber.ini @@ -52,9 +52,12 @@ PLG_ORDERNUMBER_COUNTERLIST_HEADER_VALUE="Counter value" PLG_ORDERNUMBER_COUNTERLIST_ADD="Add new counter" PLG_ORDERNUMBER_COUNTERLIST_EXISTS="Counter '%s' already exists." +PLG_ORDERNUMBER_REPLACEMENTS_DESC="Here you can define contingent custom variables, which you can then use in the number format like any pre-defined variable." + PLG_ORDERNUMBER_FIELDSET_ORDERNUMBER="Order Numbers" PLG_ORDERNUMBER_FIELDSET_INVOICENUMBER="Invoice Numbers" PLG_ORDERNUMBER_FIELDSET_CUSTOMERNUMBER="Customer Numbers" +PLG_ORDERNUMBER_FIELDSET_REPLACEMENTS="Custom Variables" PLG_ORDERNUMBER_JS_NOT_AUTHORIZED="You are not authorized to modify order number counters." PLG_ORDERNUMBER_JS_NEWCOUNTER="Please enter the format/name of the new counter:" diff --git a/language/en-GB/en-GB.plg_vmshopper_ordernumber.sys.ini b/language/en-GB/en-GB.plg_vmshopper_ordernumber.sys.ini index 27b1e03..17d801d 100644 --- a/language/en-GB/en-GB.plg_vmshopper_ordernumber.sys.ini +++ b/language/en-GB/en-GB.plg_vmshopper_ordernumber.sys.ini @@ -52,9 +52,12 @@ PLG_ORDERNUMBER_COUNTERLIST_HEADER_VALUE="Counter value" PLG_ORDERNUMBER_COUNTERLIST_ADD="Add new counter" PLG_ORDERNUMBER_COUNTERLIST_EXISTS="Counter '%s' already exists." +PLG_ORDERNUMBER_REPLACEMENTS_DESC="Here you can define contingent custom variables, which you can then use in the number format like any pre-defined variable." + PLG_ORDERNUMBER_FIELDSET_ORDERNUMBER="Order Numbers" PLG_ORDERNUMBER_FIELDSET_INVOICENUMBER="Invoice Numbers" PLG_ORDERNUMBER_FIELDSET_CUSTOMERNUMBER="Customer Numbers" +PLG_ORDERNUMBER_FIELDSET_REPLACEMENTS="Custom Variables" PLG_ORDERNUMBER_JS_NOT_AUTHORIZED="You are not authorized to modify order number counters." PLG_ORDERNUMBER_JS_NEWCOUNTER="Please enter the format/name of the new counter:" diff --git a/ordernumber.php b/ordernumber.php index 5ce7998..9b6facd 100644 --- a/ordernumber.php +++ b/ordernumber.php @@ -38,20 +38,21 @@ 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) {} - function _getCounter($nrtype, $format) { + function _getCounter($nrtype, $format, $default=0) { $db = JFactory::getDBO(); + /* prevent sql injection attacks by escaping the user-entered format! Empty for global counter... */ /* For global counting, simply read the empty number_format entries! */ - $q = 'SELECT `count` FROM `'.$this->_tablename.'` WHERE `number_type`='.(int)$nrtype.' AND `number_format`='.$db->quote($format); + $q = 'SELECT `count` FROM `'.$this->_tablename.'` WHERE `number_type`='.$db->quote($nrtype).' AND `number_format`='.$db->quote($format); $db->setQuery($q); $existing = $db->loadResult(); - $count = $existing?$existing:0; + $count = $existing?$existing:$default; return $count; } function _counterExists($nrtype, $format) { $db = JFactory::getDBO(); - $q = 'SELECT `count` FROM `'.$this->_tablename.'` WHERE `number_type`='.(int)$nrtype.' AND `number_format`='.$db->quote($format); + $q = 'SELECT `count` FROM `'.$this->_tablename.'` WHERE `number_type`='.$db->quote($nrtype).' AND `number_format`='.$db->quote($format); $db->setQuery($q); return ($db->loadResult() != null); } @@ -59,7 +60,7 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { // Insert new counter value into the db function _addCounter($nrtype, $format, $value) { $db = JFactory::getDBO(); - $q = 'INSERT INTO `'.$this->_tablename.'` (`count`, `number_type`, `number_format`) VALUES ('.(int)$value.','.(int)$nrtype.', '.$db->quote($format).')'; + $q = 'INSERT INTO `'.$this->_tablename.'` (`count`, `number_type`, `number_format`) VALUES ('.(int)$value.','.$db->quote($nrtype).', '.$db->quote($format).')'; $db->setQuery( $q ); $db->query(); return $db->getAffectedRows(); @@ -68,7 +69,7 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { // Insert new counter value into the db or update existing one function _setCounter($nrtype, $format, $value) { $db = JFactory::getDBO(); - $q = 'UPDATE `'.$this->_tablename.'` SET `count`= "'.(int)$value.'" WHERE `number_type`='.(int)$nrtype.' AND `number_format`='.$db->quote($format); + $q = 'UPDATE `'.$this->_tablename.'` SET `count`= "'.(int)$value.'" WHERE `number_type`='.$db->quote($nrtype).' AND `number_format`='.$db->quote($format); $db->setQuery( $q ); $db->query(); if ($db->getAffectedRows()<1) { @@ -82,7 +83,7 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { function _deleteCounter($nrtype, $format) { $db = JFactory::getDBO(); $format = $db->escape ($format); - $q = 'DELETE FROM `'.$this->_tablename.'` WHERE `number_type`='.(int)$nrtype.' AND `number_format`='.$db->quote($format); + $q = 'DELETE FROM `'.$this->_tablename.'` WHERE `number_type`='.$db->quote($nrtype).' AND `number_format`='.$db->quote($format); $db->setQuery( $q ); $db->query(); return $db->getAffectedRows(); @@ -125,106 +126,361 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { - /* 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) { - // 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 - ); - if (isset($details->virtuemart_vendor_id)) $reps["[vendorid]"] = $details->virtuemart_vendor_id; + 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 setupStoreReplacements (&$reps, $details, $nrtype) { + if (isset($details->virtuemart_vendor_id)) + $reps["[vendorid]"] = $details->virtuemart_vendor_id; + } + + protected function setupAddressReplacements(&$reps, $prefix, $details, $nrtype) { + if (isset($details->email)) $reps["[email]"] = $details->email; + if (isset($details->title)) $reps["[title]"] = $details->title; + if (isset($details->first_name)) $reps["[firstname]"] = $details->first_name; + if (isset($details->middle_name)) $reps["[middlename]"] = $details->middle_name; + if (isset($details->last_name)) $reps["[lastname]"] = $details->last_name; + + if (isset($details->company)) $reps["[company]"] = $details->company; + if (isset($details->zip)) { + $reps["[zip]"] = $details->zip; + $reps["[postcode]"] = $details->zip; + } + if (isset($details->city)) $reps["[city]"] = $details->city; - if ($nrtype==0 or $nrtype == 1) { // Order nr and Invoice nr - if (isset($details->ip_address)) $reps["[ipaddress]"] = $details->ip_address; + if (isset($details->virtuemart_country_id)) { + $reps["[countryid]"] = $details->virtuemart_country_id; + $country = $this->getCountryFromID ($details->virtuemart_country_id); + if ($country) { + $reps["[country]"] = $country->country_name; + $reps["[countrycode2]"] = $country->country_2_code; + $reps["[countrycode3]"] = $country->country_3_code; + } } - if ($nrtype==1 or $nrtype==2) { // Invoice nr and Customer nr - if (isset($details->email)) $reps["[email]"] = $details->email; - if (isset($details->title)) $reps["[title]"] = $details->title; - if (isset($details->first_name)) $reps["[firstname]"] = $details->first_name; - if (isset($details->middle_name)) $reps["[middlename]"] = $details->middle_name; - if (isset($details->last_name)) $reps["[lastname]"] = $details->last_name; - if (isset($details->company)) $reps["[company]"] = $details->company; - if (isset($details->zip)) $reps["[zip]"] = $details->zip; - if (isset($details->city)) $reps["[city]"] = $details->city; + if (isset($details->virtuemart_state_id)) { + $reps["[stateid]"] = $details->virtuemart_state_id; + // TODO: Also extract the state name and abbreviations + } + } + + protected function setupOrderReplacements (&$reps, $details, $nrtype) { + // Customer numbers are created before any order is submitted, so we don't have any information available. + if ($nrtype=='customer_number') + return; + // Only for Invoice: + if ($nrtype != 'order_number' && isset($details->order_number)) + $reps["[ordernumber]"] = $details->order_number; + if (isset($details->virtuemart_order_id)) + $reps["[orderid]"] = $details->virtuemart_order_id; + if (isset($details->order_status)) + $reps["[orderstatus]"] = $details->order_status; + - if (isset($details->virtuemart_country_id)) $reps["[countryid]"] = $details->virtuemart_country_id; - if (isset($details->virtuemart_country_id)) { - $country = $this->getCountryFromID ($details->virtuemart_country_id); - if ($country) { - $reps["[country]"] = $country->country_name; - $reps["[countrycode2]"] = $country->country_2_code; - $reps["[countrycode3]"] = $country->country_3_code; + $this->setupAddressReplacements($reps, "", $details, $nrtype); + +// print("<pre>details for $nrtype: ".print_r($details,1)."</pre>"); + if (isset($details->order_total)) $reps['[ordertotal]'] = $details->order_total; + if (isset($details->order_total)) $reps['[amount]'] = $details->order_total; + if (isset($details->order_subtotal)) $reps['[ordersubtotal]'] = $details->order_total; + if (isset($details->order_tax)) $reps['[ordersubtotal]'] = $details->order_tax; + if (isset($details->order_shipment)) $reps['[ordershipment]'] = $details->order_shipment; + if (isset($details->order_payment)) $reps['[orderpayment]'] = $details->order_payment; + if (isset($details->order_discount)) $reps['[orderdiscount]'] = $details->order_discount; + + $articles = 0; + $products = 0; + $skus = array(); + $categories = array(); + $menufacturers = array(); + $vendors = array(); + + // If we have a virtuemart_order_id already, load that order, + // otherwise assume the order is still in the cart + $items = array(); + if (isset($details->virtuemart_order_id)) { + $orderModel = VmModel::getModel('orders'); + $productModel = VmModel::getModel('product'); + $order = (object)$orderModel->getOrder($details->virtuemart_order_id); +// print("<pre>Order is: ".print_r($order,1)."</pre>"); + foreach ($order->items as $i) { + $articles += $i->product_quantity; + $products += 1; + + $p = $productModel->getProduct($i->virtuemart_product_id); +// print("<pre>Order product is: ".print_r($p,1)."</pre>"); + $skus[$p->product_sku] = 1; + foreach ($p->categories as $c) { + $categories[$c] = 1; + } + foreach ($p->virtuemart_manufacturer_id as $m) { + $manufacturers[$m] = 1; + } + $vendors[$p->virtuemart_vendor_id] = 1; + } + } else { + $cart = VirtueMartCart::getCart(); +// print("<pre>Cart is: ".print_r($cart,1)."</pre>"); + foreach ($cart->products as $p) { + $articles += $p->quantity; + $products += 1; + $skus[$p->product_sku] = 1; + foreach ($p->categories as $c) { + $categories[$c] = 1; + } + foreach ($p->virtuemart_manufacturer_id as $m) { + $manufacturers[$m] = 1; + } + $vendors[$p->virtuemart_vendor_id] = 1; + } + } + $reps["[articles]"] = $articles; + $reps["[products]"] = $products; + $reps["[skus]"] = array_keys($skus); + $reps["[categories]"] = array_keys($categories); + $reps["[manufacturers]"] = array_keys($menufacturers); + $reps["[vendors]"] = array_keys($vendors); + + } + + + protected function setupUserReplacements (&$reps, $details, $nrtype) { + $reps["[userid]"] = $details->virtuemart_user_id; + if (isset($details->ip_address)) $reps["[ipaddress]"] = $details->ip_address; + // Customer number: + if (isset($details->username)) $reps["[username]"] = $details->username; + if (isset($details->name)) $reps["[name]"] = $details->name; + if (isset($details->user_is_vendor)) $reps["[user_is_vendor]"] = $details->user_is_vendor; + } + + protected function setupShippingReplacements(&$reps, $order, $nrtype) { + if (isset($details->virtuemart_paymentmethod_id)) $reps['[paymentmethod]'] = $details->virtuemart_paymentmethod_id; + if (isset($details->virtuemart_shipmentmethod_id)) $reps['[shipmentmethod]'] = $details->virtuemart_shipmentmethod_id; +// $reps["[shippingmethod]"] = $order->getShippingMethod(); + } + + protected function setupThirdPartyReplacements (&$reps, $details, $nrtype) { + JPluginHelper::importPlugin('vmshopper'); + JDispatcher::getInstance()->trigger('onVmOrdernumberGetVariables',array(&$reps, $nrtype, $details)); + } + + 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); +// print("<pre>All replacements: ".print_r($reps,1)."</pre>"); + return $reps; + } + + protected function setupCustomVariables ($nrtype, $order, $reps, $customvars) { +print("<pre>Custom Variables: ".print_r($customvars,1)."</pre>"); + 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']; } - - if (isset($details->virtuemart_state_id)) $reps["[stateid]"] = $details->virtuemart_state_id; - } - if ($nrtype==1) { - // Only for Invoice: - if (isset($details->order_number)) $reps["[ordernumber]"] = $details->order_number; - if (isset($details->virtuemart_order_id)) $reps["[orderid]"] = $details->virtuemart_order_id; - if (isset($details->order_status)) $reps["[orderstatus]"] = $details->order_status; } - if ($nrtype==2) { - // Customer number: - if (isset($details->username)) $reps["[username]"] = $details->username; - if (isset($details->name)) $reps["[name]"] = $details->name; - if (isset($details->user_is_vendor)) $reps["[user_is_vendor]"] = $details->user_is_vendor; + 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; } - JPluginHelper::importPlugin('vmshopper'); - JDispatcher::getInstance()->trigger('onVmOrdernumberGetVariables',array(&$reps, $fmt, $nrtype, $details)); + } + + 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; + } - /* 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); + /* replace the variables in the given format. $type indicates the type of number. */ + 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); - // 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]; + // 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; + } + + function assignNumber($order, $type='ordernumber', $default="#") { + if ($this->params->get('customize_'.$type, 0)) { + $fmt = $this->params->get ($type.'_format', $default); + $ctrsettings = array( + "${type}_format" => '', + "${type}_counter" => '', + "${type}_global" => $this->params->get ($type.'_global', 0), + "${type}_padding" => $this->params->get ($type.'_padding', 0), + "${type}_step" => 1, + "${type}_start" => 1, + ); + $cvar = $this->params->get ('replacements', array()); + // Even though the replacements are created and stored as an array, they are retrieved as a stdClass object: + 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(); - $counterfmt = ($global==1)?"":$parts[(count($parts)>1)?1:0]; + 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; + } + } - // Look up the current counter - $count = $this->_getCounter($nrtype, $counterfmt) + 1; - $this->_setCounter($nrtype, $counterfmt, $count); - // return the format with the counter inserted - return str_replace ("#", sprintf('%0' . $padding . 's', $count), $format); + $number = $this->createNumber ($fmt, $type, $order, $customvars, $ctrsettings); + return $number; + } else { + return false; + } } - 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); + $ordernumber = $this->assignNumber($orderDetails, 'order_number', "#"); + if ($ordernumber !== false) { // TODO: Check if ordernr already exists - $orderDetails->order_number = $ordernr; + $orderDetails->order_number = $ordernumber; } - // 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, $nrtype, $orderDetails); - $orderDetails->order_pass = $passwd; + $orderpwd = $this->assignNumber($orderDetails, 'order_password', "[randomHex8]"); + if ($orderpwd !== false) { + $orderDetails->order_pass = $orderpwd; } } @@ -237,14 +493,12 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { $pdfInvoice = (int)VmConfig::get('pdf_invoice', 0); // backwards compatible $force_create_invoice = JFactory::getApplication()->input->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); - // TODO: Check if ordernr already exists - $data['invoice_number'] = $invoicenr; - return $data; + $invoicenr = $this->assignNumber((object)$orderDetails, 'invoice_number', "#"); + if ($invoicenr !== false) { + // TODO: Check if ordernr already exists + $data['invoice_number'] = $invoicenr; + return $data; + } } } } @@ -252,19 +506,16 @@ class plgVmShopperOrdernumber extends vmShopperPlugin { // 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); - // TODO: Check if ordernr already exists - $data['customer_number'] = $customernr; - return $data; + if (isset($data['customer_number_bycore']) && $data['customer_number_bycore']==1) { + $customernr = $this->assignNumber((object)$data, 'customer_number', "#"); + if ($customernr !== false) { + // TODO: Check if ordernr already exists + $data['customer_number'] = $customernr; + return $data; + } } } - + /** * plgVmOnSelfCallBE ... Called to execute some plugin action in the backend (e.g. set/reset dl counter, show statistics etc.) diff --git a/ordernumber.script.php b/ordernumber.script.php index a950ff7..4e2fd10 100644 --- a/ordernumber.script.php +++ b/ordernumber.script.php @@ -75,6 +75,21 @@ class plgVmShopperOrdernumberInstallerScript */ public function update(JAdapterInstance $adapter) { + $db = JFactory::getDBO(); + $db->setQuery('ALTER TABLE `#__virtuemart_shopper_plg_ordernumber` CHANGE `number_type` `number_type` VARCHAR(30) NULL DEFAULT NULL;'); + $db->query(); + + $countertypes = array( + 'order_number' => 0, + 'invoice_number' => 1, + 'customer_number' => 2, + 'order_password' => 3, + ); + foreach ($countertypes as $new => $old) { + $db->setQuery('update `#__virtuemart_shopper_plg_ordernumber` SET `number_type`="'.$new.'" WHERE `number_type`='.(int)$old.';'); + $db->query(); + } + } // jimport( 'joomla.filesystem.file' ); // $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); diff --git a/ordernumber.xml b/ordernumber.xml index 1e9f956..2a2995c 100644 --- a/ordernumber.xml +++ b/ordernumber.xml @@ -7,10 +7,10 @@ <authorUrl>http://www.open-tools.net/</authorUrl> <copyright>Copyright (C) 2012-2014 Reinhold Kainhofer. All rights reserved.</copyright> <license>http://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3</license> - <version>2.2</version> - <releaseDate>2014-12-07</releaseDate> - <releaseType>Minor update</releaseType> - <downloadUrl>http://www.open-tools.net/virtuemart-2-extensions/vm2-ordernumber-plugin.html</downloadUrl> + <version>3.0</version> + <releaseDate>2015-12-07</releaseDate> + <releaseType>Major update</releaseType> + <downloadUrl>http://open-tools.net/virtuemart/advanced-ordernumbers.html</downloadUrl> <description>VMSHOPPER_ORDERNUMBER_DESC</description> @@ -83,7 +83,12 @@ </field> <field name="customer_number_allcounters" type="VmOrdernumberCounters" label="PLG_ORDERNUMBER_ORDERNR_ALLCOUNTERS" countertype="customer_number" showon="customize_customer_number:1" /> </fieldset> + + <fieldset name="replacements" label="PLG_ORDERNUMBER_FIELDSET_REPLACEMENTS"> + <field name="replacements_options" type="spacer" label="PLG_ORDERNUMBER_REPLACEMENTS_DESC" /> + <field name="replacements" type="VmOrdernumberReplacements" label="" /> + </fieldset> </fields> </config> - + </extension> diff --git a/ordernumber/assets/css/ordernumber.css b/ordernumber/assets/css/ordernumber.css index 140beb3..27ecd74 100644 --- a/ordernumber/assets/css/ordernumber.css +++ b/ordernumber/assets/css/ordernumber.css @@ -31,3 +31,68 @@ div.ordernumber-ajax-loading, div.ordernumber-ajax-loading img.vmordernumber-btn div.ordernumber-ajax-loading img { z-index:0; } + + + +/* 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/ordernumber/assets/js/ordernumber.js b/ordernumber/assets/js/ordernumber.js index c7d64f8..1fa21ea 100644 --- a/ordernumber/assets/js/ordernumber.js +++ b/ordernumber/assets/js/ordernumber.js @@ -1,3 +1,8 @@ +/********************************************************************************** + * + * Javascript for the counter modification table + * + **********************************************************************************/ var updateMessages = function(messages, area) { jQuery( "#system-message-container #system-message ."+area+"-message").remove(); // Extract the messages from the returned string, add the ordernumber-message class (so the next ajax call @@ -144,3 +149,46 @@ var ajaxAddCounter = function (btn, nrtype) { }); } } + + + + +/********************************************************************************** + * + * 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'); + + // 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 () { + 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'); + } + } + ); + + jQuery("#ordernumber_variables tbody").sortable(); +}); -- GitLab