Dr. Rolex Posted May 2, 2014 Share Posted May 2, 2014 Hello and welcome! jQuery/Ajax Advanced Order Handler for osCommerce 2.3.3 - Support thread - Have you noticed how frustrating order management in osCommerce can be? Especially if you display 100 orders at a time and every single time you need to look at another order you have to reload the entire page thus querying the database for each and every order again and again and again... It can be quite annoying, at least I thought so and it definitely isn't very effective. It's as bad/slow as it gets and we don't like slow, do we?. In addition to this most (if not all) who uses osCommerce have probably run into the problem of not being able to manually create new orders. Doing this natively requires one to actually login to an account and creating the order as if you were a customer. Even more problematic is modifying an existing order, doing this natively requires the admin to either delete & create a new order with correct details or manually editing the database directly. Both of which are, to say the least, very unattractive solutions. Having used a combination of Order Editors / Batch Printing Add-Ons myself I felt it was warranted to create a new Order Management solution that incorporates all of them in an easy to use & effective way. The goal was also to completely get rid of the need to refresh/reload the order page to see new orders. Order Refresh The solution was to use Comet: Comet is a web application model in which a long-held HTTP request allows a web server to push data to a browser, without the browser explicitly requesting it. The Comet Technique used is Ajax with long polling with jQuery.ajax. This is how it works: A GET request is sent to stream.php in which it includes the current last order displayed together with a Timeout in seconds which you can set to any number you want in the order handler Bootstrap Fixed-bottom Navbar. So, if set to e.g. 20, stream.php will every 20 seconds check the order number of the newest order in the database. If the database has a newer order than the one displayed in the order table, then the script will break the loop and return a json response with the order number. This will be parsed by jQuery, which then sends another GET request to order_poller.php asking for the new order/orders, the new rows will be returned which will be added to your displayed order table by jQuery. It will only add new information, NOTmessing up your selections in the order handler. A Growl Notification will be displayed with information from the new order. Ajax Polling will automatically be enabled when the 'Pending' selection is selected from the 'Status' dropdown, but it can also be toggled in the Navbar. Order Handling To be able to display even the long order comments, I have added a feature to display them as Bootstrap Tooltips when hovered over. I have removed the menu selection box and instead used that space to display more information in columns. A feature to sort the orders by how many products are in them have been added. Clicking on a table row will not make any requests to the server but instead use jQuery to move the highlighting. The Navbar I have used a Bootstrap Navbar with a Fixed Bottom position. This means it will always be displayed at the bottom of the screen which helps a lot when seeking through orders or when updating them. I have used the Bootstrap Multiselect Add-On to get the menu items like I wanted and I have also used Font-Awesome to get some nice looking Icons. The Action field The action field is the rightmost column on the orders table, containing the five action icons. Hover mouse over them to see what each action does. Note that there will be no Dialog asking for confirmation on Order Duplication or E-Mail Order Confirmation, pressing any of those icons will instantly proceed with their action. From left to right; the first Icon will Mail a new Order Confirmation E-Mail to the customer (and the shop owner if configured) for that order; the second icon will duplicate the order; the third icon will open a Dialog for Quick Editing the order; the forth icon will open a dialog for creating a new order; the fifth icon will go to the "normal" edit order mode. Export I use Mail Merge in Pages for Mac OS X to create Envelopes. This is very easy and fast. To do this, you first need to create a Envelope template in Pages 4.3 (version 5.0 doesn't support this feature anymore for some reason). Look at what names the data-fields have and then change the code in print_batch_process_2.php around line 56 that looks something like this: echo "Name;Street address;Postcode;City;Country\n"; So it has the same names in the heading as your envelope data-fields. Then simply open the CSV created by selecting 'Envelope' in Numbers and convert the top row to a heading. Save and then open in Pages by selecting 'Mail Merge'. This will automatically create a new envelope for every address which you easily can print. Since I don't use Excel I don't know how this is done with that software, google it, I'm sure it has some similar functionality. There is also an alternative to export to XML, a format commonly used by shipping providers to import addresses to shipping labels. Ask your shipping provider how the XML file should be designed and then make the appropriate modifications to print_batch_process_2.php around line 79 to 106. Order Editing Having a dedicated page for editing orders isn't my first choice as a solution. Instead I found ledave's Add-On, the AJAX Orders Editor. The Order Editor enables you to edit the order directly on the Order page. I really liked this solution, however it still required some editing to get it to work (for me) with taxes etc. I also added the possibility to edit the Telephone and E-Mail fields*.Moreover, I added a feature to select available taxes when adding additional fields (note that the tax is overwritten if a product is added after adding the field) and adding jQuery code to automatically refresh the fields when they have been edited so that you don't have to refresh/reload the page to see the changes. Hopefully, this will work for everyone now without need to modify any code, but no promises.. Order Creation To create new orders I have used the Manual Order Maker Add-On. Used together with the Ajax Orders Editor, new orders can be created in just a couple of seconds, no matter whether it is from a new or an existing customer.I might* have added extra search functionality - search customer by E-Mail Address or Name and also added jQuery autocomplete to suggest customers from typed name. The Order Creator can be reached both from the menu and as a Modal Quick Open by clicking it's icon in the order handler, it will then automatically pre-select the customer that made the order from the same row. I have also added dropdown selections where you can select payment & shipping method for the new order. Order Duplication If you have a customer that want to place the same order as he/she has done before, then simply search for his order and press the Duplicate Order Icon. One click and a new order has been created for this customer with the same products & shipping/payment methods. This makes creating new orders really easy and if you need to change something, then just edit the order. Deleting Orders Just check the checkboxes for the orders you want to delete and then press the red 'Delete' button on the Navbar. You will get a confirmation modal where you can select to optionally restock the products and confirm/cancel the deletion. You can also delete specific order when your at "Editing mode". * I don't want to take credit for stuff I didn't do and I have used these Add-Ons for so long that I can't remember how they originally worked. Features for this Revision: Support for and tested on osCommerce 2.3.3 Tested successfully on Google Chrome and Firefox Compatible with jQuery 2.1.0 Complete Order Creator/Editor/Handler Edit any field on an order by simply clicking on it and change the value - Code used from the 'AJAX Orders Editor' Add-On by ledave. Edited fields will instantly be refreshed when edited or a product has been added/removed (including Order Total, if affected). Quickly Add/Remove products to a order by searching in a javascript window. Edit Name/Price on products. Add/Remove 'Extra' Fields like shipping/payment costs (adding tax is selectable). One-Click-Duplication of any order - E-Mail will be sent as if the order was created by a customer. Order Creator, both as a jQuery Dialog or as a whole page with added Autocomplete support for searching customers - Code used from the 'Manual Order Maker' Add-On Quickly Create New order for existing customer by either searching for customer by Name/E-Mail/Customer Nr. or clicking the quick add icon next to existing order. Quickly Create New order for new customer - Optionally Creating New account jQuery/Ajax Handling of Orders - No need for those costly page reloads every time you select a new order or want to update for new orders. Togglable Comet/Ajax Long-Polling for new orders - When a customer creates a new order, the Order Table will automatically update and add the new order to the list without messing with your selections. Never do a page refresh again! Growl like Notifications with jQuery Gritter http://boedesign.com/blog/2009/07/11/growl-for-jquery-gritter/ Order Comments will be displayable as a Bootstrap Tooltip - No need to open the order just to check what a comment says. Batch Delete of Orders - Simply toggle the checkbox for the orders you would like to Delete and then review & confirm the selections in a nice Bootstrap Modal. - Code used from one of the Batch Print Add-Ons, can't find which one. ;=( Batch Handling of Orders - Batch Print Invoices (one order = one A4) for the selected orders, without need for PDF, in one click. Code used is from 'Batch Printing without Frames or PDF v.2.3.1' Add-On by 0ethos0 (Originally made by PandA.nl) Batch Handling of Orders - Batch Update Order Status / Send E-Mail for selected orders. Batch Handling of Orders - Export CSV to be used for automatically creating Envelopes with Excel/Numbers/Pages. Batch Handling of Orders - Export XML to be used to automatically create Shipping Labels for your shipping provider. Added an 'Order by products quantity' sorting feature to simplify order processing by handling orders with just one product first. Search Orders by E-Mail, Name or Order Number. Simple Tab management - Batch Invoices will open/refresh in same tab, meaning you only need to tabs open to handle both orders and invoices. CSS3 Loading Spinner displayed while orders are updating preventing editing orders while they're being updated. HTML5 pushState with history.js - Enables Ajax support for Back/Forward button in browser. Fixed-Bottom Bootstrap Navbar with Bootstrap Multiselect - Sliding down automatically when editing orders. Quick Send New Order Confirmation E-Mail to customer oscMarket 1 Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 2, 2014 Author Share Posted May 2, 2014 Download Add-On here: http://addons.oscommerce.com/info/9055 Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 3, 2014 Author Share Posted May 3, 2014 very impressive :thumbsup: Thanks! :P Ok, so I found a bug that will prevent editing of product attributes. In ./admin/order_handler.php around line 348, find this code: echo '<br><nobr><small> <i> - <a data-product="' . $order->products[$i]['id'] . '" data-field="options" data-action="update" data-extra="" data-pred="'.$order->products[$i]['option'].'" href="#" class="ajaxLinkProduct">' . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; Replace with: echo '<br><nobr><small> <i> - <a data-product="' . $order->products[$i]['id'] . '" data-field="options" data-action="update" data-extra="' . $order->products[$i]['attributes'][$j]['option'] . '" data-pred="'.$order->products[$i]['option'].'" href="#" class="ajaxLinkProduct">' . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; In ./admin/ajax_handler.php around line 730, find: echo '<br><nobr><small> <i> - <a data-product="' . $order->products[$i]['id'] . '" data-field="options" data-action="update" data-extra="" data-pred="'.$order->products[$i]['option'].'" href="#" class="ajaxLinkProduct">' . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; Replace with: echo '<br><nobr><small> <i> - <a data-product="' . $order->products[$i]['id'] . '" data-field="options" data-action="update" data-extra="' . $order->products[$i]['attributes'][$j]['option'] . '" data-pred="'.$order->products[$i]['option'].'" href="#" class="ajaxLinkProduct">' . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; Around line 563, find: $output .= '<br><nobr><small> <i> - <a data-product="' . $order->products[$i]['id'] . '" data-field="options" data-action="update" data-extra="" data-pred="'.$order->products[$i]['option'].'" href="#" class="ajaxLinkProduct">' . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; Replace with: $output .= '<br><nobr><small> <i> - <a data-product="' . $order->products[$i]['id'] . '" data-field="options" data-action="update" data-extra="' . $order->products[$i]['attributes'][$j]['option'] . '" data-pred="'.$order->products[$i]['option'].'" href="#" class="ajaxLinkProduct">' . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; Additionally, orders_ajax.php is susceptible to SQL-injections so I have remade the entire file using MySQLi Prepared Statements. Replace the entire ./admin/orders_ajax.php with this: <?php require('includes/application_top.php'); require(DIR_WS_CLASSES . 'currencies.php'); $currencies = new currencies(); function tep_db_update_totals($order_id) { $currencies = new currencies(); //we have to update the orders_total table $products_total_query = mysqli_prepared_query("select final_price, products_quantity, products_tax from " . TABLE_ORDERS_PRODUCTS . " where orders_id = ?", "i", array($order_id)); $price = 0; $total = 0; $taxes = array(); if (count($products_total_query) > 0) { foreach ($products_total_query as $products_total) { $iva = round((100 + (float)$products_total['products_tax']) / 100, 4); $price += (float)round(((float)$products_total['final_price'] * (int)$products_total['products_quantity']) * $iva, 2); //fill the array of taxes to know which tax is used. if (tep_not_null($products_total['products_tax']) && $products_total['products_tax'] > 0) { $tax_description = mysqli_prepared_query("select tax_description from " . TABLE_TAX_RATES . " where tax_rate = ?", "d", array($products_total['products_tax'])); $tax_description = $tax_description[0]; if (sizeof($taxes)) { $ya_esta = false; for ($i=0; $i<sizeof($taxes); $i++) { if (in_array($tax_description['tax_description'], $taxes[$i])) { $ya_esta = $i; } } if ($ya_esta === false) { $taxes[] = array('description' => $tax_description['tax_description'], 'value' => round(((((float)$products_total['final_price'] * (int)$products_total['products_quantity']) * (float)$products_total['products_tax']) / 100), 4)); } else { $taxes[$ya_esta]['value'] += round(((((float)$products_total['final_price'] * (int)$products_total['products_quantity']) * (float)$products_total['products_tax']) / 100), 4); } } else { $taxes[] = array('description' => $tax_description['tax_description'], 'value' => round(((((float)$products_total['final_price'] * (int)$products_total['products_quantity']) * (float)$products_total['products_tax']) / 100), 4)); } } } } $orders_total_query = mysqli_prepared_query("select * from " . TABLE_ORDERS_TOTAL . " where orders_id = ? and class != 'ot_tax' order by sort_order", "i", array($order_id)); foreach ($orders_total_query as $order_total) { if ($order_total['class'] == 'ot_subtotal') { $new_value = (float)$price; $new_text = $currencies->format($new_value); $total += (float)$new_value; $params = array($new_text, $new_value, $order_total['orders_total_id']); mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set text = ?, value = ? where orders_total_id = ?", "sdi", $params); } elseif ($order_total['class'] == 'ot_total') { $new_value = (float)$total; $new_text = '<strong>' . $currencies->format(round($new_value)) . '</strong>'; $params = array($new_text, $new_value, $order_total['orders_total_id']); mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set text = ?, value = ? where orders_total_id = ?", "sdi", $params); } else { $total += round((float)$order_total['value'], 4); $ht = round(((float)$order_total['value']/1.25), 4); $tva = (float)$order_total['value']-(float)$ht; } } //the taxes if (sizeof($taxes)) { $orders_total_tax_query = mysqli_prepared_query("select * from " . TABLE_ORDERS_TOTAL . " where orders_id = ? and class = 'ot_tax'", "i", array($order_id)); //update the ot_tax with the same title //if title doesn't exist, insert it $tax_updated = array(); foreach ($orders_total_tax_query as $orders_total_tax) { $eliminate_tax = true; for ($i=0; $i<sizeof($taxes); $i++) { if (in_array($orders_total_tax['title'], $taxes[$i])) { $eliminate_tax = false; //keep in variable that this tax is done $tax_updated[] = $orders_total_tax['title']; //prepare text (value with currency) $texto = number_format((float)$taxes[$i]['value'], 2); $new_text = $currencies->format($texto); $params = array($new_text, $taxes[$i]['value'], $orders_total_tax['orders_total_id']); mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set text = ?, value = ? where orders_total_id = ? and class = 'ot_tax'", "sdi", $params); } } //we have eliminate the last product of one tax_rate->eliminate the ot_field if ($eliminate_tax == true) { mysqli_prepared_query("delete from " . TABLE_ORDERS_TOTAL . " where orders_total_id = ? limit 1", "i", array($orders_total_tax['orders_total_id'])); } } //insert a new tax rate in the orders_total table, if all of taxes[] is not in $tax_updated[] for ($i=0; $i<sizeof($taxes); $i++) { if ((!in_array($taxes[$i]['description'], $tax_updated)) && ((float)$taxes[$i]['value'] > 0)) { //prepare text (value with currency) $texto = round((float)$taxes[$i]['value'], 2); //$texto = (string)$texto . $currency['symbol_right']; $texto = $currencies->format($texto); if ($taxes[$i]['description'] !== null) $params = array($order_id, $taxes[$i]['description'], $texto, $taxes[$i]['value']); mysqli_prepared_query("insert into " . TABLE_ORDERS_TOTAL . " (orders_id, title, text, value, class, sort_order) values (?, ?, ?, ?, 'ot_tax', 3)", "issd", $params); } } } } $action = $_GET['action']; if (($action == 'eliminate_field') || ($action == 'eliminate') || ($action == 'update_product')) {//eliminate or modify product if ($action == 'eliminate') { mysqli_prepared_query("delete from " . TABLE_ORDERS_PRODUCTS . " where orders_products_id = ? limit 1", "i", array($_GET['pID'])); $attributes_query = mysqli_prepared_query("select orders_products_attributes_id from " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . " where orders_products_id = ?", "i", array($_GET['pID'])); //if the products has attributes, eliminate them if (count($attributes_query) > 0) { foreach ($attributes_query as $attributes) { mysqli_prepared_query("delete from " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . " where orders_products_attributes_id = ? limit 1", "i", array($attributes['orders_products_attributes_id'])); } } } elseif ($action == 'eliminate_field') { $oID = (int)$_GET['oID']; $class = $_POST['total_class']; $title = urldecode($_POST['title']); $params = array($oID, $title, $class); mysqli_prepared_query("delete from " . TABLE_ORDERS_TOTAL . " where orders_id = ? AND title = ? AND class = ? limit 1", "iss", $params); tep_db_update_totals($oID); tep_exit(); } else { // get the price to change order totals // but first, change it if we change price of attributes (so we get directly the good final_price) $field = $_GET['field']; if ($field == 'options') { $params = array( $_GET['new_value'], round((float)$_GET['option_price'], 4), $_GET['pID'], $_GET['extra']); mysqli_prepared_query("update " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . " set products_options_values = ?, options_values_price = ? where orders_products_id = ? and products_options = ?", "sdis", $params); $params = array(round((float)$_GET['option_price'], 4), $_GET['pID']); mysqli_prepared_query("update " . TABLE_ORDERS_PRODUCTS . " set final_price = (products_price + ?) where orders_products_id = ?", "di", $params); } elseif (stristr($field, 'price')) { $adapt_price_query = mysqli_prepared_query("select options_values_price from " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . " where orders_products_id = ? and options_values_price != 0", "i", array($_GET['pID'])); if (count($adapt_price_query)) { $adapt_price = $adapt_price_query[0]; $option_price = (float)$adapt_price['options_values_price']; } else { $option_price = 0; } if (stristr($field, '_excl')) { $new_price = round((float)$_GET['new_value'], 4); } else { $tax_query = mysqli_prepared_query("select products_tax from " . TABLE_ORDERS_PRODUCTS . " where orders_products_id = ? and products_tax != 0", "i", array($_GET['pID'])); if (count($tax_query)) { $tax_ = $tax_query[0]; $percent = (float)$tax_['products_tax']; $percent = round(($percent/100), 4); $percent = $percent + 1; $new_price = round(round((float)$_GET['new_value']/$percent, 4), 4); } else { $new_price = round((float)$_GET['new_value'], 4); } } $params = array($new_price, ($new_price - $option_price), $_GET['pID']); mysqli_prepared_query("update " . TABLE_ORDERS_PRODUCTS . " set final_price = ?, products_price = ? where orders_products_id = ?", "ddi", $params); } else { if (tep_not_null($field) && tep_not_null($_GET['new_value'])) { $params = array($_GET['new_value'], $_GET['pID']); mysqli_prepared_query("update " . TABLE_ORDERS_PRODUCTS . " set ".tep_db_input($field)." = ? where orders_products_id = ?", "si", $params); } } } //we have to update the orders_total table tep_db_update_totals($_GET['order']); //that's it, tell the administrator //ENGLISH echo (($action == 'eliminate') ? 'Product eliminated.' : 'Order updated.' ) . "\n" . 'Refresh the browser to see the changes.'; } elseif ($action == 'update_order_field') { $params = array($_GET['new_value'], $_GET['oID']); //mysqli_prepared_query("update ".tep_db_input($_GET['db_table'])." set ".tep_db_input($_GET['field'])." = ? where orders_id = ?", "si", $params); mysqli_prepared_query("update orders set ".tep_db_input($_GET['field'])." = ? where orders_id = ?", "si", $params); //that's it, tell the administrator die( 'Field updated.' . "\n" . 'Refresh the browser to see the changes.' ); } elseif ($action == 'search') { //search products in the db. $params = array('%'.$_GET['keyword'].'%', '%'.$_GET['keyword'].'%', $languages_id); $products_query = mysqli_prepared_query("select distinct p.products_id, pd.products_name, p.products_model from " . TABLE_PRODUCTS_DESCRIPTION . " pd left join " . TABLE_PRODUCTS . " p on (p.products_id = pd.products_id) where (pd.products_name like ? or p.products_model like ?) and pd.language_id = ? and p.products_status = '1' order by pd.products_name asc limit 20", "ssi", $params); if (count($products_query)) { foreach ($products_query as $products) { $results[] = '<a href="javascript:selectProduct(\'' . $products['products_id'] . '\', \'' . addslashes(tep_output_string_protected($products['products_name'])) . '\');">' . $products['products_name'] . (($products['products_model'] != '') ? ' (' . $products['products_model'] . ')' : '') . '</a>' . "\n"; } } else { $results[] = PRODUCTS_SEARCH_NO_RESULTS; } echo implode('<br>' . "\n", $results); } elseif ($action == 'attributes') { //we create an AJAX form $attributes = '<form name="attributes" id="attributes" action="" onsubmit="setAttr(); return false"><input type="hidden" name="products_id" value="' . (int)$_GET['prID'] . '">'; //this part comes integraly from OSC catalog/product_info.php $params = array($_GET['prID'], $languages_id); $products_attributes_query = mysqli_prepared_query("select count(*) as total from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_ATTRIBUTES . " patrib where patrib.products_id = ? and patrib.options_id = popt.products_options_id and popt.language_id = ?", "ii", $params); $products_attributes = $products_attributes_query[0]; if ($products_attributes['total'] > 0) { $attributes .= '<table border="0" cellspacing="0" cellpadding="2" class="dataTableRow" width="100%"><tr><td class="dataTableContent" colspan="2">' . TEXT_PRODUCT_OPTIONS . '</td> </tr>'; $params = array($_GET['prID'], $languages_id); $products_options_name_query = mysqli_prepared_query("select distinct popt.products_options_id, popt.products_options_name from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_ATTRIBUTES . " patrib where patrib.products_id = ? and patrib.options_id = popt.products_options_id and popt.language_id = ? order by popt.products_options_name", "ii", $params); foreach ($products_options_name_query as $products_options_name) { $products_options_array = array(); $params = array($_GET['prID'], $products_options_name['products_options_id'], $languages_id); $products_options_query = mysqli_prepared_query("select pov.products_options_values_id, pov.products_options_values_name, pa.options_values_price, pa.price_prefix from " . TABLE_PRODUCTS_ATTRIBUTES . " pa, " . TABLE_PRODUCTS_OPTIONS_VALUES . " pov where pa.products_id = ? and pa.options_id = ? and pa.options_values_id = pov.products_options_values_id and pov.language_id = ?", "iii", $params); foreach ($products_options_query as $products_options) { $products_options_array[] = array('id' => $products_options['products_options_values_id'], 'text' => $products_options['products_options_values_name']); if ($products_options['options_values_price'] != '0') { $products_options_array[sizeof($products_options_array)-1]['text'] .= ' (' . $products_options['price_prefix'] . $currencies->display_price($products_options['options_values_price'], tep_get_tax_rate($product_info['products_tax_class_id'])) .') '; } } $attributes .= '<tr><td class="main">' . $products_options_name['products_options_name'] . ':</td><td class="main">' . tep_draw_pull_down_menu('atrid_' . $products_options_name['products_options_id'], $products_options_array, $selected_attribute) . '</td></tr>'; } $button = '<span><button type="submit" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-secondary ui-priority-secondary" role="button" aria-disabled="false" style="margin-bottom: 5px;"><span class="ui-button-icon-secondary ui-icon ui-icon-disk"></span><span class="ui-button-text">' . IMAGE_CONFIRM . '</span></button></span>'; $attributes .= '<tr><td colspan="2">' . $button . '</td></tr></table></form>'; } else { $button = '<span><button type="submit" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-secondary ui-priority-secondary" role="button" aria-disabled="false" style="margin-bottom: 5px;" onmouseover="$( this ).addClass( \'ui-state-hover\' );" onmouseout="$( this ).removeClass( \'ui-state-hover\' );"><span class="ui-button-icon-secondary ui-icon ui-icon-disk"></span><span class="ui-button-text">' . IMAGE_CONFIRM . '</span></button></span>'; $attributes .= $button; } echo $attributes; } elseif ($action == 'set_attributes') { $attributes = array(); $products_id = 0; $products_quantity = 0; foreach($_POST as $key => $value) { if ($key == 'products_id') { $products_id = $value; } elseif ($key == 'products_quantity') { $products_quantity = $value; } elseif (stristr($key, 'trid_')) { $attributes[] = array(substr($key, 6), $value); } } $orders_id = $_GET['oID']; $params = array($products_id, $languages_id); $product_info_query = mysqli_prepared_query("select p.products_model, pd.products_name, p.products_price, p.products_tax_class_id from " . TABLE_PRODUCTS . " p left join " . TABLE_PRODUCTS_DESCRIPTION . " pd on p.products_id = pd.products_id where p.products_id = ? and pd.language_id = ?", "ii", $params); $product_info = $product_info_query[0]; if (DISPLAY_PRICE_WITH_TAX == 'true') { $tax_query = mysqli_prepared_query("select tax_rate, tax_description from " . TABLE_TAX_RATES . " where tax_rates_id = ?", "i", array($product_info['products_tax_class_id'])); $tax_ = $tax_query[0]; $tax = $tax_['tax_rate']; $tax_desc = $tax_['tax_description']; } else { $tax = 0; } //OJO, hay que insertar primero el product para saber el orders_products_id... $attribute_price_sum = 0; $attribute_update = false; if (sizeof($attributes) > 0) { $attribute_update = true; for ($j=0; $j<sizeof($attributes); $j++) { $attribute_price_query = mysqli_prepared_query("select options_values_price, price_prefix from " . TABLE_PRODUCTS_ATTRIBUTES . " where products_id = ? and options_id = ? and options_values_id = ?", "iii", array($products_id, $attributes[$j][0], $attributes[$j][1])); $attribute_price = $attribute_price_query[0]; if ($attribute_price['price_prefix'] == '+') { $attribute_price_sum += (float)$attribute_price['options_values_price']; } else { $attribute_price_sum -= (float)$attribute_price['options_values_price']; } $attribute_name_query = mysqli_prepared_query("select products_options_name from " . TABLE_PRODUCTS_OPTIONS . " where products_options_id = ? and language_id = ?", "ii", array($attributes[$j][0], $languages_id)); $attribute_name = $attribute_name_query[0]; $options_name_query = mysqli_prepared_query("select products_options_values_name from " . TABLE_PRODUCTS_OPTIONS_VALUES . " where products_options_values_id = ? and language_id = ?", "ii", array($attributes[$j][1], $languages_id)); $options_name = $options_name_query[0]; $params = array($orders_id, $attribute_name['products_options_name'], $options_name['products_options_values_name'], $attribute_price['options_values_price'], $attribute_price['price_prefix']); mysqli_prepared_query("insert into " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . " (orders_id, orders_products_id, products_options, products_options_values, options_values_price, price_prefix) values (?, 0, ?, ?, ?, ?)", "issds", $params); } } // Per calcolare i prezzi tra le offerte qualora ci siano e siano attive $special_price = mysqli_prepared_query(" SELECT specials_new_products_price FROM " . TABLE_SPECIALS . " WHERE products_id = ? AND status=1", "i", array($products_id)); $new_price = $special_price[0]; if ($new_price) { $final_price = (float)$new_price['specials_new_products_price']+ (float)$attribute_price_sum; } else { //fine specials $final_price = (float)$product_info['products_price'] + (float)$attribute_price_sum; //aggiunta parentesi per specials mod } $params = array($orders_id, $products_id, $product_info['products_model'], $product_info['products_name'], $product_info['products_price'], $final_price, $tax, $products_quantity); mysqli_prepared_query("insert into " . TABLE_ORDERS_PRODUCTS . " (orders_id, products_id, products_model, products_name, products_price, final_price, products_tax, products_quantity) values (?, ?, ?, ?, ?, ?, ?, ?)", "iissdddi", $params); $orders_products_id = tep_db_insert_id(); if ($attribute_update == true){ mysqli_prepared_query("update " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . " set orders_products_id = ? where orders_products_id = 0", "i", array($orders_products_id)); } tep_db_update_totals($orders_id); //that's it, tell the administrator //ENGLISH echo 'Product added.' . "\n" . 'Refresh the browser to see the changes.'; } elseif ($action == 'orders_total_update') { if ($_GET['column'] == 'value') { //get the order's currency $currency_query = mysqli_prepared_query("select currency, currency_value from " . TABLE_ORDERS . " where orders_id = ?", "i", array($_GET['oID'])); $currency = $currency_query[0]; $text = $currencies->format((float)$_GET['new_value'], true, $currency['currency'], $currency['currency_value']); mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set text = ? where orders_id = ? and class = ?", "sis", array($text, $_GET['oID'], $_GET['class'])); } mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set " . tep_db_input($_GET['column']) . " = ? where orders_id = ? and class = ?", "sis", array($_GET['new_value'], $_GET['oID'], $_GET['class'])); tep_db_update_totals($_GET['oID']); //that's it, tell the administrator //ENGLISH echo 'Total updated.' . "\n" . 'Refresh the browser to see the changes.'; } elseif ($action == 'new_order_total') { $sort_order_query = mysqli_prepared_query("select max(sort_order) as maxim from " . TABLE_ORDERS_TOTAL . " where orders_id = ? and class != 'ot_total'", "i", array($_GET['oID'])); $sort_order = $sort_order_query[0]; $new_sort_order = (int)$sort_order['maxim'] + 1; //get the order's currency $currency_query = mysqli_prepared_query("select currency, currency_value from " . TABLE_ORDERS . " where orders_id = ?", "i", array($_GET['oID'])); $currency = $currency_query[0]; $class_query = mysqli_prepared_query("select class from " . TABLE_ORDERS_TOTAL . " where orders_id = ? and class like '%ot_extra_%'", "i", array($_GET['oID'])); $classs = 'ot_extra_' . (count($class_query) + 1); $new_order_total_value_txt = $currencies->format($_GET['value'], true, $currency['currency'], $currency['currency_value']); mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set sort_order = ? where orders_id = ? and class = 'ot_total'", "ii", array(((int)$new_sort_order + 1), $_GET['oID'])); // Fix Taxes if ( isset($_GET['add_tax']) && $_GET['add_tax'] == "1" ) { $selected_tax_query = mysqli_prepared_query("select * from " . TABLE_TAX_RATES . " where tax_class_id = ?", "i", array($_GET['tax_value'])); $selected_tax = $selected_tax_query[0]; $new_tax = $_GET['value'] * ( $selected_tax['tax_rate'] / 100 ); $new_text = $currencies->format($new_tax, true, $currency['currency'], $currency['currency_value']); $orders_total_tax_query = mysqli_prepared_query("select * from " . TABLE_ORDERS_TOTAL . " where orders_id = ? and class = 'ot_tax'", "i", array($_GET['oID'])); $tax_updated = false; foreach ($orders_total_tax_query as $orders_total_tax) { if ( $tax_updated === false ) { if ( $orders_total_tax['title'] == $selected_tax['tax_description'] ) { $new_tax += $orders_total_tax['value']; $new_text = $currencies->format($new_tax, true, $currency['currency'], $currency['currency_value']); mysqli_prepared_query("update " . TABLE_ORDERS_TOTAL . " set text = ?, value = ? where orders_total_id = ? and class = 'ot_tax'", "sdi", array($new_text, number_format($new_tax, 4), $orders_total_tax['orders_total_id'])); $tax_updated = true; break; } } } if ( $tax_updated === false ) { mysqli_prepared_query("insert into " . TABLE_ORDERS_TOTAL . " (orders_id, title, text, value, class, sort_order) values (?, ?, ?, ?, 'ot_tax', 3)", "issd", array($_GET['oID'], $selected_tax['tax_description'], $new_text, $new_tax)); } } mysqli_prepared_query("insert into " . TABLE_ORDERS_TOTAL . " (orders_id, title, text, value, class, sort_order) values (?, ?, ?, ?, ?, ?)", "issdsi", array($_GET['oID'], $_GET['title'] . ':', $new_order_total_value_txt, round((float)$_GET['value'], 4), $classs, $new_sort_order)); tep_exit(); } ?> Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 3, 2014 Author Share Posted May 3, 2014 I found another bug that needs to be fixed. In ./admin/ajax_handler.php around line 251, find: EMAIL_TEXT_INVOICE_URL . ' ' . tep_href_link(FILENAME_ACCOUNT_HISTORY_INFO, 'order_id=' . $insert_id, 'SSL', false) . "\n" . Replace with: EMAIL_TEXT_INVOICE_URL . ' ' . tep_catalog_href_link(FILENAME_ACCOUNT_HISTORY_INFO, 'order_id=' . $insert_id, 'SSL', false) . "\n" . Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
PRH Posted May 3, 2014 Share Posted May 3, 2014 (edited) I'm getting a blank create_order.php page using php version 5.4.28 and osc osc 2.3.3.4 before and after making the modifications on this post. I'm running a heavily modded shop, but mostly on the front end. Have you run into this before? Everything else seems to be working correctly -- and wow -- this is a very well done and useful contrib. Bravo. Hopefully I can get the create order page loaded. Edited May 3, 2014 by prheinrich Quote Link to comment Share on other sites More sharing options...
PRH Posted May 3, 2014 Share Posted May 3, 2014 I've resolved the issue in my post above -- It was related to my usps module. This code just after $http= new httpClient(); in usps.php seemed to clear it up--if anyone else experiences this problem. if (!class_exists('httpClient')) { include('includes/classes/http_client.php'); } Quote Link to comment Share on other sites More sharing options...
pmsmiers Posted May 4, 2014 Share Posted May 4, 2014 I'm getting a blank order_handler.php, I'm using PHP 5.3.3-7 and osCommerce 2.3.3.4 Quote Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 4, 2014 Author Share Posted May 4, 2014 I'm getting a blank order_handler.php, I'm using PHP 5.3.3-7 and osCommerce 2.3.3.4 Hello pmsmiers, Can you check what information the PHP Error Log has? If you can't access the error log then find this line in ./admin/includes/application_top.php error_reporting(E_ALL & ~E_NOTICE); And change it, temporarily, to: error_reporting(E_ALL); ini_set('display_errors', '1'); Then go to order_handler.php page again and reply with the error messages that you get. Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 4, 2014 Author Share Posted May 4, 2014 I'm getting a blank create_order.php page using php version 5.4.28 and osc osc 2.3.3.4 before and after making the modifications on this post. I'm running a heavily modded shop, but mostly on the front end. Have you run into this before? Everything else seems to be working correctly -- and wow -- this is a very well done and useful contrib. Bravo. Hopefully I can get the create order page loaded. I'm glad you found it useful and got it working! Feel free to make suggestions on further improvement/functionality. Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
PRH Posted May 4, 2014 Share Posted May 4, 2014 After day one of use I've noticed "sort product by quantity X" doesn't seem to function. Admittedly I have not spent any time trying to resolve this myself--I plan to. General Suggestions: Sorting data output by table header in Order Handler would be very useful, I hope to expand this myself unless you beat me to it. Using the X ui element in some places to denote an on/off toggle while also using it as it's common delete/remove/close function may confuse some users. Adding on hover tool tips to the sticky/fixed menu on the bottom may be useful to some users--also, some functions lack tool tips that otherwise have them elsewhere (the left-most pencil icon in the first row cell lacks an "Edit" tool tip for example. While I personally feel the interface is intuitive enough--they are all functions common to osc after all--but some may benefit from additional cues. I must again give you a hand for all of the hard work you put into your contributions on a whole, and this one in particular. Thanks! Quote Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 4, 2014 Author Share Posted May 4, 2014 Sorting data output by table header in Order Handler would be very useful, I hope to expand this myself unless you beat me to it. Yeah, I thought about this to but haven't begun to work on it, so feel free to expand, if you want to. ;) Adding on hover tool tips to the sticky/fixed menu on the bottom may be useful to some users Noted, I'll fix this in the next update. After day one of use I've noticed "sort product by quantity X" doesn't seem to function. Admittedly I have not spent any time trying to resolve this myself--I plan to. I have fixed this now, as it is now it apparently only works if you have selected a status sorting from the pull down menu. There's also errors in the code that prevents searching orders by customers_id. And the problem with sorting by products quantity comes from that the get requests has "status=&" when you have the 'status' sorting to 'All orders', if you remove that from the request it should work. And the button only works when a 'Status' sorting has been selected for some reason, so there's some error in the jQuery code as well.. This should be easy to fix though. I must again give you a hand for all of the hard work you put into your contributions on a whole, and this one in particular. Thanks! I'm glad that I can be of help. :) Anyway, I have fixed a couple of other bugs I found and is currently changing all queries to prepared statements. I also noticed I forgot to include instructions on a change to the splitPageResults class. Replace the splitPageResults function in ./admin/includes/classes/split_page_results.php to this: function splitPageResults(&$current_page_number, $max_rows_per_page, &$sql_query, &$query_num_rows, $fixed_number_rows = null) { if (empty($current_page_number)) $current_page_number = 1; if (is_null($fixed_number_rows)) { $pos_to = strlen($sql_query); $pos_from = strpos($sql_query, ' from', 0); $pos_group_by = strpos($sql_query, ' group by', $pos_from); if (($pos_group_by < $pos_to) && ($pos_group_by != false)) $pos_to = $pos_group_by; $pos_having = strpos($sql_query, ' having', $pos_from); if (($pos_having < $pos_to) && ($pos_having != false)) $pos_to = $pos_having; $pos_order_by = strpos($sql_query, ' order by', $pos_from); if (($pos_order_by < $pos_to) && ($pos_order_by != false)) $pos_to = $pos_order_by; $reviews_count_query = tep_db_query("select count(*) as total " . substr($sql_query, $pos_from, ($pos_to - $pos_from))); $reviews_count = tep_db_fetch_array($reviews_count_query); $query_num_rows = $reviews_count['total']; } else { $query_num_rows = $fixed_number_rows; } $num_pages = ceil($query_num_rows / $max_rows_per_page); if ($current_page_number > $num_pages) { $current_page_number = $num_pages; } $offset = ($max_rows_per_page * ($current_page_number - 1)); $sql_query .= " limit " . max($offset, 0) . ", " . $max_rows_per_page; } There's also still a lot of code that needs to be cleaned up, commented (explanation) and indented properly so that it more easily can be interpreted. I will fix this and the other stuff and upload the updated version in the coming week (as long as I have enough time over). Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Guest Posted May 5, 2014 Share Posted May 5, 2014 This one is just what I have been looking for but just can't seem to make it work. I tried to install it on three fresh installs of 2.3.3 Each time I received the same errors, I just replaced the files since I have just installed 2.3.3 Index Fatal error: Call to undefined function xdebug_disable() in C:\Inetpub\wwwroot\ashoptest\includes\application_top.php on line 18 admin Fatal error: Call to undefined function xdebug_disable() in C:\Inetpub\wwwroot\ashoptest\admin\includes\application_top.php on line 18 If I comment out xdebug_disable(); in both applicatin_top.php index.php will come up with ajax working but with errors Notice: Undefined index: products_id in C:\Inetpub\wwwroot\ashoptest\includes\application_top.php on line 515 Warning: array_key_exists() expects parameter 2 to be array, null given in C:\Inetpub\wwwroot\ashoptest\includes\application_top.php on line 515 admin section does not come up with error Parse error: syntax error, unexpected '}' in C:\Inetpub\wwwroot\ashoptest\admin\includes\functions\cache.php on line 40 Sure would like to get this to work and will spend as much time as needed to help, I am working n a site that is so slow, I saw a snail cross the road before the index loaded. Thanks and keep up the good work. Quote Link to comment Share on other sites More sharing options...
pmsmiers Posted May 5, 2014 Share Posted May 5, 2014 Hello pmsmiers, Can you check what information the PHP Error Log has? If you can't access the error log then find this line in ./admin/includes/application_top.php error_reporting(E_ALL & ~E_NOTICE); And change it, temporarily, to: error_reporting(E_ALL); ini_set('display_errors', '1'); Then go to order_handler.php page again and reply with the error messages that you get. Thanks for your help, I get the following error : PHP Parse error: syntax error, unexpected '[' in /storage/web/public/sites/www.espresso-t.nl/admin/order_handler.php on line 577, referer: http://www.espresso-t.nl/admin/orders.php?page=1&oID=42&action=edit Peter Quote Link to comment Share on other sites More sharing options...
Guest Posted May 5, 2014 Share Posted May 5, 2014 Well Donky I posted my comments in the wrong section, Sorry about that. Quote Link to comment Share on other sites More sharing options...
jackhill Posted May 5, 2014 Share Posted May 5, 2014 Dr. Rolex, you oscommerce genius! :) I've been meaning to make contact since you released your last ajax update. Thanks very much for both of these new addons, I'll be installing and testing on my development site tonight Quote Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 5, 2014 Author Share Posted May 5, 2014 This one is just what I have been looking for but just can't seem to make it work. I tried to install it on three fresh installs of 2.3.3 Each time I received the same errors, I just replaced the files since I have just installed 2.3.3 Index Fatal error: Call to undefined function xdebug_disable() in C:\Inetpub\wwwroot\ashoptest\includes\application_top.php on line 18 admin Fatal error: Call to undefined function xdebug_disable() in C:\Inetpub\wwwroot\ashoptest\admin\includes\application_top.php on line 18 If I comment out xdebug_disable(); in both applicatin_top.php index.php will come up with ajax working but with errors Notice: Undefined index: products_id in C:\Inetpub\wwwroot\ashoptest\includes\application_top.php on line 515 Warning: array_key_exists() expects parameter 2 to be array, null given in C:\Inetpub\wwwroot\ashoptest\includes\application_top.php on line 515 admin section does not come up with error Parse error: syntax error, unexpected '}' in C:\Inetpub\wwwroot\ashoptest\admin\includes\functions\cache.php on line 40 Sure would like to get this to work and will spend as much time as needed to help, I am working n a site that is so slow, I saw a snail cross the road before the index loaded. Thanks and keep up the good work. Remove the line with xdebug_disable(); That shouldn't be there, I probably forgot to remove it, it's purpose is to disable stacktraces but if you don't have xdebug installed it will throw a fatal error. Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 5, 2014 Author Share Posted May 5, 2014 (edited) Thanks for your help, I get the following error : PHP Parse error: syntax error, unexpected '[' in /storage/web/public/sites/www.espresso-t.nl/admin/order_handler.php on line 577, referer: http://www.espresso-t.nl/admin/orders.php?page=1&oID=42&action=edit Peter Ok, in ./admin/order_handler.php around line 576, replace this code: if (isset($orders_rows_raw)) $orders_query_total = (int)tep_db_fetch_array(tep_db_query($orders_rows_raw))['total']; else $orders_query_total = tep_db_num_rows(tep_db_query($orders_query_raw)); To This: if (isset($orders_rows_raw)) { $orders_query_total = (int)tep_db_fetch_array(tep_db_query($orders_rows_raw)); $orders_query_total = $orders_query_total['total']; } else { $orders_query_total = tep_db_num_rows(tep_db_query($orders_query_raw)); } Edited May 5, 2014 by Dr. Rolex pmsmiers 1 Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 5, 2014 Author Share Posted May 5, 2014 Dr. Rolex, you oscommerce genius! :) I've been meaning to make contact since you released your last ajax update. Thanks very much for both of these new addons, I'll be installing and testing on my development site tonight He he, well what's life without a little bit of sharing, huh? :rolleyes: Anyway.. I fixed the Sorting data output by table header that Mr. PRH wanted. I'm almost done with updating the queries to prepared statements so I should have an update tomorrow or on Thursday as long as nothing f*cks up.. Tsimi 1 Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
pmsmiers Posted May 6, 2014 Share Posted May 6, 2014 Ok, in ./admin/order_handler.php around line 576, replace this code: if (isset($orders_rows_raw)) $orders_query_total = (int)tep_db_fetch_array(tep_db_query($orders_rows_raw))['total']; else $orders_query_total = tep_db_num_rows(tep_db_query($orders_query_raw)); To This: if (isset($orders_rows_raw)) { $orders_query_total = (int)tep_db_fetch_array(tep_db_query($orders_rows_raw)); $orders_query_total = $orders_query_total['total']; } else { $orders_query_total = tep_db_num_rows(tep_db_query($orders_query_raw)); } That did the trick :) you're the best Quote Link to comment Share on other sites More sharing options...
[email protected] Posted May 6, 2014 Share Posted May 6, 2014 Hello pmsmiers, Can you check what information the PHP Error Log has? If you can't access the error log then find this line in ./admin/includes/application_top.php error_reporting(E_ALL & ~E_NOTICE); And change it, temporarily, to: error_reporting(E_ALL); ini_set('display_errors', '1'); Then go to order_handler.php page again and reply with the error messages that you get. I have the same problem I make the above in aplication_top but the blank page continues only in orfer_handler.php everywhere else it returns me the correct page with the log any idea? Quote regards Lazaros Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 6, 2014 Author Share Posted May 6, 2014 I have the same problem I make the above in aplication_top but the blank page continues only in orfer_handler.php everywhere else it returns me the correct page with the log any idea? If you can't see the error messages on the screen, then you need to find your PHP Error log. Can you check what messages it has? Go to order_handler.php and then check the bottom most messages, look for something that looks like "PHP Fatal Error: [...]". If you don't know where the error log is located, then you can create a new file in ./admin/where_is_error_log.php and put this code in it: <?php die( ini_get('error_log') ); ?> Go to that page in your browser and you will see the location of the log file. Don't forget to remove the file ( where_is_error_log.php that is, not the log file ) when you're done. Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
breakbred101 Posted May 7, 2014 Share Posted May 7, 2014 Way to go Jonas! Way to take the inititive on joining all the useful things that the community has offered into a bundled package. I love my oscommerce but i hate the speed of the backend with all its page loading, and am pretty sure this package is just what i need to be more productive. I wanted to make you aware of at least what I'm experiencing with my 2.3.3 shop. 1. The implementation of the below code into template_top.php breaks the signups/orders graphs on index.php and practically all other javascript/jquery functionality. They dont show up at all. It also broke my tabbed product details on categories.php which uses jquery & javascript. <link rel="stylesheet" type="text/css" href="<?php echo tep_catalog_href_link('ext/jquery/ui/redmond/jquery-ui-1.10.4.css'); ?>"> <script type="text/javascript" src="<?php echo tep_catalog_href_link('ext/jquery/jquery-2.1.0.min.js'); ?>"></script> <script type="text/javascript" src="<?php echo tep_catalog_href_link('ext/jquery/ui/jquery-ui-1.10.4.custom.min.js'); ?>"></script> I simply placed an if/else statement their calling the old scripts if the page is either of these two. Of which does ok, allowing me to at least edit my pages but the graphs still a little flustered. <?php if (($PHP_SELF == 'categories.php') || ($PHP_SELF == 'index.php')) { ?> <link rel="stylesheet" type="text/css" href="<?php echo tep_catalog_href_link('ext/jquery/ui/redmond/jquery-ui-1.8.22.css'); ?>"> <script type="text/javascript" src="<?php echo tep_catalog_href_link('ext/jquery/jquery-1.8.0.min.js'); ?>"></script> <script type="text/javascript" src="<?php echo tep_catalog_href_link('ext/jquery/ui/jquery-ui-1.8.22.min.js'); ?>"></script> <?php }else{ ?> <link rel="stylesheet" type="text/css" href="<?php echo tep_catalog_href_link('ext/jquery/ui/redmond/jquery-ui-1.10.4.css'); ?>"> <script type="text/javascript" src="<?php echo tep_catalog_href_link('ext/jquery/jquery-2.1.0.min.js'); ?>"></script> <script type="text/javascript" src="<?php echo tep_catalog_href_link('ext/jquery/ui/jquery-ui-1.10.4.custom.min.js'); ?>"></script> <?php } ?> 2. In the order_handler.php file is a little out of wack too and doesnt relect the screen shots provided. There is a blue batch delete button at the top of the screen, also an unfunctional "Add a Product" search form is layered over the page in the same position. It throws a "Warning: mysqli_prepare() expects parameter 1 to be mysqli, resource given in .../includes/functions/database.php on line 167 There is no results." when attempted to execute a search If i click the cancel button, this layered form will disappear from the screen until page reloads. 3. There are only three buttons in my action colum, Expand Order, Create New Order & Edit. Wheres the Duplicate Order? 4. The floating toolbar does not contain the circular gear image as the screenshots show. 5. Is the default menu on the right suppose to still show up? It really messes with the layout. You know the one that contains the old buttons. Screenshots dont show it. 6. On the editing of any data on any order, not a single value will change/update on submit. The return back to order_handler after attempting to edit an order brings you back to a white page as well. Quote Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 7, 2014 Author Share Posted May 7, 2014 Hello Eric! h The implementation of the below code into template_top.php breaks the signups/orders graphs on index.php and practically all other javascript/jquery functionality. They dont show up at all. It also broke my tabbed product details on categories.php which uses jquery & javascript. Oh, sorry about that, I forgot to include the new files for jQuery flot. You can easily download and replace the ones you have in ./ext/flot/* Press here to download jQuery Flot 0.8.3 or Click here to visit their website. After that, you can remove your code modification to template_top.php. If your tabbed mod doesn't work, then open the console in Chrome and reply with the error messages that it shows and also expand the error and look at what code it's complaining about, it's the one that's not in the jQuery.js or jQuery-ui.js files. It's usually an easy fix to mod the code to be compatible with the newer versions of jQuery. In the order_handler.php file is a little out of wack too and doesnt relect the screen shots provided. There is a blue batch delete button at the top of the screen, also an unfunctional "Add a Product" search form is layered over the page in the same position. It throws a "Warning: mysqli_prepare() expects parameter 1 to be mysqli, resource given in .../includes/functions/database.php on line 167 There is no results." when attempted to execute a search Could you upload a screenshot of the layout? Regarding the database error, I need to see the track trace Could you also go to Admin => Tools => Server Info and check which MySQL driver you're using. At least in the next update I will upload, it will be require to use the MySQL native driver (mysqlnd). This is the preffered driver which gives the best performance increase for PHP and is required when using mysqli_stmt::get_result. So make sure that you can find both "Mysqli Support" and the "mysqlnd" section in your server info. In order to solve the warning you get you need to enable backtraces on your webserver so I can see where the the function has been called. Line 167 in database.php, is that the mysqli_prepared_query function? If you can't enable backtraces, then you can temporarily replace the mysqli_prepared_query function to this: function mysqli_prepared_query($sql, $typeDef = FALSE, $params = FALSE, $link = "db_link") { global $$link; if($stmt = mysqli_prepare($$link,$sql)){ if(count($params) == count($params,1)){ $params = array($params); $multiQuery = FALSE; } else { $multiQuery = TRUE; } if($typeDef){ $bindParams = array(); $bindParamsReferences = array(); $bindParams = array_pad($bindParams,(count($params,1)-count($params))/count($params),""); foreach($bindParams as $key => $value){ $bindParamsReferences[$key] = &$bindParams[$key]; } array_unshift($bindParamsReferences,$typeDef); $bindParamsMethod = new ReflectionMethod('mysqli_stmt', 'bind_param'); $bindParamsMethod->invokeArgs($stmt,$bindParamsReferences); } $result = array(); foreach($params as $queryKey => $query){ foreach($bindParams as $paramKey => $value){ $bindParams[$paramKey] = $query[$paramKey]; } $queryResult = array(); if(mysqli_stmt_execute($stmt)){ $resultMetaData = mysqli_stmt_result_metadata($stmt); if($resultMetaData){ $stmtRow = array(); $rowReferences = array(); while ($field = mysqli_fetch_field($resultMetaData)) { $rowReferences[] = &$stmtRow[$field->name]; } mysqli_free_result($resultMetaData); $bindResultMethod = new ReflectionMethod('mysqli_stmt', 'bind_result'); $bindResultMethod->invokeArgs($stmt, $rowReferences); while(mysqli_stmt_fetch($stmt)){ $row = array(); foreach($stmtRow as $key => $value){ $row[$key] = $value; } $queryResult[] = $row; } mysqli_stmt_free_result($stmt); } else { $queryResult[] = mysqli_stmt_affected_rows($stmt); } } else { $queryResult[] = FALSE; } $result[$queryKey] = $queryResult; } mysqli_stmt_close($stmt); } else { $result = FALSE; } var_dump(debug_backtrace()); debug_print_backtrace(); if($multiQuery){ return $result; } else { return $result[0]; } } And then try to find where the function has been called where you get the warning. Also, make sure that the "Add a Product" form has a display:none; CSS attribute to it in stylesheet.css under .addProduct. 3. There are only three buttons in my action colum, Expand Order, Create New Order & Edit. Wheres the Duplicate Order? 4. The floating toolbar does not contain the circular gear image as the screenshots show. Make sure that the following section in stylesheet.css has the correct path to the fontawesome files: @font-face { font-family: 'FontAwesome'; src: url('../css/fonts/fontawesome-webfont.eot?v=4.0.3'); src: url('../css/fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../css/fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../css/fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../css/fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } 5. Is the default menu on the right suppose to still show up? It really messes with the layout. You know the one that contains the old buttons. Screenshots dont show it. No, you can remove it in order_handler.php, remove this: <?php $heading = array(); $contents = array(); switch ($action) { case 'delete': $heading[] = array('text' => '<b>' . TEXT_INFO_HEADING_DELETE_ORDER . '</b>'); $contents = array('form' => tep_draw_form('orders', FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action', 'ajax')) . 'oID=' . $oInfo->orders_id . '&action=deleteconfirm')); $contents[] = array('text' => TEXT_INFO_DELETE_INTRO . '<br><br><b>' . $oInfo->customers_name . '</b>'); $contents[] = array('text' => '<br>' . tep_draw_checkbox_field('restock') . ' ' . TEXT_INFO_RESTOCK_PRODUCT_QUANTITY); $contents[] = array('align' => 'center', 'text' => '<br>' . tep_draw_button(IMAGE_DELETE, 'trash') . tep_draw_button(IMAGE_CANCEL, 'close', tep_href_link(FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action')) . 'oID=' . $oInfo->orders_id))); break; default: if (isset($oInfo) && is_object($oInfo)) { $heading[] = array('text' => '<b>[' . $oInfo->orders_id . '] ' . tep_datetime_short($oInfo->date_purchased) . '</b>'); $contents[] = array('align' => 'center', 'text' => tep_draw_button(IMAGE_EDIT, 'document', tep_href_link(FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action')) . 'oID=' . $oInfo->orders_id . '&action=edit'), 'secondary', array('params' => 'class="ajax_disable"')) . tep_draw_button(IMAGE_DELETE, 'trash', tep_href_link(FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action', 'ajax')) . 'oID=' . $oInfo->orders_id . '&action=delete'), 'secondary', array('params' => 'class="ajax_disable"'))); $contents[] = array('align' => 'center', 'text' => tep_draw_button(IMAGE_ORDERS_INVOICE, 'document', tep_href_link(FILENAME_ORDERS_INVOICE, 'oID=' . $oInfo->orders_id), null, array('newwindow' => true), 'secondary', array('params' => 'class="ajax_disable"')) . tep_draw_button(IMAGE_ORDERS_PACKINGSLIP, 'document', tep_href_link(FILENAME_ORDERS_PACKINGSLIP, 'oID=' . $oInfo->orders_id), null, array('newwindow' => true)) . tep_draw_button(IMAGE_CREATE_ORDER, 'document', tep_href_link(FILENAME_CREATE_ORDER, 'Customer=' . $oInfo->customers_id), 'secondary', array('params' => 'class="ajax_disable"'))); $contents[] = array('text' => '<br />' . TEXT_DATE_ORDER_CREATED . ' ' . tep_date_short($oInfo->date_purchased)); if (tep_not_null($oInfo->last_modified)) $contents[] = array('text' => TEXT_DATE_ORDER_LAST_MODIFIED . ' ' . tep_date_short($oInfo->last_modified)); $contents[] = array('text' => '<br />' . ' ' . $oInfo->payment_method); } break; } if ( (tep_not_null($heading)) && (tep_not_null($contents)) ) { echo ' <td width="20%" valign="top" id="defaultMenu">' . "\n"; $box = new box; echo $box->infoBox($heading, $contents); echo ' </td>' . "\n"; //tep_exit(); } else { echo ' <td id="defaultMenu"></td>'; } ?> And this in ajax_handler.php <?php switch ($action) { case 'delete': $heading[] = array('text' => '<b>' . TEXT_INFO_HEADING_DELETE_ORDER . '</b>'); $contents = array('form' => tep_draw_form('orders', FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action', 'cID', 'ajax')) . 'oID=' . (int)$oID . '&action=deleteconfirm')); $contents[] = array('text' => TEXT_INFO_DELETE_INTRO . '<br><br><b>' . $order->customer['name'] . '</b>'); $contents[] = array('text' => '<br>' . tep_draw_checkbox_field('restock') . ' ' . TEXT_INFO_RESTOCK_PRODUCT_QUANTITY); $contents[] = array('align' => 'center', 'text' => '<br>' . tep_draw_button(IMAGE_DELETE, 'trash') . tep_draw_button(IMAGE_CANCEL, 'close', tep_href_link(FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action')) . 'oID=' . (int)$oID))); break; default: if (isset($oID)) { $heading[] = array('text' => '<b>[' . $oID . '] ' . tep_datetime_short($orders['date_purchased']) . '</b>'); $contents[] = array('align' => 'center', 'text' => tep_draw_button(IMAGE_EDIT, 'document', tep_href_link(FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action')) . 'oID=' . $oID . '&action=edit'), 'secondary', array('params' => 'class="ajax_disable"')) . tep_draw_button(IMAGE_DELETE, 'trash', tep_href_link(FILENAME_ORDERS_HANDLER, tep_get_all_get_params(array('oID', 'action', 'ajax')) . 'oID=' . (int)$oID . '&action=delete'), 'secondary', array('params' => 'class="ajax_disable"'))); $contents[] = array('align' => 'center', 'text' => tep_draw_button(IMAGE_ORDERS_INVOICE, 'document', tep_href_link(FILENAME_ORDERS_INVOICE, 'oID=' . (int)$oID), null, array('newwindow' => true), 'secondary', array('params' => 'class="ajax_disable"')) . tep_draw_button(IMAGE_ORDERS_PACKINGSLIP, 'document', tep_href_link(FILENAME_ORDERS_PACKINGSLIP, 'oID=' . (int)$oID), null, array('newwindow' => true)) . tep_draw_button(IMAGE_CREATE_ORDER, 'document', tep_href_link(FILENAME_CREATE_ORDER, 'Customer=' . $orders['customers_id']), 'secondary', array('params' => 'class="ajax_disable"'))); $contents[] = array('text' => '<br />' . TEXT_DATE_ORDER_CREATED . ' ' . tep_date_short($orders['date_purchased'])); if (tep_not_null($orders['last_modified'])) $contents[] = array('text' => TEXT_DATE_ORDER_LAST_MODIFIED . ' ' . tep_date_short($orders['last_modified'])); $contents[] = array('text' => '<br />' . ' ' . $orders['payment_method']); } break; } if ( (tep_not_null($heading)) && (tep_not_null($contents)) ) { echo ' <td width="20%" valign="top" id="defaultMenu">' . "\n"; $box = new box; echo $box->infoBox($heading, $contents); echo ' </td>' . "\n"; tep_exit(); } ?> 6. On the editing of any data on any order, not a single value will change/update on submit. The return back to order_handler after attempting to edit an order brings you back to a white page as well. It sounds like order_handler.js isn't loaded correctly, check in Chrome Developer Tools under the tab "Sources" that you can find admin/js/order_handler.js and also make sure that there are no error messages in the console. Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Scheers Posted May 8, 2014 Share Posted May 8, 2014 First i want to say that this is a very nice contribution. but..... there is always a but..... :thumbsup: I have made all of the changes that are in this topic but i can't add a new product... If i click on add new product everything is going wel... i select a product and the amount and then ok...but there comes the problem... it doesn't add the product. there is another problem if i change the amount of 1 producht the total changes to 0.00$... Where am i going wrong? greeting from the netherlands (sorry about my thereble english) Quote Link to comment Share on other sites More sharing options...
Dr. Rolex Posted May 8, 2014 Author Share Posted May 8, 2014 First i want to say that this is a very nice contribution. but..... there is always a but..... :thumbsup: I have made all of the changes that are in this topic but i can't add a new product... If i click on add new product everything is going wel... i select a product and the amount and then ok...but there comes the problem... it doesn't add the product. there is another problem if i change the amount of 1 producht the total changes to 0.00$... Where am i going wrong? greeting from the netherlands (sorry about my thereble english) Could you open the "Network" tab in Chrome Developer Tools, go to the Edit Order Page. Then clear the view by pressing the small icon to the top left under the Network Tab. Now, try again to either add a product or change the amount. Press Filter and select XHR. There should be two rows, press the top one, the one that makes the request to orders_ajax.php. Then Copy the Request URL and Post it back here. It should look something like this: Request URL:https://[YOUR_SERVER]/admin/orders_ajax.php?action=update_product&pID=303&order=318&field=products_quantity&new_value=2 Quote osC OpenSSL Encryption with jCryptionSupport Forum jQuery/Ajax Advanced Statistics 2.3Support Forum jQuery/Ajax Advanced Order Handler 2.3.3Support ForumjQuery/Ajax Advanced Caching System 2.3.3Support ForumjQuery/Ajax Fast checkout/Shopping Cart/Login/Create Account 2.3.3Support ForumjQuery/Ajax Shopping Cart 2.3.3Support ForumjQuery/Ajax Dynamic Checkout 2.3.3Support ForumjQuery/Ajax Mini Cart for osCommerce 2.3.3Support ForumjQuery Cycle What's New InfoboxAuto Out Of Stock CSS Image OverlayjQuery-UI Autocomplete Product Search with Links & MySQLi support for osCommerce 2.X Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.