ash_usps

New USPS module for V4 Domestic RateRequest API and V2 International Rate Request.

Defined (1)

The class is defined in the following location(s).

/wpsc-shipping/usps_20.php  
  1. class ash_usps { 
  2.  
  3. /** 
  4. * The USPS User ID for the API user Account 
  5. * @var string 
  6. */ 
  7. var $usps_id; 
  8.  
  9. /** 
  10. * The USPS password for the API user account 
  11. * @var string 
  12. * @deprecated Deprecated since 2.0 
  13. */ 
  14. var $usps_password; 
  15.  
  16. /** 
  17. * The name that the USPS class identifies itself as to internal systems 
  18. * default "usps" Don't change unless you know what you are doing! 
  19. * @var string 
  20. */ 
  21. var $internal_name = "usps"; 
  22.  
  23. /** 
  24. * The external name that the USPS class identifies itself. 
  25. * This is the "prettier" version of $internal_name to be shown to end-users. 
  26. * @var string 
  27. */ 
  28. var $name = "USPS"; 
  29.  
  30. /** 
  31. * This flag is used by WP-E-Commerce to denote whether or not 
  32. * it accesses an external API to provide shipping rates and requires cURL 
  33. * @var boolean 
  34. */ 
  35. var $is_external = TRUE; 
  36.  
  37. /** 
  38. * This flag is used by WP-E-Commerce to denote whether or not 
  39. * it requires a zipcode to process the quote. 
  40. * @var boolean 
  41. */ 
  42. var $needs_zipcode = TRUE; 
  43.  
  44. /** 
  45. * This flag is used by USPS (Not locked to WPEC) to denote which 
  46. * endpoint / rating environment it is to use. 
  47. * True = Use Testing Environment API Endpoint 
  48. * False = Use Production Environment API Endpoint 
  49. * @since 2.0 
  50. * @var boolean 
  51. */ 
  52. var $use_test_env = FALSE; 
  53.  
  54. /** 
  55. * This stores an ASHShipment instance object used in rating 
  56. * @var ASHShipment|Null 
  57. */ 
  58. var $shipment = NULL; 
  59.  
  60.  
  61. /** 
  62. * Constructor for USPS class 
  63. * Automatically loads services that are available into the class instance 
  64. * @since 1.0 
  65. */ 
  66. function ash_usps() { 
  67. $this->_load_services(); 
  68. return TRUE; 
  69.  
  70. /** 
  71. * retrieves the USPS ID, not used 
  72. * This function only exists due to legacy code 
  73. * @since 1.0 
  74. * @deprecated deprecated since version 2.0 
  75. * @return string 
  76. */ 
  77. function getID() { 
  78. return $this->$usps_id; 
  79.  
  80. /** 
  81. * Sets the USPS ID, not used 
  82. * This function only exists due to legacy code, unused 
  83. * @since 1.0 
  84. * @param int $id 
  85. * @deprecated deprecated since version 2.0 
  86. */ 
  87. function setId( $id ) { 
  88. $this->$usps_id = $id; 
  89.  
  90. /** 
  91. * Retrieves the external display name for the module 
  92. * @since 1.0 
  93. * @return string 
  94. */ 
  95. function getName() { 
  96. return $this->name; 
  97.  
  98. /** 
  99. * Retrieves internal name of the module 
  100. * @since 1.0 
  101. * @return string 
  102. */ 
  103. function getInternalName() { 
  104. return $this->internal_name; 
  105.  
  106. /** 
  107. * Houses the list of services available to USPS API. 
  108. * The majority is commented out until a proper 
  109. * Service->Package map can be created 
  110. * @since 2.0 
  111. */ 
  112. function _load_services() { 
  113. $services = array( 
  114. // "Online Only *"=>"ONLINE",  
  115. // "All Services"=>"ALL",  
  116. // "Library Mail"=>"LIBRARY",  
  117. __( "First Class", 'wpsc' ) => "FIRST CLASS",  
  118. __( "Standard Post *", 'wpsc' ) => "STANDARD POST",  
  119. // "First Class Metered"=>"FIRST CLASS METERED",  
  120. // "First Class Commercial"=>"FIRST CLASS COMMERCIAL",  
  121. // "First Class Hold For Pickup Commercial"=>"FIRST CLASS HFP COMMERCIAL",  
  122. __( "Priority Mail *", 'wpsc' ) => "PRIORITY",  
  123. // "Priority Commercial"=>"PRIORITY COMMERCIAL",  
  124. // "Priority CPP"=>"PRIORITY CPP",  
  125. // "Priority Hold For Pickup Commercial"=>"PRIORITY HFP COMMERCIAL",  
  126. // "Priority Hold For Pickup CPP"=>"PRIORITY HFP CPP",  
  127. __( "Priority Express", 'wpsc' ) => "PRIORITY EXPRESS",  
  128. // "Priority Express Commerical"=>"PRIORITY EXPRESS COMMERCIAL",  
  129. // "Priority Express CPP"=>"PRIORITY EXPRESS CPP",  
  130. // "Priority Express SH"=>"PRIORITY EXPRESS SH",  
  131. // "Priority Express SH Commercial"=> "PRIORITY EXPRESS SH COMMERCIAL",  
  132. // "Priority Express Hold for Pickup"=> "PRIORITY EXPRESS HFP",  
  133. // "Priority Express Hold for Pickup Commercial"=>"PRIORITY EXPRESS HFP COMMERCIAL" 
  134. // "Priority Express Hold for Pickup CPP"=>"PRIORITY EXPRESS HFP CPP" 
  135. __( "Media Mail **", 'wpsc' ) => "MEDIA" ,  
  136. ); 
  137. $this->services = $services; 
  138.  
  139.  
  140. /** 
  141. * Provides the appropriate endpoint for the API to use to 
  142. * retrieve rates from USPS 
  143. * @since 2.0 
  144. * @param boolean $intl Flag denotes if we are getting international rates or not, Default FALSE 
  145. * @return string The endpoint / URL 
  146. */ 
  147. function _get_endpoint( $intl = FALSE ) { 
  148. $end_points = array( 
  149. "prod" => array( "server" => "production.shippingapis.com",  
  150. "dll" => "ShippingAPI.dll" 
  151. ),  
  152. "test" => array( "server" => "testing.shippingapis.com",  
  153. "dll" => "ShippingAPITest.dll" 
  154. ),  
  155. ); 
  156.  
  157. $api = "API=RateV4"; //"API=" was missing https://www.usps.com/business/web-tools-apis/price-calculators.htm. 
  158. if ( $intl ) { 
  159. $api = "API=IntlRateV2"; //"API=" was missing https://www.usps.com/business/web-tools-apis/price-calculators.htm. 
  160.  
  161. $env = "prod"; 
  162. if ( (bool) $this->use_test_env === true ) { 
  163. $env = "test"; 
  164.  
  165. return "http://" . $end_points[$env]["server"] . "/" . $end_points[$env]["dll"] . "?" . $api; 
  166.  
  167. /** 
  168. * Returns the settings form that controls the USPS API information 
  169. * @since 1.0 
  170. */ 
  171. function getForm() { 
  172. $defaults = array( 
  173. 'test_server' => 0,  
  174. 'adv_rate' => 0,  
  175. 'id' => '',  
  176. 'services' => array( 'ONLINE' ),  
  177. ); 
  178. $settings = get_option( "wpec_usps", array() ); 
  179. $settings = array_merge( $defaults, $settings ); 
  180. $settings['services'] = array_merge( $defaults['services'], $settings['services'] ); 
  181.  
  182. ob_start(); 
  183. ?> 
  184. <tr> 
  185. <td><?php _e( 'USPS ID', 'wpsc' ); ?></td> 
  186. <td> 
  187. <input type='text' name='wpec_usps[id]' value='<?php esc_attr_e( $settings["id"] ); ?>' /> 
  188. <p class='description'><?php printf( __("Don't have a USPS API account? <a href='%s' target='_blank'>Register for USPS Web Tools</a>", 'wpsc' ), 'https://secure.shippingapis.com/registration/' ); ?></p> 
  189. <p class='description'><?php _e( "Make sure your account has been activated with USPS - if you're unsure if this applies to you then please check with USPS", 'wpsc' ); ?></p> 
  190. </td> 
  191. </tr> 
  192.  
  193. <tr> 
  194. <td><?php _e( 'Shipping Settings', 'wpsc' ); ?></td> 
  195. <td> 
  196. <label> 
  197. <input type='checkbox' <?php checked( isset( $settings['test_server'] ) && (bool) $settings['test_server'], 1 ); ?> name='wpec_usps[test_server]' value='1' /> 
  198. <?php _e( 'Use Test Server', 'wpsc' ); ?> 
  199. </label> 
  200. <br /> 
  201.  
  202. <label> 
  203. <input type='checkbox' <?php checked( isset( $settings['adv_rate'] ) && (bool) $settings['adv_rate'], 1 ); ?> name='wpec_usps[adv_rate]' value='1' /> 
  204. <?php _e( 'Advanced Rates', 'wpsc' ); ?> 
  205. </label> 
  206. <p class='description'><?php _e( 'This setting will provide rates based on the dimensions from each item in your cart', 'wpsc' ); ?></p> 
  207. <label> 
  208. <input type='checkbox' <?php checked( isset( $settings['intl_rate'] ) && (bool) $settings['intl_rate'], 1 ); ?> name='wpec_usps[intl_rate]' value='1' /> 
  209. <?php _e( 'Disable International Shipping', 'wpsc' ); ?> 
  210. </label> 
  211. <p class='description'><?php _e( 'No shipping rates will be displayed if the shipment destination country is different than your base country/region.', 'wpsc' ); ?></p> 
  212. </td> 
  213. </tr> 
  214.  
  215. <?php 
  216. $wpec_usps_services = $settings["services"]; 
  217. ?> 
  218. <tr> 
  219. <td><?php _e( 'Select Services', 'wpsc' ); ?></td> 
  220. <td> 
  221. <div class="ui-widget-content multiple-select"> 
  222. <?php foreach ( $this->services as $label => $service ): ?> 
  223. <input type="checkbox" id="wpec_usps_srv_<?php esc_attr_e( $service ); ?>" name="wpec_usps[services][]" value="<?php echo esc_attr_e( $service ); ?>" <?php checked( (bool) array_search( $service, $wpec_usps_services ) ); ?> /> 
  224. <label for="wpec_usps_srv_$service"><?php echo $label; ?></label> 
  225. <br /> 
  226. <?php endforeach; ?> 
  227. </div> 
  228. <p class='description'><?php _e( "* Standard Post should never be used as the sole USPS mail service provided. It's only available for destinations located far from your base zipcode. In this case, and to provide shipping coverage for locations closer to your base zipcode, the Priority Mail service must be selected too.", 'wpsc' ); ?></p> 
  229. <p class='description'><?php printf( __("** Media Mail must only be used for books, printed material and sound or video recordings (CDs, DVDs, Blu-rays and other, excluding games). It may be subjected to postal inspection to enforce this. For more information, please consult the <a href='%s' target='_blank'>Media Mail's Rules & Restrictions web page.</a>", 'wpsc' ), 'https://www.usps.com/ship/media-mail.htm' ); ?></p> 
  230. </td> 
  231. </tr> 
  232.  
  233. <?php 
  234. $mt_array = array( 
  235. __( "Package", 'wpsc' ),  
  236. __( "Envelope", 'wpsc' ),  
  237. __( "Postcards or aerogrammes", 'wpsc' ),  
  238. __( "Matter for the Blind", 'wpsc' ) 
  239. ); 
  240.  
  241. $mt_selected = ( array_key_exists( "intl_pkg", $settings ) ) ? $settings["intl_pkg"] : __( "Package", 'wpsc' ); 
  242. ?> 
  243. <tr> 
  244. <td><?php _e( "International Package Type", "wpsc" ); ?></td> 
  245. <td> 
  246. <select id="wpec_usps_intl_pkg" name="wpec_usps[intl_pkg]"> 
  247. <?php foreach ( $mt_array as $mt ): ?> 
  248. <option value="<?php esc_attr_e( $mt ); ?>" <?php selected( $mt, $mt_selected );?> ><?php echo $mt; ?></option> 
  249. <?php endforeach; ?> 
  250. </select> 
  251. </td> 
  252. </tr> 
  253.  
  254. <?php 
  255. // If First Class, Online or All is selected then we need to know what Kind of First class 
  256. // will be used. 
  257. $fcl_types = array( 
  258. __( "Parcel", 'wpsc' ) => "PARCEL",  
  259. __( "Letter", 'wpsc' ) => "LETTER",  
  260. __( "Flat", 'wpsc' ) => "FLAT",  
  261. __( "Postcard", 'wpsc' ) => "POSTCARD" 
  262. ); 
  263. $type_selected = ( array_key_exists( "fcl_type", $settings ) ) ? $settings["fcl_type"] : $fcl_types["Parcel"]; 
  264. ?> 
  265. <tr> 
  266. <td><?php _e( "First Class Mail Type", "wpsc" ); ?></td> 
  267. <td> 
  268. <select id="first_cls_type" name="wpec_usps[fcl_type]"> 
  269. <?php foreach ( $fcl_types as $label => $value ): ?> 
  270. <option value="<?php esc_attr_e( $value ); ?>" <?php selected( $value, $type_selected ); ?>><?php echo $label; ?></option> 
  271. <?php endforeach; ?> 
  272. </select> 
  273. <br /> 
  274. <p class='description'><?php _e( "Note: Only used for First Class service rates if selected", "wpsc" ); ?></p> 
  275. </td> 
  276. </tr> 
  277. <?php 
  278. return ob_get_clean(); 
  279.  
  280. /** 
  281. * This is called when the form provided from get_form is submitted 
  282. * @since 1.0 
  283. */ 
  284. function submit_form() { 
  285. // Completely revamped how these values are stored 
  286. if ( ! empty( $_POST['wpec_usps'] ) ) { 
  287. $settings = stripslashes_deep( $_POST['wpec_usps'] ); 
  288. update_option( 'wpec_usps', $settings ); 
  289. return TRUE; 
  290.  
  291. /** 
  292. * This is a temporary hack until I can 
  293. * build a UI to build "Service Packages" so you can designate 
  294. * all of these based on services 
  295. * @since 2.0 
  296. * @param array $base 
  297. * @param string $service 
  298. * @param ASHPackage $package 
  299. */ 
  300. function _translate_package_options( &$base, $service, $package = FALSE ) { 
  301. $container = ""; 
  302. $machinable = "true"; 
  303. $size = "REGULAR"; 
  304. switch ( $service ) { 
  305. case "PRIORITY": 
  306. $container = "VARIABLE"; 
  307. break; 
  308. case "PRIORITY EXPRESS": 
  309. $container = "VARIABLE"; 
  310. break; 
  311. case "STANDARD POST": 
  312. $container = "VARIABLE"; 
  313. $machinable = "true"; 
  314. $size = "REGULAR"; 
  315. break; 
  316. case "ALL": 
  317. $machinable = "true"; 
  318. break; 
  319. case "ONLINE": 
  320. $machinable = "true"; 
  321. break; 
  322. if ( $package && ( (float)$package->width > 12 || (float)$package->length > 12 || (float)$package->height > 12 ) ) { 
  323. if ( $container == "VARIABLE") { 
  324. $container = "RECTANGULAR"; 
  325. $size = "LARGE"; 
  326. $base["Container"] = apply_filters( 'wpsc_usps_container', $container ); 
  327. $base["Size"] = $size; 
  328. if ( $package ) { 
  329. $base["Width"] = $package->width; 
  330. $base["Length"] = $package->length; 
  331. $base["Height"] = $package->height; 
  332. $base["Girth"] = $package->girth; 
  333. // $base["SpecialServices"] = ""; // Its here, not ready for it yet, think ASH 1.0 or higher. 
  334. $base["Machinable"] = $machinable; 
  335.  
  336. /** 
  337. * Helper function that builds the list of packages for domestic rating 
  338. * @since 2.0 
  339. * @param array reference $request 
  340. * @param array $data 
  341. * @param ASHPackage $package 
  342. * @return array 
  343. */ 
  344. function _build_domestic_shipment( &$request, $data, $package ) { 
  345. $shipment = array(); 
  346. if ( $package ) { 
  347. $data["weight"] = $package->weight; 
  348. $pound = floor( $data["weight"] ); 
  349. $ounce = ceil( ( $data["weight"] - $pound ) * 16 ); //"Ounces field < 5 digits" See 1.2 -> http://pe.usps.com/text/dmm300/133.htm 
  350. $data["pound"] = $pound; 
  351. $data["ounce"] = $ounce; 
  352.  
  353. if ( ! array_key_exists( "services", (array) $data ) ) { 
  354. $data["services"] = array( "ONLINE" ); 
  355. $base = array( 
  356. "ZipOrigination" => $data["base_zipcode"],  
  357. "ZipDestination" => $data["dest_zipcode"],  
  358. "Pounds" => $data["pound"],  
  359. "Ounces" => $data["ounce"],  
  360. ); 
  361. foreach ( $data["services"] as $label => $service ) { 
  362. $temp = array(); 
  363. $temp["Service"] = $service; 
  364. $temp["@attr"] = array( "ID" => count( $shipment ) ); 
  365.  
  366. if ( $ounce > 13 || $pound > 1 ) { 
  367. if ( strpos( $service, "FIRST" ) === FALSE || $service == "ONLINE" ) { 
  368. $temp["FirstClassMailType"] = $data["fcl_type"]; 
  369. $temp = array_merge( $temp, $base ); 
  370. $this->_translate_package_options( $temp, $service, $package ); 
  371. array_push( $shipment, $temp ); 
  372. } else { 
  373. if ( strpos( $service, "FIRST" ) !== FALSE || $service == "ONLINE" ) { 
  374. $temp["FirstClassMailType"] = $data["fcl_type"]; 
  375. $temp = array_merge( $temp, $base ); 
  376. $this->_translate_package_options( $temp, $service, $package ); 
  377. } else { 
  378. $temp = array_merge( $temp, $base ); 
  379. $this->_translate_package_options( $temp, $service, $package ); 
  380. array_push( $shipment, $temp ); 
  381. $request[ $data["req"] ]["Package"] = $shipment; 
  382.  
  383. /** 
  384. * Helper function that builds the list of packages for international rating 
  385. * @since 2.0 
  386. * @param array reference $request 
  387. * @param array $data 
  388. * @param ASHPackage $package 
  389. * @return array 
  390. */ 
  391. function _build_intl_shipment( &$request, array $data, $package ) { 
  392. $shipment = array(); 
  393. $data["size"] = "REGULAR"; //No dimensions needed, but USPS says no GXG pricing returned. 
  394. $data["width"] = ""; 
  395. $data["length"] = ""; 
  396. $data["height"] = ""; 
  397. $data["girth"] = ""; 
  398.  
  399. if ( $package ) { 
  400. $data["weight"] = $package->weight; 
  401. $data["value"] = $package->value; 
  402. if ( (float)$package->width > 12 || (float)$package->length > 12 || (float)$package->height > 12 ) { 
  403. $data['size'] = "LARGE"; 
  404. $data["width"] = $package->width; 
  405. $data["length"] = $package->length; 
  406. $data["height"] = $package->height; 
  407. $data["girth"] = $package->girth; 
  408.  
  409. $data["pounds"] = floor( $data["weight"] ); 
  410. $data["ounces"] = ceil( ( $data["weight"] - $data["pounds"] ) * 16 ); //"Ounces field < 5 digits" See 1.2 -> http://pe.usps.com/text/dmm300/133.htm 
  411.  
  412. if ( ! array_key_exists( "mail_type", (array) $data ) ) { 
  413. $data["mail_type"] = array( "Package" ); 
  414.  
  415. $base = array( 
  416. "Pounds" => $data["pounds"],  
  417. "Ounces" => $data["ounces"],  
  418. "Machinable" => "True",  
  419. "MailType" => $data["mail_type"],  
  420. "GXG" => array( 
  421. "POBoxFlag" => "N",  
  422. "GiftFlag" => "N" 
  423. ),  
  424. "ValueOfContents" => $data["value"],  
  425. "Country" => $data["dest_country"],  
  426. "Container" => apply_filters( 'wpsc_usps_container', "RECTANGULAR" ),  
  427. "Size" => $data["size"],  
  428. "Width" => $data["width"],  
  429. "Length" => $data["length"],  
  430. "Height" => $data["height"],  
  431. "Girth" => $data["girth"],  
  432. "OriginZip" => $data["base_zipcode"],  
  433. "CommercialFlag" => "Y" 
  434. ); 
  435.  
  436. if ( ! $package ) { 
  437. unset( $base["GXG"] ); //Error returned if present and no dimensions are set. 
  438. $base["@attr"]["ID"] = 0; 
  439. array_push( $shipment, $base ); 
  440. $request[$data["req"]]["Package"] = $shipment; 
  441.  
  442. /** 
  443. * Used to build request to send to USPS API 
  444. * @since 2.0 
  445. * @param array $data 
  446. * @return array 
  447. */ 
  448. function _build_request( &$data ) { 
  449. global $wpec_ash_xml; 
  450. if ( ! is_array( $data ) ) { 
  451. return array(); 
  452. $req = "RateV4Request"; 
  453. if ( $data["dest_country"] != "USA" ) { 
  454. $req = "IntlRateV2Request"; 
  455. $data["req"] = $req; 
  456. $request = array( 
  457. $req => array( 
  458. "@attr" => array( 
  459. "USERID" => $data["user_id"] 
  460. ),  
  461. "Revision" => "2" 
  462. ); 
  463. return $request; 
  464.  
  465. /** 
  466. * Handles contacting the USPS server via cURL 
  467. * @since 2.0 
  468. * @param string $request is the raw XML request 
  469. * @param boolean $intl flag to denote if it is US Domestic or International 
  470. * @return string XML Response from USPS API 
  471. */ 
  472. function _make_request( $request, $intl = false ) { 
  473.  
  474. // Get the proper endpoint to send request to 
  475. $endpoint = $this->_get_endpoint( $intl ); 
  476. // Need to url encode the XML for the request 
  477. $encoded_request = urlencode( $request ); 
  478.  
  479. // Put endpoint and request together 
  480. $url = $endpoint . "&XML=" . $encoded_request; 
  481.  
  482. $request = wp_remote_post( 
  483. $url,  
  484. array( 
  485. 'httpversion' => '1.1',  
  486. 'user-agent' => 'wp-e-commerce',  
  487. 'redirection' => 10,  
  488. 'timeout' => 120 
  489. ); 
  490.  
  491. return wp_remote_retrieve_body( $request ); 
  492.  
  493. /** 
  494. * USPS seems to be not able to encode their own XML appropriately 
  495. * This function is used to fix their mistakes. 
  496. * @since 2.0 
  497. * @param string $response Reference to the $response string 
  498. */ 
  499. function _clean_response( &$response ) { 
  500. $response = str_replace('&lt;sup&gt;&#174;&lt;/sup&gt;', '®', $response); 
  501. $response = str_replace('&lt;sup&gt;&#8482;&lt;/sup&gt;', '™', $response); 
  502. $response = str_replace('&lt;sup&gt;&#xAE;&lt;/sup&gt;', '®', $response); 
  503. $response = wp_specialchars_decode( wp_specialchars_decode( $response ) ); 
  504. return $response; 
  505.  
  506. /** 
  507. * Parse the service out of the package 
  508. * @since 2.0 
  509. * @param string $package 
  510. * @return string 
  511. */ 
  512. function _get_service( $ServiceTag, $package ) { 
  513. global $wpec_ash_xml; 
  514. $service = ""; 
  515. $temp_service = $wpec_ash_xml->get( $ServiceTag, $package ); 
  516.  
  517. if ( $temp_service ) { 
  518. $service = $temp_service[0]; 
  519.  
  520. preg_match( '/(.*?)<sup>/', $service, $temp ); 
  521. if ( ! empty( $temp ) ) { 
  522. $service = $temp[1]; 
  523.  
  524. return $service; 
  525.  
  526. /** 
  527. * Merges N-Many arrays together by key, without replacement 
  528. * @since 2.0 
  529. * @param array $arrays 
  530. * @return array 
  531. */ 
  532. function _merge_arrays( array $arrays ) { 
  533. $final_array = array(); 
  534. if ( ! is_array( $arrays ) || count( $arrays ) == 0 ) { 
  535. // How did that happen, I mean really, I am specifying array as the base type 
  536. return $final_array; 
  537. if ( count( $arrays ) != count( $arrays, COUNT_RECURSIVE ) ) { 
  538. foreach( $arrays as $arr ) { 
  539. foreach( $arr as $key => $value ) { 
  540. if ( ! array_key_exists( $key, $final_array ) ) { 
  541. if ( $value ) { 
  542. $final_array[ $key ] = $value; 
  543. } elseif ( $final_array[ $key ] < $value ) { 
  544. $final_array[ $key ] = $value; 
  545. } else { 
  546. $final_array = $arrays; 
  547. return $final_array; 
  548.  
  549. /** 
  550. * This function parses the provided XML response from USPS to retrieve the final rates. 
  551. * @since 2.0 
  552. * @param string $response The XML response from USPS 
  553. * @return array 
  554. */ 
  555. function _parse_domestic_response( $response ) { 
  556. global $wpec_ash_xml; 
  557.  
  558. $package_services = array(); 
  559.  
  560. $this->_clean_response( $response ); 
  561.  
  562. $packages = $wpec_ash_xml->get( "Package", $response ); 
  563. $errors = ''; 
  564.  
  565. if ( ! is_array( $packages ) ) { 
  566. return array(); 
  567.  
  568. foreach ( $packages as $package ) { 
  569.  
  570. if ( stripos( $package, '<ERROR>') === 0 ) { 
  571.  
  572. $errors = $wpec_ash_xml->get( "Description", $package ); 
  573.  
  574. } else { 
  575.  
  576. $postage_services = $wpec_ash_xml->get( "Postage", $package ); 
  577.  
  578. if ( count( $postage_services ) == 1 ) { 
  579. $postage_services = array( $package ); 
  580.  
  581. foreach ( $postage_services as $postage ) { 
  582. $temp = array(); 
  583.  
  584. $service_name = $this->_get_service( "MailService", $postage ); 
  585. $temp_rate = $wpec_ash_xml->get( "Rate", $postage ); 
  586.  
  587. if ( ! empty( $temp_rate ) ) { 
  588. $rate = $temp_rate[0]; 
  589. } else { 
  590. continue; 
  591.  
  592. if ( ! empty( $service_name ) ) { 
  593. $temp[ $service_name ] = apply_filters( 'wpsc_usps_domestic_rate', $rate, $service_name ); 
  594.  
  595. array_push( $package_services, $temp ); 
  596.  
  597.  
  598. if ( empty( $package_services ) && ! empty( $errors ) ) { 
  599. _wpsc_shipping_add_error_message( $errors[0] ); 
  600.  
  601. return $package_services; 
  602.  
  603. /** 
  604. * This function parses the provided XML response for international requests 
  605. * from USPS to retrieve the final rates as an array. 
  606. * @since 2.0 
  607. * @param string $response The XML response from USPS 
  608. * @return array 
  609. */ 
  610. function _parse_intl_response( $response ) { 
  611. global $wpec_ash_xml; 
  612. $services_table = array(); 
  613. $this->_clean_response( $response ); 
  614.  
  615. $services = $wpec_ash_xml->get( "Service", $response ); 
  616.  
  617. if ( empty( $services ) ) { 
  618. return array(); 
  619.  
  620. foreach ( $services as $service ) { 
  621. $service_name = $this->_get_service( "SvcDescription", $service ); 
  622. $temp_rate = $wpec_ash_xml->get( "Postage", $service ); 
  623.  
  624. if ( ! empty( $temp_rate ) ) { 
  625. $rate = $temp_rate[0]; 
  626. } else { 
  627. continue; 
  628.  
  629. if ( ! empty( $service_name ) ) { 
  630. $service_table[ $service_name ] = apply_filters( 'wpsc_usps_intl_rate', $rate, $service_name ); 
  631. return $service_table; 
  632.  
  633. /** 
  634. * Returns an array using the common keys from all arrasy and the sum of those common keys values; 
  635. * @since 2.0 
  636. * @param array $rate_tables 
  637. * @return array 
  638. */ 
  639. function _combine_rates( $rate_tables ) { 
  640. $final_table = array(); 
  641. if ( ! is_array( $rate_tables ) || count( $rate_tables ) == 0 ) { 
  642. return array(); 
  643. if ( count( $rate_tables ) < 2 ) { 
  644. return $rate_tables[0]; 
  645. $temp_services = call_user_func_array( 'array_intersect_key', $rate_tables ); 
  646.  
  647. $valid_services = array_keys( $temp_services ); 
  648. if ( count( $rate_tables ) != count( $rate_tables, COUNT_RECURSIVE ) ) { 
  649. foreach ( $rate_tables as $rate_table ) { 
  650. foreach ( $rate_table as $service => $rate ) { 
  651. if ( in_array( $service, $valid_services ) ) { 
  652. if ( ! array_key_exists( $service, $final_table ) ) { 
  653. $final_table[ $service ] = 0; 
  654. $final_table[ $service ] += $rate; 
  655. } else { 
  656. $final_table = $rate_tables; 
  657. return $final_table; 
  658.  
  659. /** 
  660. * Merges arrays and adds the values of common keys. 
  661. * @since 2.0 
  662. * @param array $arrays 
  663. * @return array 
  664. */ 
  665. function merge_sum_arrays( $arrays ) { 
  666. $temp = array(); 
  667. if ( ! is_array( $arrays ) || count( $arrays ) == 0 ) { 
  668. return array(); 
  669. if ( count( $arrays ) > 1 ) { 
  670. $temp_arr = call_user_func_array( 'array_intersect_key', $arrays ); 
  671. $intersect_keys = array_keys( (array) $temp_arr ); 
  672. } else { 
  673. $intersect_keys = array_keys( $arrays[0] ); 
  674.  
  675.  
  676.  
  677. foreach ( $arrays as $arr ) { 
  678. foreach ( $arr as $key => $value ) { 
  679. if ( in_array( $key, (array) $intersect_keys ) ) { 
  680. if ( ! array_key_exists( $key, $temp ) ) { 
  681. $temp[ $key ] = 0; 
  682. $temp[ $key ] += $value; 
  683. return $temp; 
  684.  
  685. /** 
  686. * Runs the quote process for a simple quote and returns the final quote table 
  687. * @since 2.0 
  688. * @param array $data 
  689. * @return array 
  690. */ 
  691. function _quote_simple_intl( array $data ) { 
  692. global $wpec_ash_xml; 
  693. //*** Build Request **\\ 
  694. $request = $this->_build_request( $data ); 
  695. if ( empty( $request ) ) { 
  696. return array(); 
  697. $this->_build_intl_shipment( $request, $data, FALSE ); 
  698. $request_xml = $wpec_ash_xml->build_message( $request ); 
  699. //*** Make the Request ***\\ 
  700. $response = $this->_make_request( $request_xml, TRUE ); 
  701. if ( empty( $response ) || $response === FALSE ) { 
  702. return array(); 
  703. //*** Parse the response from USPS ***\ 
  704. $package_rate_table = $this->_parse_intl_response( $response ); 
  705. $rate_table = $this->_merge_arrays( $package_rate_table ); 
  706. return $rate_table; 
  707.  
  708. /** 
  709. * Runs the quote process for a simple quote and returns the final quote table 
  710. * @since 2.0 
  711. * @param array $data 
  712. * @return array 
  713. */ 
  714. function _quote_simple( array $data ) { 
  715. global $wpec_ash_xml; 
  716. //*** Build Request **\\ 
  717. $request = $this->_build_request( $data ); 
  718. if ( empty( $request ) ) { 
  719. return array(); 
  720. $this->_build_domestic_shipment( $request, $data, FALSE ); 
  721. $request_xml = $wpec_ash_xml->build_message( $request ); 
  722. //*** Make the Request ***\\ 
  723. $response = $this->_make_request( $request_xml, FALSE ); 
  724. if ( empty( $response ) || $response === FALSE ) { 
  725. return array(); 
  726. //*** Parse the response from USPS ***\ 
  727. $package_rate_table = $this->_parse_domestic_response( $response ); 
  728. $rate_table = $this->_merge_arrays( $package_rate_table ); 
  729. return $rate_table; 
  730.  
  731. /** 
  732. * Runs the quote process for an advanced quote and returns the final quote table 
  733. * @since 2.0 
  734. * @param array $data 
  735. * @return array 
  736. */ 
  737. function _quote_advanced( array $data ) { 
  738. global $wpec_ash_xml; 
  739.  
  740. $rate_tables = array(); 
  741. $cart_shipment = apply_filters( 'wpsc_the_shipment', $this->shipment, $data ); //Filter to allow reprocesing the shipment before is quoted. 
  742. foreach ( $cart_shipment->packages as $package ) { 
  743. $temp_data = $data; 
  744. $request = $this->_build_request( $temp_data ); 
  745. if ( empty( $request ) ) { 
  746. continue; 
  747. $this->_build_domestic_shipment( $request, $temp_data, $package ); 
  748. $request_xml = $wpec_ash_xml->build_message( $request ); 
  749. //*** Make the Request ***\\ 
  750. $response = $this->_make_request( $request_xml, FALSE ); 
  751. if ( empty( $response ) ) { 
  752. continue; 
  753. //*** Parse the Response ***\\ 
  754. $package_rate_table = $this->_parse_domestic_response( $response ); 
  755. //*** Reformat the array structure ***\\ 
  756. $temp = $this->_merge_arrays( $package_rate_table ); 
  757.  
  758. array_push( $rate_tables, $temp ); 
  759.  
  760. $rates = $this->merge_sum_arrays( $rate_tables ); 
  761. return $rates; 
  762.  
  763. /** 
  764. * Runs the quote process for an international quote and returns the final quote table 
  765. * @since 2.0 
  766. * @param array $data 
  767. * @return array 
  768. */ 
  769. function _quote_intl( array $data ) { 
  770. global $wpec_ash_xml; 
  771. $rate_tables = array(); 
  772. $cart_shipment = apply_filters( 'wpsc_the_shipment', $this->shipment, $data ); //Filter to allow reprocesing the shipment before is quoted. 
  773. foreach ( $cart_shipment->packages as $package ) { 
  774. $temp_data = $data; 
  775. $request = $this->_build_request( $temp_data ); 
  776. if ( empty( $request ) ) { 
  777. continue; 
  778. $this->_build_intl_shipment( $request, $temp_data, $package ); 
  779. $request_xml = $wpec_ash_xml->build_message( $request ); 
  780. //*** Make the Request ***\\ 
  781. $response = $this->_make_request( $request_xml, TRUE ); 
  782. if ( empty( $response ) || $response === FALSE ) { 
  783. continue; 
  784. $rate_table = $this->_parse_intl_response( $response ); 
  785. array_push( $rate_tables, $rate_table ); 
  786. $rates = $this->_combine_rates( $rate_tables ); 
  787. return $rates; 
  788.  
  789. /** 
  790. * Returns an updated country based on several rules that USPS has 
  791. * @since 2.0 
  792. * @param string $full_name The countries full name 
  793. * @return string 
  794. * ::rules:: 
  795. * U.K. Is an invalid name, they use Great Britain and Northern Ireland 
  796. * Any US Posession is rated as USA 
  797. */ 
  798. function _update_country( $full_name ) { 
  799. $us_posessions = array( 
  800. "Puerto Rico",  
  801. "Virgin Islands (USA)",  
  802. "USA Minor Outlying Islands",  
  803. "Guam (USA)" 
  804. ); 
  805. if ( in_array( $full_name, $us_posessions ) ) { 
  806. return "USA"; 
  807. if ( $full_name == "U.K.") { 
  808. return 'Great Britain and Northern Ireland'; 
  809. return $full_name; 
  810.  
  811. /** 
  812. * Takes a rate table and returns a new table with only services selected in the back end 
  813. * @since 2.0 
  814. * @param array $rate_table 
  815. * @param array $data 
  816. * @return array 
  817. */ 
  818. function _validate_services( $rate_table, $data ) { 
  819.  
  820. if ( ! is_array( $rate_table ) ) { 
  821. return array(); 
  822. $final_table = array(); 
  823. $services = array(); 
  824. foreach ( $this->services as $service => $code ) { 
  825. if ( in_array( $code, $data["services"] ) ) { 
  826. $services[ $service ] = $code; 
  827. $valid_services = array_intersect_key( (array) $rate_table, $services ); 
  828. return $valid_services; 
  829.  
  830. /** 
  831. * This function handles the process of getting a quote. 
  832. * It is kept abstracted from the entry points so you can 
  833. * implement a testing framework separate from wordpress. 
  834. * @since 2.0 
  835. * @param array $data This is an array that USPS uses to build its request 
  836. * Expected Values for $data: 
  837. * Required : String : "fcl_type" : Is the First Class Package Type ("Package", "Envelope", "Postcards or aerogrammes", "Matter for the Blind", "All") 
  838. * Required : Int : "base_zipcode" : The originating zipcode where the shipment is from 
  839. * Required : String : "user_id" : USPS user ID 
  840. * Required : Array : "services" : List of services to get rates for, One or More services required 
  841. */ 
  842. function _run_quote( array $data ) { 
  843. global $wpec_ash_tools; 
  844.  
  845. if ( $wpec_ash_tools->is_military_zip( $data["dest_zipcode"] ) ) { 
  846. $data["dest_country"] = "USA"; 
  847. //\\************** END common config **************\\// 
  848. //*** Get the Quotes ***\\ 
  849. $quotes = array(); 
  850. if ( $data["dest_country"] == "USA" && $data["adv_rate"] == TRUE ) { 
  851. $quotes = $this->_quote_advanced( $data ); 
  852. } elseif ( $data["dest_country"] != "USA" && $data["adv_rate"] == TRUE ) { 
  853. $quotes = $this->_quote_intl( $data ); 
  854. } elseif ( $data["dest_country"] == "USA" && $data["adv_rate"] != TRUE ) { 
  855. $quotes = $this->_quote_simple( $data ); 
  856. } else { 
  857. $quotes = $this->_quote_simple_intl( $data ); 
  858. $rate_table = $this->_validate_services( $quotes, $data ); 
  859. if ( ! empty( $quotes ) ) { //Don't try to sort an empty array 
  860. asort( $quotes, SORT_NUMERIC ); 
  861. return $quotes; 
  862.  
  863. /** 
  864. * This function is used to provide rates for single items 
  865. * Due to the nature of external calculators it is too costly to use this 
  866. * @deprecated Do Not Use 
  867. */ 
  868. function get_item_shipping() { 
  869.  
  870. /** 
  871. * General entry point for WPEC external shipping calculator 
  872. * This function expects no arguments but requires POST data 
  873. * and configuration from the plugin settings 
  874. * @return array $rate_table List of rates in "Service"=>"Rate" format 
  875. */ 
  876. function getQuote() { 
  877. global $wpdb, $wpec_ash, $wpec_ash_tools, $wpsc_cart; 
  878. $data = array(); 
  879. //************** These values are common to all entry points ************** 
  880. //*** User/Customer Entered Values ***\\ 
  881. //*** Set up the destination country ***\ 
  882. $data["dest_country"] = wpsc_get_customer_meta( 'shipping_country' ); 
  883. $settings = get_option( 'wpec_usps' ); 
  884. //Disable International Shipping. Default: Enabled as it currently is. 
  885. $data['intl_rate'] = isset( $settings['intl_rate'] ) && ! empty( $settings['intl_rate'] ) ? FALSE : TRUE; 
  886. if ( ! $data['intl_rate'] && $data['dest_country'] != get_option( 'base_country' ) ) { 
  887. return array(); 
  888.  
  889. // If ths zip code is provided via a form post use it! 
  890. $data["dest_zipcode"] = (string) wpsc_get_customer_meta( 'shippingpostcode' ); 
  891.  
  892. if ( ! is_object( $wpec_ash_tools ) ) { 
  893. $wpec_ash_tools = new ASHTools(); 
  894.  
  895. if ( empty( $data["dest_zipcode"] ) && $wpec_ash_tools->needs_post_code( $data["dest_country"] ) ) { 
  896. // We cannot get a quote without a zip code so might as well return! 
  897. return array(); 
  898.  
  899. //*** Grab Total Weight from the shipment object for simple shipping 
  900. $data["weight"] = wpsc_cart_weight_total(); 
  901. if ( empty( $data["weight"] ) ) { 
  902. return array(); 
  903.  
  904.  
  905. // If the region code is provided via a form post use it! 
  906. if ( isset( $_POST['region'] ) && ! empty( $_POST['region'] ) ) { 
  907. $data['dest_state'] = wpsc_get_region( $_POST['region'] ); 
  908. } else if ( $dest_state = wpsc_get_customer_meta( 'shipping_state' ) ) { 
  909. // Well, we have a zip code in the session and no new one provided 
  910. $data['dest_state'] = $dest_state; 
  911. } else { 
  912. $data['dest_state'] = ""; 
  913. $data["dest_country"] = $wpec_ash_tools->get_full_country( $data["dest_country"] ); 
  914. $data["dest_country"] = $this->_update_country( $data["dest_country"] ); 
  915.  
  916. if ( ! is_object( $wpec_ash ) ) { 
  917. $wpec_ash = new ASH(); 
  918. $shipping_cache_check['state'] = $data['dest_state']; 
  919. $shipping_cache_check['country'] = $data['dest_country']; 
  920. $shipping_cache_check['zipcode'] = $data["dest_zipcode"]; 
  921. $this->shipment = $wpec_ash->get_shipment(); 
  922. $this->shipment->set_destination( $this->internal_name, $shipping_cache_check ); 
  923. $this->shipment->rates_expire = date('Y-m-d'); //Date will be checked against the cached date. 
  924. $data['shipper'] = $this->internal_name; 
  925. $data["adv_rate"] = (!empty($settings["adv_rate"])) ? $settings["adv_rate"] : FALSE; // Use advanced shipping for Domestic Rates ? Not available 
  926. if ( $data["weight"] > 70 && ! (boolean) $data["adv_rate"] ) { //USPS has a weight limit: https://www.usps.com/send/can-you-mail-it.htm?#3. 
  927. $over_weight_txt = apply_filters( 'wpsc_shipment_over_weight',  
  928. __( 'Your order exceeds the standard shipping weight limit. 
  929. Please contact us to quote other shipping alternatives.', 'wpsc' ),  
  930. $data ); 
  931. $shipping_quotes[$over_weight_txt] = 0; // yes, a constant. 
  932. $wpec_ash->cache_results( $this->internal_name, array($shipping_quotes), $this->shipment ); 
  933. return array($shipping_quotes); 
  934.  
  935. // Check to see if the cached shipment is still accurate, if not we need new rate 
  936. $cache = $wpec_ash->check_cache( $this->internal_name, $this->shipment ); 
  937. // We do not want to spam USPS (and slow down our process) if we already 
  938. // have a shipping quote! 
  939. if ( count($cache["rate_table"] ) >= 1 ) { //$cache['rate_table'] could be array(0). 
  940. return $cache["rate_table"]; 
  941. //*** WPEC Configuration values ***\\ 
  942. $this->use_test_env = ( ! isset( $settings["test_server"] ) ) ? false : (bool) $settings['test_server']; 
  943. $data["fcl_type"] = ( ! empty( $settings["fcl_type"] ) ) ? $settings["fcl_type"] : "PARCEL"; 
  944. $data["mail_type"] = ( ! empty( $settings["intl_pkg"] ) ) ? $settings["intl_pkg"] : "Package"; 
  945. $data["base_zipcode"] = get_option( "base_zipcode" ); 
  946. $data["services"] = ( ! empty( $settings["services"] ) ) ? $settings["services"] : array( "STANDARD POST", "PRIORITY", "PRIORITY EXPRESS", "FIRST CLASS" ); 
  947. foreach( $data["services"] as $id => $service ) { 
  948. if ( $service == 'PARCEL' ) { 
  949. $data["services"][$id] = 'STANDARD POST'; 
  950. if ( $service == 'EXPRESS' ) { 
  951. $data["services"][$id] = 'PRIORITY EXPRESS'; 
  952. $data["user_id"] = $settings["id"]; 
  953. $data["value"] = $wpsc_cart->calculate_subtotal( true ); //Required by $this->_build_intl_shipment. 
  954. $data = apply_filters( 'wpsc_shipment_data', $data, $this->shipment ); 
  955. if ( isset( $data['stop'] ) ) { //Do not get rates. 
  956. return array(); 
  957. //************ GET THE RATE ************\\ 
  958. $rate_table = apply_filters( 'wpsc_rates_table', $this->_run_quote( $data ), $data, $this->shipment ); 
  959. //Avoid trying getting rates again and again when the stored zipcode is incorrect. 
  960.  
  961. //************ CACHE the Results ************\\ 
  962. $wpec_ash->cache_results( $this->internal_name, $rate_table, $this->shipment ); 
  963. return $rate_table; 
  964.  
  965. /** 
  966. * A testing entrypoint to run a quote without 
  967. * access to wordpress/wpec settings & database 
  968. * @see run_quote() for required $data values 
  969. * @param array $data 
  970. */ 
  971. function test( $data, $shipment ) { 
  972. $this->shipment = $shipment; 
  973. return $this->_run_quote( $data ); 
  974.