Select Git revision
ordernumber.php 27.56 KiB
<?php
/**
* @package VirtueMart 2 OrderNumber plugin for Joomla! 2.5
* @author Reinhold Kainhofer, reinhold@kainhofer.com
* @copyright (C) 2012-2014 - 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');
if (!class_exists( 'VmConfig' ))
require(JPATH_ROOT.DS.'administrator'.DS.'components'.DS.'com_virtuemart'.DS.'helpers'.DS.'config.php');
VmConfig::loadConfig();
class plgVmShopperOrdernumber extends vmShopperPlugin {
function __construct(& $subject, $config) {
parent::__construct($subject, $config);
/* Create the database table */
$this->tableFields = array_keys ($this->getTableSQLFields ());
}
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' => 'varchar(30)',
'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) {}
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`='.$db->quote($nrtype).' AND `number_format`='.$db->quote($format);
$db->setQuery($q);
$existing = $db->loadResult();
$count = $existing?$existing:$default;
return $count;
}
function _counterExists($nrtype, $format) {
$db = JFactory::getDBO();
$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);
}
// 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.','.$db->quote($nrtype).', '.$db->quote($format).')';
$db->setQuery( $q );
$db->query();
return $db->getAffectedRows();
}
// 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`='.$db->quote($nrtype).' AND `number_format`='.$db->quote($format);
$db->setQuery( $q );
$db->query();
if ($db->getAffectedRows()<1) {
return $this->_addCounter($nrtype, $format, $value);
} else {
return $db->getAffectedRows();
}
}
// Insert new counter value into the db or update existing one
function _deleteCounter($nrtype, $format) {
$db = JFactory::getDBO();
$format = $db->escape ($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();
}
/* 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);
}
/* Extract the country information from the given ID */
static function getCountryFromID ($country_id) {
$db = JFactory::getDBO();
$query = 'SELECT * FROM `#__virtuemart_countries` WHERE `virtuemart_country_id` = ' . (int)$country_id;
$db->setQuery($query);
return $db->loadObject();
}
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 (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 (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;
// TODO: Make the shipping/billing address available for order numbers, too!
$this->setupAddressReplacements($reps, "", $details, $nrtype);
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);
foreach ($order->items as $i) {
$articles += $i->product_quantity;
$products += 1;
$p = $productModel->getProduct($i->virtuemart_product_id);
$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();
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) {
// TODO: Implement shopper group!
$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;
}
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);
return $reps;
}
protected function setupCustomVariables ($nrtype, $order, $reps, $customvars) {
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'];
}
}
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;
}
}
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"]==1)?"":$parts[(count($parts)>1)?1:0];
return $ctrsettings;
}
/* 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);
// JFactory::getApplication()->enqueueMessage("<pre>Replacements for $type:".print_r($reps,1)."</pre>", 'error');
// 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();
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;
}
}
$number = $this->createNumber ($fmt, $type, $order, $customvars, $ctrsettings);
return $number;
} else {
return false;
}
}
function plgVmOnUserOrder(&$orderDetails/*,&$data*/) {
$ordernumber = $this->assignNumber($orderDetails, 'order_number', "#");
if ($ordernumber !== false) {
// TODO: Check if ordernr already exists
$orderDetails->order_number = $ordernumber;
}
$orderpwd = $this->assignNumber($orderDetails, 'order_password', "[randomHex8]");
if ($orderpwd !== false) {
$orderDetails->order_pass = $orderpwd;
}
}
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('C'));
if(!is_array($orderstatusForInvoice)) $orderstatusForInvoice = array($orderstatusForInvoice); //for backward compatibility 2.0.8e
$pdfInvoice = (int)VmConfig::get('pdf_invoice', 0); // backwards compatible
// For VM<3.0.12, the URL parameter is an int, for VM>=3.0.12, the URL param is a string/cmd
$force_create_invoice = JFactory::getApplication()->input->getCmd('create_invoice', -1);
if (is_numeric($force_create_invoice)) {
// numeric means we have the old behavor pre-3.0.12 => No invoice_pass to check
$invoice_pass = 1;
} else {
$invoice_pass = isset($orderDetails['order_create_invoice_pass']) ? $orderDetails['order_create_invoice_pass'] : 'DO_NOT_CREATE_INVOICE';
}
if ( in_array($orderDetails['order_status'],$orderstatusForInvoice) or $pdfInvoice==1 or $force_create_invoice==$invoice_pass ){
$invoicenr = $this->assignNumber((object)$orderDetails, 'invoice_number', "#");
if ($invoicenr !== false) {
// TODO: Check if ordernr already exists
$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) {
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.)
*/
function plgVmOnSelfCallBE($type, $name, &$output) {
if ($name != $this->_name || $type != 'vmshopper') return false;
vmDebug('plgVmOnSelfCallBE');
$user = JFactory::getUser();
$authorized = ($user->authorise('core.admin','com_virtuemart') or
$user->authorise('core.manage','com_virtuemart') or
$user->authorise('vm.orders','com_virtuemart'));
$json = array();
$json['authorized'] = $authorized;
if (!$authorized) return FALSE;
$action = vRequest::getCmd('action');
$counter= vRequest::getString('counter');
$nrtype = vRequest::getString('nrtype');
$json['action'] = $action;
$json['success'] = 0; // default: unsuccessfull
switch ($action) {
case "deleteCounter":
$json['success'] = $this->_deleteCounter($nrtype, $counter);
break;
case "addCounter":
$value = vRequest::getInt('value',0);
if ($this->_counterExists($nrtype, $counter)) {
$json['error'] = JText::sprintf('PLG_ORDERNUMBER_COUNTERLIST_EXISTS', $counter);
$json['success'] = false;
} else {
$json['success'] = $this->_addCounter($nrtype, $counter, $value);
// Return the table row for the new counter in the JSON:
$pluginpath = '/plugins/vmshopper/ordernumber/ordernumber/';
$displayfmt = ($counter=="") ? JText::_('PLG_ORDERNUMBER_COUNTERLIST_GLOBAL') : $counter;
$html=array();
$html[] = "<tr class='counter_row counter_type_$nrtype'>";
$html[] = " <td class='counter_format'>" . (string)$displayfmt . "</td>";
$html[] = " <td class='counter_value'>" . (string)$value . "</td>";
$html[] = " <td class='counter_buttons'><img src='" .JURI::root(true).$pluginpath . "assets/images/icon-16-edit.png' class='vmordernumber-counter-editbtn vmordernumber-btn' onClick='ajaxEditCounter(this, ".json_encode($nrtype).", ".json_encode($counter).", $value)' /><img src='" . JURI::root(true).$pluginpath . "assets/images/icon-16-delete.png' class='vmordernumber-counter-deletebtn vmordernumber-btn' onClick='ajaxDeleteCounter(this, ".json_encode($nrtype).", ".json_encode($counter).", $value)' /></td>";
$html[] = "</tr>";
$json['newrow'] = implode("\n", $html);
}
break;
case "setCounter":
$value = vRequest::getInt('value');
$json['success'] = $this->_setCounter($nrtype, $counter, $value);
break;
}
// Also return all messages (in HTML format!):
// Since we are in a JSON document, we have to temporarily switch the type to HTML
// to make sure the html renderer is actually used
$document = JFactory::getDocument ();
$previoustype = $document->getType();
$document->setType('html');
$msgrenderer = $document->loadRenderer('message');
$json['messages'] = $msgrenderer->render('Message');
$document->setType($previoustype);
// WORKAROUND for broken (i.e. duplicate) content-disposition headers in Joomla 2.x:
// We request everything in raw and here send the headers for JSON and return
// the raw output in json format
$document =JFactory::getDocument();
$document->setMimeEncoding('application/json');
JResponse::setHeader('Content-Disposition','attachment;filename="ordernumber.json"');
$output = json_encode($json);
}
/* In versions before VM 2.6.8, the onStoreInstallPluginTable function was protected, so the installer couldn't call it to create the plugin table...
This function simply is a public wrapper to make this function available to the installer on all VM versions: */
public function plgVmOnStoreInstallPluginTable($psType, $name='') {
return $this->onStoreInstallPluginTable($psType, $name);
}
}