Example #1
0
	/**
	 * Search for a matching address
	 *
	 * Method will do a predefined search for a matching address
	 *
	 * @access public
	 * @param array $address The address details array
	 * @return int The matching address ID if found, FALSE if no match
	 */
	public function basicSearch($address)
	{
		if (!is_array($address)) {
			return false;
		}

		/**
		 * Clean our address array
		 */
		$address = Interspire_Array::clean($address);

		if (!is_array($address) || !isset($address["shipcustomerid"]) || !isset($address["shipfirstname"]) || !isset($address["shiplastname"]) || !isset($address["shipaddress1"])) {
			return false;
		}

		$searchFields = array();
		$searchFields["shipcustomerid"] = $address["shipcustomerid"];
		$searchFields["shipfirstname"] = array(
												"value" => $address["shipfirstname"],
												"func" => "LOWER"
										);

		$searchFields["shiplastname"] = array(
												"value" => $address["shiplastname"],
												"func" => "LOWER"
										);

		$searchFields["shipaddress1"] = array(
												"value" => $address["shipaddress1"],
												"func" => "LOWER"
										);

		$formSessionId = 0;
		if (isset($address["shipformsessionid"])) {
			$formSessionId = $address["shipformsessionid"];
		}

		return parent::search($searchFields, array(), array(), $formSessionId);
	}
	public function TaxStatsByDateGrid()
	{
		$GLOBALS['TaxGrid'] = "";

		if(!(isset($_GET['From']) && isset($_GET['To']))) {
			return;
		}

		$from_stamp = (int)$_GET['From'];
		$to_stamp = (int)$_GET['To'];

		// How many records per page?
		if(isset($_GET['Show'])) {
			$per_page = (int)$_GET['Show'];
		}
		else {
			$per_page = 20;
		}

		$GLOBALS['TaxPerPage'] = $per_page;
		$GLOBALS["IsShowPerPage" . $per_page] = 'selected="selected"';

		// Should we limit the records returned?
		if(isset($_GET['Page'])) {
			$page = (int)$_GET['Page'];
		}
		else {
			$page = 1;
		}

		$GLOBALS['TaxByDateCurrentPage'] = $page;

		// Workout the start and end records
		$start = ($per_page * $page) - $per_page;
		$end = $start + ($per_page - 1);

		// Only fetch data this user can actually see
		$vendorRestriction = $this->GetVendorRestriction();
		$vendorSql = '';
		if($vendorRestriction !== false) {
			$vendorSql = " AND ordvendorid='".(int)$vendorRestriction."'";
		}

		// Calculate the number of seconds from GMT +0 that we are in. We'll be adjusting
		// the orddate in the query below so that it becomes timezone specific (remember, MySQL thinks we're +0)
		$timezoneAdjustment = GetConfig('StoreTimeZone');
		if(GetConfig('StoreDSTCorrection')) {
			++$timezoneAdjustment;
		}
		$timezoneAdjustment *= 3600;

		if (empty($_GET['TaxListBy'])) {
			$groupBy = 'Day';
		}
		else {
			$groupBy = $_GET['TaxListBy'];
		}
		$fieldSQL = '';
		$addDay = 0;
		$addMonth = 0;
		$addYear = 0;
		$startStamp = $from_stamp;
		$endStamp = mktime(23, 59, 59, date('m', $to_stamp), date('j', $to_stamp), date('Y', $to_stamp));
		switch ($groupBy) {
			case 'Day':
				$fieldSQL = "DATE_FORMAT(FROM_UNIXTIME(orddate+".$timezoneAdjustment."), '%Y-%m-%d') AS formatteddate";
				$addDay = 1;
				$currentStamp = $startStamp;
				$dateFormat = 'jS M Y';
				break;
			case 'Month':
				$fieldSQL = "DATE_FORMAT(FROM_UNIXTIME(orddate+".$timezoneAdjustment."), '%Y-%m-1') AS formatteddate";
				$addMonth = 1;
				$currentStamp = mktime(0, 0, 0, date('m', $from_stamp) + $start, 1, date('Y', $from_stamp));
				$dateFormat = 'F Y';
				break;
			case 'Year':
				$fieldSQL = "DATE_FORMAT(FROM_UNIXTIME(orddate+".$timezoneAdjustment."), '%Y-1-1') AS formatteddate";
				$addYear = 1;
				$currentStamp = mktime(0, 0, 0, 1, 1, date('Y', $from_stamp));
				$dateFormat = 'Y';
				break;
		}

		$query = "
			SELECT
				t.name,
				t.class,
				t.rate,
				SUM(t.line_amount) AS amount,
				COUNT(DISTINCT t.order_id) AS numorders,
				".$fieldSQL."
			FROM [|PREFIX|]order_taxes t
			JOIN [|PREFIX|]orders o ON (o.orderid=t.order_id)
			WHERE
				t.line_amount > 0 AND
				o.ordstatus IN (".implode(',', GetPaidOrderStatusArray()).") AND
				o.deleted = 0 AND
				orddate >= '" . $startStamp . "' AND
				orddate <= '" . $endStamp . "'
				" . $vendorSql . "
			GROUP BY
				formatteddate,
				t.name,
				t.class,
				t.rate
			ORDER BY
				formatteddate
		";

		$result = $GLOBALS['ISC_CLASS_DB']->Query($query);

		$dataRows = array();
		while($row = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
			$date = strtotime($row['formatteddate']);
			$dataRows[$date][] = $row;
		}

		$lastdate = '';
		$outputRows = array();

		while (true) {
			if (!isset($dataRows[$currentStamp])) {
				$outputRows[] = array(
					'period'	=> $currentStamp,
					'taxtype'	=> '',
					'taxrate'	=> '',
					'numorders'	=> 0,
					'taxamount'	=> 0
				);
			}
			else {
				foreach ($dataRows[$currentStamp] as $row) {
					$outputRows[] = array(
						'period'	=> $currentStamp,
						'taxtype'	=> $row['name'].' - '.$row['class'],
						'taxrate'	=> $row['rate'],
						'numorders'	=> $row['numorders'],
						'taxamount'	=> $row['amount']
					);
				}
			}

			$currentStamp = mktime(0,0,0,date('m', $currentStamp) + $addMonth, date('d',$currentStamp) + $addDay, date('Y', $currentStamp) + $addYear);

			if ($currentStamp > $endStamp) {
				break;
			}
		}

		// Workout the paging
		$num_pages = ceil(count($outputRows) / $per_page);
		$paging = sprintf(GetLang('PageXOfX'), $page, $num_pages);
		$paging .= "&nbsp;&nbsp;&nbsp;&nbsp;";

		// Is there more than one page? If so show the &laquo; to jump back to page 1
		if($num_pages > 1) {
			$paging .= "<a href='javascript:void(0)' onclick='ChangeTaxByDatePage(1)'>&laquo;</a> | ";
		}
		else {
			$paging .= "&laquo; | ";
		}

		// Are we on page 2 or above?
		if($page > 1) {
			$paging .= sprintf("<a href='javascript:void(0)' onclick='ChangeTaxByDatePage(%d)'>%s</a> | ", $page-1, GetLang('Prev'));
		}
		else {
			$paging .= sprintf("%s | ", GetLang('Prev'));
		}

		for($i = 1; $i <= $num_pages; $i++) {
			// Only output paging -5 and +5 pages from the page we're on
			if($i >= $page-6 && $i <= $page+5) {
				if($page == $i) {
					$paging .= sprintf("<strong>%d</strong> | ", $i);
				}
				else {
					$paging .= sprintf("<a href='javascript:void(0)' onclick='ChangeTaxByDatePage(%d)'>%d</a> | ", $i, $i);
				}
			}
		}

		// Are we on page 2 or above?
		if($page < $num_pages) {
			$paging .= sprintf("<a href='javascript:void(0)' onclick='ChangeTaxByDatePage(%d)'>%s</a> | ", $page+1, GetLang('Next'));
		}
		else {
			$paging .= sprintf("%s | ", GetLang('Next'));
		}

		// Is there more than one page? If so show the &raquo; to go to the last page
		if($num_pages > 1) {
			$paging .= sprintf("<a href='javascript:void(0)' onclick='ChangeTaxByDatePage(%d)'>&raquo;</a> | ", $num_pages);
		}
		else {
			$paging .= "&raquo; | ";
		}

		$paging = rtrim($paging, ' |');
		$GLOBALS['Paging'] = $paging;

		if(isset($_GET['SortOrder']) && $_GET['SortOrder'] == "desc") {
			$sortOrder = "desc";
		}
		else {
			$sortOrder = "asc";
		}

		$sortFields = array('period','taxtype','taxrate','numorders','taxamount');
		if(isset($_GET['SortBy']) && in_array($_GET['SortBy'], $sortFields)) {
			$sortField = $_GET['SortBy'];
			SaveDefaultSortField("TaxStats", $_REQUEST['SortBy'], $sortOrder);
		}
		else {
			list($sortField, $sortOrder) = GetDefaultSortField("TaxStats", "period", $sortOrder);
		}

		$sortLinks = array(
			"Period" => "period",
			"TaxType" => "taxtype",
			"TaxRate" => "taxrate",
			"NumOrders" => "numorders",
			"TaxAmount" => "taxamount"
		);
		BuildAdminSortingLinks($sortLinks, "javascript:SortTaxStats('%%SORTFIELD%%', '%%SORTORDER%%');", $sortField, $sortOrder);

		if ($sortOrder == "asc") {
			$arraySort = SORT_ASC;
		}
		else {
			$arraySort = SORT_DESC;
		}

		// now lets sort the rows we've built
		$outputRows = Interspire_Array::msort($outputRows, array($sortField => $arraySort));

		$offset = ($page - 1) * $per_page;

		// lets grab the subset of rows for the current page
		$outputRows = array_slice($outputRows, $offset, $per_page);

		$lastStamp = 0;
		$output = '';
		foreach ($outputRows as $row) {
			$output .= "<tr class=\"GridRow\" onmouseover=\"this.className='GridRowOver';\" onmouseout=\"this.className='GridRow';\">";
			if ($lastStamp != $row['period']) {
				$output .= '<td class="'.$GLOBALS['SortedFieldPeriodClass'].'">' . date($dateFormat, $row['period']) . '</td>';
			}
			else {
				$output .= '<td class="'.$GLOBALS['SortedFieldPeriodClass'].'">&nbsp;</td>';
			}

			if ($row['taxamount'] == 0) {
				$output .= '<td align="left" colspan="2" class="'.$GLOBALS['SortedFieldTaxTypeClass'].'"><em>' . GetLang('NoTaxCollected') . '</em></td>';
				$output .= '<td align="center" class="'.$GLOBALS['SortedFieldNumOrdersClass'].'">0</td>';
				$output .= '<td align="right" class="'.$GLOBALS['SortedFieldTaxAmountClass'].'">' . FormatPrice(0) . '</td>';
			}
			else {
				$output .= '<td class="'.$GLOBALS['SortedFieldTaxTypeClass'].'">' . $row['taxtype'] . '</td>';
				$output .= '<td align="center" class="'.$GLOBALS['SortedFieldTaxRateClass'].'">' . ($row['taxrate']/1) .'%</td>';
				$output .= '<td align="center" class="'.$GLOBALS['SortedFieldNumOrdersClass'].'">' . number_format($row['numorders']) . '</td>';
				$output .= '<td align="right" class="'.$GLOBALS['SortedFieldTaxAmountClass'].'">' . FormatPrice($row['taxamount']) . '</td>';
			}

			$output .= '</tr>';
			$lastStamp = $row['period'];
		}

		$GLOBALS['TaxGrid'] = $output;

		// Add the limit
		$result = $GLOBALS['ISC_CLASS_DB']->Query($query);

		$this->template->display('stats.orders.salestax.tpl');
	}
	private function continueRebuildVariationsAction()
	{
		$sessionId = $_POST['session'];

		if (!isset($_SESSION['variations'][$sessionId])) {
			ISC_JSON::output('session ' . $sessionId . ' not found', false);
		}

		$session = &$_SESSION['variations'][$sessionId];

		// get the next product id
		$query = "
			SELECT
				productid
			FROM
				[|PREFIX|]products
			WHERE
				productid > " . $session['lastProductId'] . " AND
				prodvariationid = " . $session['variationId'] . "
			ORDER BY
				productid
			LIMIT
				1
		";

		$res = $this->db->Query($query);
		$productId = $this->db->FetchOne($res);

		// no more products to process? done.
		if (empty($productId)) {
			unset($_SESSION['variations'][$sessionId]);
			if (empty($_SESSION['variations'])) {
				unset($_SESSION['variations']);
			}
			ISC_JSON::output('', true, array('done' => true));
		}

		if ($this->db->StartTransaction() === false) {
			ISC_JSON::output('failed to start transaction', false);
		}

		$existingData = $session['existingData'];

		// were new option values (eg a new colour) added? we'll need to create some blank combinations to fill in the missing gaps.
		if (!empty($session['newValues'])) {
			$newValues = $session['newValues'];

			// iterate over the new option values
			foreach ($newValues as $optionName => $newValueIds) {
				foreach ($newValueIds as $newValueId) {
					// build combination id set
					$optionIdSets = array();

					foreach ($existingData['options'] as $optionIndex => $option) {
						if ($optionName == $option['name']) {
							$optionIdSets[$optionIndex][] = $newValueId;
							continue;
						}

						foreach ($option['values'] as $valueIndex => $value) {
							$optionIdSets[$optionIndex][] = $value['valueid'];
						}
					}

					// build a cartesian product of all the combinations that we need to generate
					$cartesian = Interspire_Array::generateCartesianProduct($optionIdSets);

					// iterate over each combination and insert to DB for all products using this variation
					foreach ($cartesian as $combination) {
						$combinationString = implode(',', $combination);

						$newCombination = array(
							'vcproductid'		=> $productId,
							'vcvariationid'		=> $session['variationId'],
							'vcoptionids'		=> $combinationString,
							'vclastmodified'	=> time(),
						);

						$this->db->InsertQuery('product_variation_combinations', $newCombination);
					}
				}
			}
		}

		// process new option set (eg. Material)
		if (!empty($session['newOptionValues'])) {
			$valuesForNewOption = $session['newOptionValues'];

			$likeMatch = str_repeat(",%", count($existingData['options']) - 2);
			$likeMatch = "%" . $likeMatch;

			foreach ($valuesForNewOption as $newOptionIndex => $newValueIds) {
				$newOptionCount = 0;

				foreach ($newValueIds as $newValueId) {
					// for the first new option value, we don't want to insert new combinations, but update the existing ones
					// store the option id for later and continue on
					if ($newOptionCount == 0) {
						$delayForUpdate = $newValueId;

						$newOptionCount++;
						continue;
					}

					$query = "
						INSERT INTO
							[|PREFIX|]product_variation_combinations (vcproductid, vcproducthash, vcvariationid, vcenabled, vcoptionids, vcsku, vcpricediff, vcprice, vcweightdiff, vcweight, vcimage, vcimagezoom, vcimagestd, vcimagethumb, vcstock, vclowstock, vclastmodified)
							SELECT
								vcproductid,
								vcproductid,
								vcvariationid,
								vcenabled,
								CONCAT(vcoptionids, ',', " . $newValueId . "),
								vcsku,
								vcpricediff,
								vcprice,
								vcweightdiff,
								vcweight,
								vcimage,
								vcimagezoom,
								vcimagestd,
								vcimagethumb,
								vcstock,
								vclowstock,
								" . time() . "
							FROM
								[|PREFIX|]product_variation_combinations
							WHERE
								vcproductid = " . $productId . " AND
								vcproducthash = ''
					";

					$this->db->Query($query);

					$newOptionCount++;
				}
			}

			// for the first new option id, add it onto the remaining existing row
			if (!empty($delayForUpdate)) {
				$query = "
					UPDATE
						[|PREFIX|]product_variation_combinations
					SET
						vcoptionids = CONCAT(vcoptionids, ',', " . $delayForUpdate . ")
					WHERE
						vcproductid = " . $productId . " AND
						vcproducthash = ''
				";

				$this->db->Query($query);
			}

			// blank the hash
			$query = "
				UPDATE
					[|PREFIX|]product_variation_combinations
				SET
					vcproducthash = ''
				WHERE
					vcproductid = " . $productId . "
			";
			$this->db->Query($query);
		}

		if ($this->db->CommitTransaction() === false) {
			$this->db->RollbackTransaction();
			ISC_JSON::output('failed to commit transaction', false);
		}

		$session['lastProductId'] = $productId;

		ISC_JSON::output('', true);
	}
