Jump to content


  • Content count

  • Joined

  • Last visited

1 Follower

Profile Information

  • Real Name
    Andrew Edmond
  1. Well, after realizing that the CGI/HTML version of qcostcgi.cgi is breaking down rapidly, I decided to whip up some quick XML Rate Quote service for the community to use. The qcostcgi.cgi program at UPS is what ALL OSCOMMERCE STORES RUN ON FOR UPS, and it's a DEPRECATED PROGRAM. My stores are getting around a 75% success rate on quotes, which is ridiculous. The amount of money we lost due to this is very, very large :) I'm not a great programmer. I can hack out code very quickly though. The contribution below should be considered AN EMERGENCY REPLACEMENT ONLY. My hope is that Fritz (the author of UPS Choice) can take a look at this code and whip it into production quality code. Either way I consider this a MAJOR ISSUE with OSCommerce. UPS can turn of the qcostcgi.cgi tomorrow (as they've indicated they will soon) and all OSCommerce stores relying on this antiquated rate method will be SCREWED. All that being said, the below code is provided AS IS. It works on my Oct 02 2.2-CVS, and is based on UPS 1.47. It does NOT have the nice UPS Choice features. Hopefully someone will resolve that when they repackage this in a new module. I have no desire to maintain, improve, or issue new versions of this source code. I've written major modules for OSCommerce in the past, and I simply do not have the time to make this module what you all want it to be. Someone else will have to pickup the ball on this, but the good news is that I've already done most of the architecture, now it just needs to be brought up for production use on servers with more error testing, and better UPS Choice style administration function. Lastly, you'll need CURL support with your PHP installation for the XML parsing to work correctly. All that being said, here is the code... <?php /* $Id: ups.php,v 1.47 2002/12/09 19:07:17 dgw_ Exp $ osCommerce, Open Source E-Commerce Solutions http://www.oscommerce.com Copyright © 2002 osCommerce Released under the GNU General Public License MODIFIED: Andrew Edmond <edmond@aravia.com> June 18th, 2003 CREDITS: Modified from UPSTrack Class by Jon Whitcraft <jon_whitcraft@sweetwater.com> Updated by Chris Lee (http://www.neox.net) This module is based on ups.php v1.47, NOT UPS Choice. The author of UPS Choice for OSCommerce should be contacted to migrate changes to XML rate information into that program. THERE IS NO SUPPORT PROVIDED FOR THIS UPGRADE / MODIFICATION AND IS PROVIDED "AS IS!". UPS Choice is currently only running CGI/HTML quotes and is a service UPS might stop ANYTIME. Write osc@sonnybargercycles.com to ask UPS Choice author to upgrade to XML rate quoting! NOTE: YOU HAVE TO EDIT the class "upsRate" below with your UPS Login/Password and Access Code. Get your access code at: http://www.ec.ups.com/ */ class ups { var $code, $title, $descrption, $icon, $enabled, $types; function ups() { $this->code = 'ups'; $this->title = MODULE_SHIPPING_UPS_TEXT_TITLE; $this->description = MODULE_SHIPPING_UPS_TEXT_DESCRIPTION; $this->icon = DIR_WS_ICONS . 'shipping_ups.gif'; $this->enabled = MODULE_SHIPPING_UPS_STATUS; /* * You can remove or comment out the type of packages that you do not wish to ship */ $this->types = array ( '01' => 'UPS Next Day Air', '02' => 'UPS 2nd Day Air', // '03' => 'UPS Ground', // '07' => 'UPS Worldwide Express', '08' => 'UPS Worldwide Expedited', // '11' => 'UPS Standard', // '12' => 'UPS 3 Day Select', // '13' => 'UPS Next Day Air Saver', // '14' => 'UPS Next Day Air Early A.M.', // '54' => 'UPS Worldwide Express Plus', '59' => 'UPS 2nd Day Air A.M.', // '64' => '', // '65' => 'UPS Express Saver', ); } function quote($method = '') { global $HTTP_POST_VARS, $order, $shipping_weight, $shipping_num_boxes, $cart; if ( (tep_not_null($method)) && (isset($this->types[$method])) ) { $prod = $method; } else { $prod = 'GND'; } if ($method) $this->_upsAction('3'); // return a single quote $this->_upsProduct($prod); $country_name = tep_get_countries(STORE_COUNTRY, true); $this->_upsOrigin(STORE_ORIGIN_ZIP, $country_name['countries_iso_code_2']); $this->_upsDest($order->delivery['postcode'], $order->delivery['country']['iso_code_2']); $this->_upsRate(MODULE_SHIPPING_UPS_PICKUP); $this->_upsContainer(MODULE_SHIPPING_UPS_PACKAGE); $this->_upsWeight($shipping_weight); $this->_upsRescom(MODULE_SHIPPING_UPS_RES); $upsQuote = $this->_upsGetQuote(); if (count($upsQuote)) { $this->quotes = array('id' => $this->code, 'module' => $this->title . ' (' . $shipping_num_boxes . ' x ' . $shipping_weight . 'lbs)'); $methods = array(); if (($order->info['total'] > 50) && (($method == '')||($method == 'QFREEGND'))) { $methods[] = array('id' => "QFREEGND", 'title' => "Free UPS Shipping", 'cost' => '0.00'); } $qsize = count($upsQuote); for ($i=0; $i<$qsize; $i++) { $type = $upsQuote[$i]['service']; $cost= number_format($upsQuote[$i]['basic'],2); if ($this->types[$type] == "") continue; $methods[] = array('id' => $type, 'title' => $this->types[$type], 'cost' => (SHIPPING_HANDLING + $cost) * $shipping_num_boxes); } $this->quotes['methods'] = $methods; } else { $this->quotes = array('module' => $this->title, 'error' => 'An error occured with the UPS shipping calculations. ' . '<br>This is a problem with the UPS server. Reload your browser ' . 'to try the UPS server again. If not, please call us ' . 'and we can manually process your order!'); } if (tep_not_null($this->icon)) $this->quotes['icon'] = tep_image($this->icon, $this->title); return $this->quotes; } function check() { if (!isset($this->_check)) { $check_query = tep_db_query("select configuration_value from " . TABLE_CONFIGURATION . " where configuration_key = 'MODULE_SHIPPING_UPS_STATUS'"); $this->_check = tep_db_num_rows($check_query); } return $this->_check; } function install() { tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Enable UPS Shipping', 'MODULE_SHIPPING_UPS_STATUS', '1', 'Do you want to offer UPS shipping?', '6', '9', now())"); tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('UPS Pickup Method', 'MODULE_SHIPPING_UPS_PICKUP', 'CC', 'How do you give packages to UPS? CC - Customer Counter, RDP - Daily Pickup, OTP - One Time Pickup, LC - Letter Center, OCA - On Call Air', '6', '10', now())"); tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('UPS Packaging?', 'MODULE_SHIPPING_UPS_PACKAGE', 'CP', 'CP - Your Packaging, ULE - UPS Letter, UT - UPS Tube, UBE - UPS Express Box', '6', '11', now())"); tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Residential Delivery?', 'MODULE_SHIPPING_UPS_RES', 'RES', 'Quote for Residential (RES) or Commercial Delivery (COM)', '6', '12', now())"); } function remove() { tep_db_query("delete from " . TABLE_CONFIGURATION . " where configuration_key in ('" . implode("', '", $this->keys()) . "')"); } function keys() { return array('MODULE_SHIPPING_UPS_STATUS', 'MODULE_SHIPPING_UPS_PICKUP', 'MODULE_SHIPPING_UPS_PACKAGE', 'MODULE_SHIPPING_UPS_RES'); } function _upsProduct($prod){ $this->_upsProductCode = $prod; } function _upsOrigin($postal, $country){ $this->_upsOriginPostalCode = $postal; $this->_upsOriginCountryCode = $country; } function _upsDest($postal, $country){ $postal = str_replace(' ', '', $postal); if ($country == 'US') { $this->_upsDestPostalCode = substr($postal, 0, 5); } else { $this->_upsDestPostalCode = $postal; } $this->_upsDestCountryCode = $country; } function _upsRate($foo) { switch ($foo) { case 'RDP': $this->_upsRateCode = 'Regular+Daily+Pickup'; break; case 'OCA': $this->_upsRateCode = 'On+Call+Air'; break; case 'OTP': $this->_upsRateCode = 'One+Time+Pickup'; break; case 'LC': $this->_upsRateCode = 'Letter+Center'; break; case 'CC': $this->_upsRateCode = 'Customer+Counter'; break; } } function _upsContainer($foo) { switch ($foo) { case 'CP': // Customer Packaging $this->_upsContainerCode = '02'; break; case 'ULE': // UPS Letter Envelope $this->_upsContainerCode = '01'; break; case 'UT': // UPS Tube $this->_upsContainerCode = '03'; break; case 'UEB': // UPS Express Box $this->_upsContainerCode = '21'; break; case 'UW25': // UPS Worldwide 25 kilo $this->_upsContainerCode = '24'; break; case 'UW10': // UPS Worldwide 10 kilo $this->_upsContainerCode = '25'; break; } } function _upsWeight($foo) { $this->_upsPackageWeight = $foo; } function _upsRescom($foo) { switch ($foo) { case 'RES': // Residential Address $this->_upsResComCode = '1'; break; case 'COM': // Commercial Address $this->_upsResComCode = '2'; break; } } function _upsAction($action) { /* 3 - Single Quote 4 - All Available Quotes */ $this->_upsActionCode = $action; } function _upsGetQuote() { if (!isset($this->_upsActionCode)) $this->_upsActionCode = '4'; $upsXMLRate = new upsRate($this->_upsOriginPostalCode, '', $this->_upsOriginCountryCode); $upsXMLRateQuote = $upsXMLRate->rate('', $this->_upsDestPostalCode, '',$this->_upsDestCountryCode, $this->_upsPackageWeight, 1, $this->_upsContainerCode); return $upsXMLRateQuote; } } class upsRate { // You need to create userid ... at http://www.ec.ups.com var $userid = "your user id"; var $passwd = "your password"; var $accesskey = "your access code"; var $upstool='https://www.ups.com/ups.app/xml/Rate'; var $request; //'rate' for single service or 'shop' for all possible services var $service; var $pickuptype='01'; // 01 daily pickup /* Pickup Type 01- Daily Pickup 03- Customer Counter 06- One Time Pickup 07- On Call Air 11- Suggested Retail Rates 19- Letter Center 20- Air Service Center */ var $residential; //ship from location or shipper var $s_zip; var $s_state; var $s_country; //ship to location var $t_zip; var $t_state; var $t_country; //package info var $package_type; // 02 customer supplied package var $weight; var $error=0; var $errormsg; var $xmlarray = array(); var $xmlreturndata = ""; function upsRate($szip,$sstate,$scountry) { // init function $this->s_zip = $szip; $this->s_state = $sstate; $this->s_country = $scountry; } function construct_request_xml(){ $xml="<?xml version="1.0"?> <AccessRequest xml:lang="en-US"> <AccessLicenseNumber>$this->accesskey</AccessLicenseNumber> <UserId>$this->userid</UserId> <Password>$this->passwd</Password> </AccessRequest> <?xml version="1.0"?> <RatingServiceSelectionRequest xml:lang="en-US"> <Request> <TransactionReference> <CustomerContext>Rating and Service</CustomerContext> <XpciVersion>1.0001</XpciVersion> </TransactionReference> <RequestAction>Rate</RequestAction> <RequestOption>$this->request</RequestOption> </Request> <PickupType> <Code>$this->pickuptype</Code> </PickupType> <Shipment> <Shipper> <Address> <StateProvinceCode>$this->s_state</StateProvinceCode> <PostalCode>$this->s_zip</PostalCode> <CountryCode>$this->s_country</CountryCode> </Address> </Shipper> <ShipTo> <Address> <StateProvinceCode>$this->t_state</StateProvinceCode> <PostalCode>$this->t_zip</PostalCode> <CountryCode>$this->t_country</CountryCode> <ResidentialAddressIndicator>$this->residential</ResidentialAddressIndicator> </Address> </ShipTo> <Service> <Code>$this->service</Code> </Service> <Package> <PackagingType> <Code>$this->package_type</Code> <Description>Package</Description> </PackagingType> <Description>Rate Shopping</Description> <PackageWeight> <Weight>$this->weight</Weight> </PackageWeight> </Package> <ShipmentServiceOptions/> </Shipment> </RatingServiceSelectionRequest>"; return $xml; } function rate($service='', $tzip, $tstate='', $tcountry='US', $weight, $residential=0, $packagetype='02') { if($service=='') $this->request = 'shop'; else $this->request = 'rate'; $this->service = $service; $this->t_zip = $tzip; $this->t_state= $tstate; $this->t_country = $tcountry; $this->weight = $weight; $this->residential=$residential; $this->package_type=$packagetype; $this->__runCurl(); $this->xmlarray = $this->_get_xml_array($this->xmlreturndata); //check if error occurred if($this->xmlarray=="") { $this->error=0; $this->errormsg="Unable to retrieve the Shipping info"; return NULL; } $values = $this->xmlarray[RatingServiceSelectionResponse][Response][0]; if($values[ResponseStatusCode] == 0) { $this->error=$values[Error][0][ErrorCode]; $this->errormsg=$values[Error][0][ErrorDescription]; return NULL; } return $this->get_rates(); } function __runCurl() { $y = $this->construct_request_xml(); $ch = curl_init(); curl_setopt ($ch, CURLOPT_URL,"$this->upstool"); /// set the post-to url (do not includethe ?query+string here!) curl_setopt ($ch, CURLOPT_HEADER, 0); /// Header control curl_setopt ($ch, CURLOPT_POST, 1); /// tell it to make a POST, not a GET curl_setopt ($ch, CURLOPT_POSTFIELDS, "$y"); /// put the querystring here startingwith "?" curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); /// This allows the output to be setinto a variable $xyz curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); /// some php version cause error withcurl without this line $this->xmlreturndata = curl_exec ($ch); /// execute the curl session and return theoutput to a variable $xyz curl_close ($ch); /// close the curl session } function __get_xml_array($values, &$i) { $child = array(); if ($values[$i]['value']) array_push($child, $values[$i]['value']); while (++$i < count($values)) { switch ($values[$i]['type']) { case 'cdata': array_push($child, $values[$i]['value']); break; case 'complete': $name = $values[$i]['tag']; $child[$name]= $values[$i]['value']; if($values[$i]['attributes']) { $child[$name] = $values[$i]['attributes']; } break; case 'open': $name = $values[$i]['tag']; $size = sizeof($child[$name]); if($values[$i]['attributes']) { $child[$name][$size] = $values[$i]['attributes']; $child[$name][$size] = $this->__get_xml_array($values, $i); } else { $child[$name][$size] = $this->__get_xml_array($values, $i); } break; case 'close': return $child; break; } } return $child; } function _get_xml_array($data) { $values = array(); $index = array(); $array = array(); $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parse_into_struct($parser, $data, $values, $index); xml_parser_free($parser); $i = 0; $name = $values[$i]['tag']; $array[$name] = $values[$i]['attributes']; $array[$name] = $this->__get_xml_array($values, $i); return $array; } function get_rates() { // $retArray = array('service'=>'','basic'=>0,'option'=>0,'total'=>0,'days'=>'','time'=>''); $retArray=array(); $values = $this->xmlarray[RatingServiceSelectionResponse][RatedShipment]; $ct = count($values); for($i=0;$i<$ct;$i++) { $current=&$values[$i]; $retArray[$i]['service'] = $current[service][0][code]; $retArray[$i]['basic'] = $current[TransportationCharges][0][MonetaryValue]; $retArray[$i]['option'] = $current[serviceOptionsCharges][0][MonetaryValue]; $retArray[$i]['total'] = $current[TotalCharges][0][MonetaryValue]; $retArray[$i]['days'] = $current[GuaranteedDaysToDelivery]; $retArray[$i]['time'] = $current[scheduledDeliveryTime]; } unset($values); return $retArray; } } ?>
  2. That is the current and final version that I'll be working on. The module was originally written by another, I cleaned it up siginficantly, and now the hot potato will go to someone else (hopefully :). Andrew
  3. You have to modify the SQL statements in cross_sale.php. Add: where language_id = 1 To the tables that have multiple langauges. Andrew
  4. What, me, only think about the english language? NEVER! I'm AMERICAN! We are globally conscious! ;) Linda is right, you want to alter the code to only look for language_id=1 (or whatever your preferred language is) Andrew
  5. nero

    Free Call for Price v4.0

    I'm getting this message when downloading Free Call For price: At this page: https://secure.sashbox.net/thewebmakerscorn...e2fbfc5a7c96432 Can you please fix this so I can download this module? Thanks!!!
  6. I was about to make $10, $20, $50, and $100 gift certificate graphics for use with Ian's GV system, but I thought I'd check to see if anyone else out there had some nice ones to share? Andrew
  7. Topic has been moved to this post: http://forums.oscommerce.com/viewtopic.php?p=69302 Which has information about the new ALPHA release of the User Tracking Contribution. Please disregard previous poster's modifications, I've incorporated that and much more into the new release.
  8. Here is the link to the new release of the User Tracking modifications for your OSC 2.2-CVS stores. This release was built on a mid July version of OSC, so I need help testing this on more recent releases. You can download the file here: http://www.aravia.com/user_tracking-0.2.tar.gz This version should be fully functional and has been tested on over a month worth of traffic on a heavily visited site. However, please submit any bug reports to me at osc@aravia.com. New changes include the purging of data, displaying only 50 user sessions at a time, MUCH MUCH better shopping cart navigation, and a lot of new statistics. Enjoy and good luck! ---------------------------- 1. INTRODUCTION ----------------------------------------------------------------------- This set of modifications will display the activity of each user as they come to your site, the links they clicked, and the time they spent between each page. You also will be able to view the cart contents of their shopping cart historically, and will be able to surf between days to see tracking data historically. You'll need to copy THREE files, and modify slightly FOUR files. 2. TODO ------------------------------------------------------------------------------- Summary statistics: number of users, average time per visit, highest ranked pages, lowest rank pages, most common last pages, most common first pages. Better historical data navigation.
  9. Well, the purge has been added, but I also have "Original Referer" working. I still have to add "View 50 At a Time" with a page by page selector, as well as I have to add "global statistics". Once those are done, I have to clean up the code and we should have a alpha release. Andrew
  10. Tyler, I've been tweaking this script on my machine for the past weeks as I've been getting more experience with the data we might be able to collect. It's already a lot better than it was. In a few more weeks I'll release a new set of features to everyone. Andrew
  11. nero

    Category Tabs V1.1

    Any chance for DHTML drop downs for sub categories? Andrew
  12. Hey thanks! Hopefully this weekend I'll be able to put some work into more "average" / "median" / "trend" analysis of this data. I'm not promising anything, but hopefully it will turn out pretty good. As it is, I'm satisified it's a pretty decent modification that I personally can't live without :) Let me know if you have any suggestions.
  13. Well, you are right. However... I think there is intrinsic value to integrating the analysis functions right into the software. First off you don't have to buy or configure a log analyzer if you use this mod - it is a fully integrated statistics package (and will get better in that regards). Second, you can track individual users by session, as well as associated session data - most importantly the shopping cart. Abandoned shopping carts are a commerce killer and knowing the habits and trends that make that happen is how you can improve sales. No log analysis package will help you with that. Andrew
  14. Who's online performs a "insert into whos_online" as well as "delete from whos_online where ... " EVERY time a user loads a page. MySQL is a pretty fast database. I've had databases with more than 800 million rows across 30 columns with 2 keys and the speed is incredible still, even on moderately complicated SQL select queries. The problem comes from complicated SQL select queries (reads) when the database is too busy writing. This is not a MySQL problem as much as it's a problem with the technology of hard drive manufacture. For OSC to experience a performance loss due to this modification would take on the order of 2 million rows of user_tracking data with several hundreds of people surfing an OSC site at the same time. The only reason you'd want to clean this database is if you had STORAGE requirements (as on a shared server), or if you really didn't like stale data hanging around. For me, this data is never stale, how my users are navigating my site is the number one most important thing to me. I might just not recommend the user tracking reports to 1) shared server folk, 2) those with hundreds of users tracking per second, all in favor or a more expensive log analysis package. Andrew
  15. CC - I write these hacks for myself because they aren't available. I struggle to let them out into open source because I'm so greedy by nature :) I have to convince myself that I'll get more joy out of letting them out into the open that hoarding them for myself ;) hehehehe... Yeah, in the beta version I'll have a "CLEAN HISTORY" button that will nuke out any data older than 72 hours. Doing it automatically is a possibility as well, but would decrease performance (it would have to process the delete from table_name where time < now() - XX hours logic on EVERY page load). I'll see what users ask for and then add that. I think the CLEAN HISTORY button would be better by far, but what do I know. Andrew