Example #4
0
	private function HandleVariations($row)
	{
		//========Product Variations==========//

		if ($row['prodvariationid'] == 0) {
			$row['productVariations'] = array();
			return $row;
		}

		// get the position of the options in the variation fields
		$option_position = $this->GetFieldPosition('productVarDetails', $this->fields['productVariations']['fields']);

		// get the variation options
		$query = "SELECT * FROM [|PREFIX|]product_variation_options WHERE vovariationid = " . $row['prodvariationid'] . " ORDER BY voptionid";
		$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
		$options_cache = array();
		while ($optionRow = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
			$options_cache[$optionRow['voname']][] = array("id" => $optionRow['voptionid'], "value" => $optionRow['vovalue']);
		}

		// get a list of all possible combinations using the options for this variation
		$options = Interspire_Array::generateCartesianProduct($options_cache, true);

		$new_options = array();

		// the options for each variation
		foreach ($options as $option) {
			$optionids = "";
			$description = "";

			// build strings of the id's and values of each option
			foreach ($option as $key => $value) {
				if ($optionids) {
					$optionids .= ",";
				}
				$optionids .= $value["id"];

				if ($description) {
					$description .= ", ";
				}
				$description .= $key . ": " . $value["value"];
			}

			$new_options[$optionids] = array(
				"options"		=> $option,
				"description"	=> $description
			);
		}

		// get the data for the combinations
		$query = "SELECT * FROM [|PREFIX|]product_variation_combinations WHERE vcproductid = " . $row['prodid'];
		$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
		while ($comborow = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
			// get the specific combination of options
			$options = $new_options[$comborow['vcoptionids']]['options'];

			$prodoptions = array();

			$prodoptions['productVarDetails']		= $new_options[$comborow['vcoptionids']]['description'];
			$prodoptions['productVarSKU']			= $comborow['vcsku'];
			switch ($comborow['vcpricediff']) {
				case "add":
					$pricediff = "+";
					break;
				case "substract":
					$pricediff = "-";
					break;
				default:
					$pricediff = "";
			}
			$prodoptions['productVarPrice'] = $pricediff . $comborow['vcprice'];
			switch ($comborow['vcweightdiff']) {
				case "add":
				$weightdiff = "+";
					break;
				case "substract":
					$weightdiff = "-";
					break;
				default:
					$weightdiff = "";
			}
			$prodoptions['productVarWeight']		= $weightdiff . $comborow['vcweight'];
			$prodoptions['productVarStockLevel']	= $comborow['vcstock'];
			$prodoptions['productVarLowStockLevel']	= $comborow['vclowstock'];

			$prodoptions = $this->CreateSubItemArray($prodoptions, $this->fields['productVariations']['fields']);

			if ($this->fields['productVariations']['fields']['productVarDetails']['used']) {
				$optionkeys = array();
				$optionvalues = array();

				foreach ($options as $key => $value) {
					$optionvalues[$key] = $value['value'];
					$optionkeys[] = $key;
				}

				$keys = array_keys($prodoptions);
				// insert the fields into correct position
				array_splice($prodoptions, $option_position, 0, $optionvalues);
				array_splice($keys, $option_position, 0, $optionkeys);

				$prodoptions = array_combine($keys, $prodoptions);
			}

			$prodvariations[] = $prodoptions;
		}

		$row["productVariations"] = $prodvariations;

		return $row;
	}
Example #5
0
/**
* Returns an array containing all possible variation combinations
*
* @param int The product ID to get combinations for
* @param string Optional option name to exclude in the combinations
* @return array Array of all combinations
*/
function GetVariationCombinations($productID, $excludeOption = '')
{
	$where = "";
	if ($excludeOption) {
		$where = " AND voname != '" . $GLOBALS['ISC_CLASS_DB']->Quote($excludeOption) . "' ";
	}

	$query = "
		SELECT
			voname,
			GROUP_CONCAT(voptionid ORDER BY voptionid) AS optionids
		FROM
			[|PREFIX|]product_variation_options
			INNER JOIN [|PREFIX|]products p ON vovariationid = prodvariationid
		WHERE
			p.productid = " . $productID . "
			" . $where . "
		GROUP BY
			voname
	";
	$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
	$optionArray = array();
	while ($optionRow = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
		$optionArray[$optionRow['voname']] = explode(',', $optionRow['optionids']);
	}

	// calculate all the combinations
	return Interspire_Array::generateCartesianProduct($optionArray, true);
}
Example #6
0
	private function commitProducts($orderId, $rawInput, $itemAddressMap, $editingExisting=false, $adjustInventory=true)
	{
		$orderId = (int)$orderId;
		if (!$orderId) {
			$this->setError("Invalid arguments passed to commitProducts");
			return false;
		}

		/** @var ISC_QUOTE */
		$quote = $rawInput['quote'];

		$existingOrder = false;
		$existingProducts = false;

		if ($editingExisting && $orderId) {
			$existingOrder = $this->get($orderId);
			if (!$existingOrder) {
				$this->setError("editingExisting specified in commitProducts but order " . $orderId . " not found");
				return false;
			}

			// prune products from the existing order which are no longer on the incoming edit (products tied to unused addresses should already be gone after commitAddresses())
			$this->deleteUnusedOrderProducts($orderId, $quote);

			$existingProducts = $this->getOrderProducts($orderId);
			$existingProducts = Interspire_Array::remapToSubkey($existingProducts, 'orderprodid'); // make existing products easier to find by id
		}

		$giftCertificates = array();
		foreach($quote->getItems() as /** @var ISC_QUOTE_ITEM */$item) {
			$itemType = 'physical';
			if($item->getType() == PT_DIGITAL) {
				$itemType = 'digital';
			}
			else if($item instanceof ISC_QUOTE_ITEM_GIFTCERTIFICATE) {
				// Gift certificates cannot be modified so continue if this is an existing order
				if($existingOrder) {
					continue;
				}
				$itemType = 'giftcertificate';
				$giftCertificates[] = $item;
			}

			$existingProduct = false;
			if (is_numeric($item->getId())) {
				// numeric quote_item id denotes existing order_product, but if it doesn't exist for some reason treat it as a new order_product I guess
				if (isset($existingProducts[$item->getId()])) {
					$existingProduct = $existingProducts[$item->getId()];
				}
			}

			// addresses are already stored in db and assigned real ids by commitAddresses()
			$addressId = 0;
			$itemAddressId = $item->getAddressId();
			if(isset($itemAddressMap[$itemAddressId])) {
				$addressId = $itemAddressMap[$itemAddressId];
			}

			$appliedDiscounts = $item->getDiscounts();
			if (empty($appliedDiscounts)) {
				$appliedDiscounts = '';
			} else {
				$appliedDiscounts = serialize($appliedDiscounts);
			}

			// build order_products data for upsert
			$newProduct = array(
				'orderorderid'				=> $orderId,
				'ordprodid'					=> $item->getProductId(),
				'ordprodsku'				=> $item->getSku(),
				'ordprodname'				=> $item->getName(),
				'ordprodtype'				=> $itemType,
				'ordprodqty'				=> $item->getQuantity(),

				'base_price'				=> $item->getBasePrice(),
				'price_ex_tax'				=> $item->getPrice(false),
				'price_inc_tax'				=> $item->getPrice(true),
				'price_tax'					=> $item->getTax(),

				'base_total'				=> $item->getBaseTotal(),
				'total_ex_tax'				=> $item->getTotal(false),
				'total_inc_tax'				=> $item->getTotal(true),
				'total_tax'					=> $item->getTaxTotal(),

				'base_cost_price'			=> $item->getBaseCostPrice(),
				'cost_price_inc_tax'		=> $item->getCostPrice(false),
				'cost_price_inc_tax'		=> $item->getCostPrice(true),
				'cost_price_tax'			=> $item->getCostPriceTax(),

				'ordprodweight'				=> $item->getWeight(),

				'ordprodoptions'			=> '',
				'ordprodvariationid'		=> $item->getVariationid(),

				'ordprodwrapid'				=> 0,
				'ordprodwrapname'			=> '',
				'base_wrapping_cost'		=> $item->getBaseWrappingCost(),
				'wrapping_cost_ex_tax'		=> $item->getWrappingCost(false),
				'wrapping_cost_inc_tax'		=> $item->getWrappingCost(true),
				'wrapping_cost_tax'			=> $item->getWrappingCost(true) - $item->getWrappingCost(false),
				'ordprodwrapmessage'		=> '',
				'ordprodeventname'			=> $item->getEventName(),
				'ordprodeventdate'			=> $item->getEventDate(true),
				'ordprodfixedshippingcost'	=> $item->getFixedShippingCost(),

				'order_address_id'			=> $addressId,

				'applied_discounts'			=> $appliedDiscounts,
			);

			$variationOptions = $item->getVariationOptions();
			if(!empty($variationOptions)) {
				$newProduct['ordprodoptions'] = serialize($variationOptions);
			}

			$wrapping = $item->getGiftWrapping();
			if(!empty($wrapping)) {
				$newProduct['ordprodwrapid'] = $wrapping['wrapid'];
				$newProduct['ordprodwrapname'] = $wrapping['wrapname'];
				$newProduct['ordprodwrapmessage'] = $wrapping['wrapmessage'];
			}

			// upsert to order_products
			if ($existingProduct) {
				if (!$this->db->UpdateQuery('order_products', $newProduct, "orderprodid = " . $existingProduct['orderprodid'])) {
					$this->setError("Failed to update existing order product");
					return false;
				}

				// remove existing configurable fields so they can be reinserted if needed
				$this->deleteOrderProductConfigurableFields($existingProduct['orderprodid']);
			} else {
				$orderProductId = $GLOBALS['ISC_CLASS_DB']->insertQuery('order_products', $newProduct);
				if(!$orderProductId) {
					$this->setError("Failed to insert new order product");
					return false;
				}
			}

			// adjust inventory levels for existing orders only (UpdateOrderStatus() seems to do it for new orders)
			if ($adjustInventory && $existingOrder && $existingOrder['ordinventoryupdated']) {
				$inventoryRequiresAdjustment = true;

				if ($existingProduct && $existingProduct['ordprodid']) {
					if ($existingProduct['ordprodid'] == $item->getProductId() && $existingProduct['ordprodvariationid'] == $item->getVariationId() && $existingProduct['ordprodqty'] == $item->getQuantity()) {
						// don't bother adjusting if the product, variation and qty details have not changed
						$inventoryRequiresAdjustment = false;
					}

					if ($inventoryRequiresAdjustment) {
						$product = $this->product->get($existingProduct['ordprodid']);
						if ($product) {
						// put old product inventory back to store
							if (!AdjustProductInventory($existingProduct['ordprodid'], $existingProduct['ordprodvariationid'], $product['prodinvtrack'], '+' . $existingProduct['ordprodqty'])) {
								$this->setError("Failed to adjust inventory for old order product");
								return false;
							}
						}
					}
				}

				// pull new product inventory from store
				if ($inventoryRequiresAdjustment && $item->getProductId()) {
					if (!AdjustProductInventory($item->getProductId(), $item->getVariationId(), $item->getInventoryTrackingMethod(), '-' . $item->getQuantity())) {
						$this->setError("Failed to adjust inventory for new order product");
						return false;
					}
				}
			}

			$configurableFields = $item->getConfiguration();
			foreach($configurableFields as $fieldId => $field) {
				if($field['type'] == 'file' && trim($field['value']) != '') {
					$filePath = ISC_BASE_PATH.'/'.GetConfig('ImageDirectory').'/configured_products/'.$field['value'];
					$fileTmpPath = ISC_BASE_PATH.'/'.GetConfig('ImageDirectory').'/configured_products_tmp/'.$field['value'];

					//do not remove the temp file here, because the payment may not successful
					//the file should still be viewable in in the cart,
					@copy($fileTmpPath, $filePath);
				}

				$newField = array(
					'ordprodid' => $orderProductId,
					'fieldid' => $fieldId,
					'orderid' => $orderId,
					'fieldname' => $field['name'],
					'fieldtype' => $field['type'],
					'fieldselectoptions' => '',
					'textcontents' => '',
					'filename' => '',
					'filetype' => '',
					'originalfilename' => '',
					'productid' => $item->getProductId(),
				);

				if($field['type'] == 'file' && trim($field['value']) != '') {
					$newField['filename'] = trim($field['value']);
					$newField['filetype'] = trim($field['fileType']);
					$newField['originalfilename'] = trim($field['fileOriginalName']);
				}
				elseif ($field['type'] == 'select') {
					$newField['fieldselectoptions'] = $field['selectOptions'];
					$newField['textcontents'] = trim($field['value']);
				}
				else {
					$newField['textcontents'] = trim($field['value']);
				}

				if(!$GLOBALS['ISC_CLASS_DB']->InsertQuery('order_configurable_fields', $newField)) {
					return false;
				}
			}
		}

		if(!empty($giftCertificates)) {
			getClass('ISC_GIFTCERTIFICATES')->createGiftCertificatesFromOrder(
				$orderId,
				$giftCertificates,
				1
			);
		}

		return true;
	}