1 <?php
   2 
   3 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
   4 
   5 /**
   6  * WooCommerce WC_AJAX
   7  *
   8  * AJAX Event Handler
   9  *
  10  * @class       WC_AJAX
  11  * @version     2.1.0
  12  * @package     WooCommerce/Classes
  13  * @category    Class
  14  * @author      WooThemes
  15  */
  16 class WC_AJAX {
  17 
  18     /**
  19      * Hook into ajax events
  20      */
  21     public function __construct() {
  22 
  23         // woocommerce_EVENT => nopriv
  24         $ajax_events = array(
  25             'get_refreshed_fragments'                           => true,
  26             'apply_coupon'                                      => true,
  27             'update_shipping_method'                            => true,
  28             'update_order_review'                               => true,
  29             'add_to_cart'                                       => true,
  30             'checkout'                                          => true,
  31             'feature_product'                                   => false,
  32             'mark_order_complete'                               => false,
  33             'mark_order_processing'                             => false,
  34             'add_new_attribute'                                 => false,
  35             'remove_variation'                                  => false,
  36             'remove_variations'                                 => false,
  37             'save_attributes'                                   => false,
  38             'add_variation'                                     => false,
  39             'link_all_variations'                               => false,
  40             'revoke_access_to_download'                         => false,
  41             'grant_access_to_download'                          => false,
  42             'get_customer_details'                              => false,
  43             'add_order_item'                                    => false,
  44             'add_order_fee'                                     => false,
  45             'remove_order_item'                                 => false,
  46             'reduce_order_item_stock'                           => false,
  47             'increase_order_item_stock'                         => false,
  48             'add_order_item_meta'                               => false,
  49             'remove_order_item_meta'                            => false,
  50             'calc_line_taxes'                                   => false,
  51             'add_order_note'                                    => false,
  52             'delete_order_note'                                 => false,
  53             'json_search_products'                              => false,
  54             'json_search_products_and_variations'               => false,
  55             'json_search_downloadable_products_and_variations'  => false,
  56             'json_search_customers'                             => false,
  57             'term_ordering'                                     => false,
  58             'product_ordering'                                  => false
  59         );
  60 
  61         foreach ( $ajax_events as $ajax_event => $nopriv ) {
  62             add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( $this, $ajax_event ) );
  63 
  64             if ( $nopriv ) {
  65                 add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( $this, $ajax_event ) );
  66             }
  67         }
  68     }
  69 
  70     /**
  71      * Output headers for JSON requests
  72      */
  73     private function json_headers() {
  74         header( 'Content-Type: application/json; charset=utf-8' );
  75     }
  76 
  77 
  78     /**
  79      * Get a refreshed cart fragment
  80      */
  81     public function get_refreshed_fragments() {
  82 
  83         $this->json_headers();
  84 
  85         // Get mini cart
  86         ob_start();
  87 
  88         woocommerce_mini_cart();
  89 
  90         $mini_cart = ob_get_clean();
  91 
  92         // Fragments and mini cart are returned
  93         $data = array(
  94             'fragments' => apply_filters( 'add_to_cart_fragments', array(
  95                     'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
  96                 )
  97             ),
  98             'cart_hash' => WC()->cart->get_cart() ? md5( json_encode( WC()->cart->get_cart() ) ) : ''
  99         );
 100 
 101         echo json_encode( $data );
 102 
 103         die();
 104     }
 105 
 106     /**
 107      * AJAX apply coupon on checkout page
 108      */
 109     public function apply_coupon() {
 110 
 111         check_ajax_referer( 'apply-coupon', 'security' );
 112 
 113         if ( ! empty( $_POST['coupon_code'] ) ) {
 114             WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
 115         } else {
 116             wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
 117         }
 118 
 119         wc_print_notices();
 120 
 121         die();
 122     }
 123 
 124     /**
 125      * AJAX update shipping method on cart page
 126      */
 127     public function update_shipping_method() {
 128 
 129         check_ajax_referer( 'update-shipping-method', 'security' );
 130 
 131         if ( ! defined('WOOCOMMERCE_CART') ) {
 132             define( 'WOOCOMMERCE_CART', true );
 133         }
 134 
 135         $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
 136 
 137         if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
 138             foreach ( $_POST['shipping_method'] as $i => $value ) {
 139                 $chosen_shipping_methods[ $i ] = wc_clean( $value );
 140             }
 141         }
 142 
 143         WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
 144 
 145         WC()->cart->calculate_totals();
 146 
 147         woocommerce_cart_totals();
 148 
 149         die();
 150     }
 151 
 152     /**
 153      * AJAX update order review on checkout
 154      */
 155     public function update_order_review() {
 156 
 157         check_ajax_referer( 'update-order-review', 'security' );
 158 
 159         if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
 160             define( 'WOOCOMMERCE_CHECKOUT', true );
 161         }
 162 
 163         if ( 0 == sizeof( WC()->cart->get_cart() ) ) {
 164             echo '<div class="woocommerce-error">' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' <a href="' . home_url() . '" class="wc-backward">' . __( 'Return to homepage', 'woocommerce' ) . '</a></div>';
 165             die();
 166         }
 167 
 168         do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] );
 169 
 170         $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
 171 
 172         if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) {
 173             foreach ( $_POST['shipping_method'] as $i => $value ) {
 174                 $chosen_shipping_methods[ $i ] = wc_clean( $value );
 175             }
 176         }
 177 
 178         WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
 179         WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
 180 
 181         if ( isset( $_POST['country'] ) ) {
 182             WC()->customer->set_country( $_POST['country'] );
 183         }
 184 
 185         if ( isset( $_POST['state'] ) ) {
 186             WC()->customer->set_state( $_POST['state'] );
 187         }
 188 
 189         if ( isset( $_POST['postcode'] ) ) {
 190             WC()->customer->set_postcode( $_POST['postcode'] );
 191         }
 192 
 193         if ( isset( $_POST['city'] ) ) {
 194             WC()->customer->set_city( $_POST['city'] );
 195         }
 196 
 197         if ( isset( $_POST['address'] ) ) {
 198             WC()->customer->set_address( $_POST['address'] );
 199         }
 200 
 201         if ( isset( $_POST['address_2'] ) ) {
 202             WC()->customer->set_address_2( $_POST['address_2'] );
 203         }
 204 
 205         if ( "yes" == get_option( 'woocommerce_ship_to_billing_address_only' ) ) {
 206 
 207             if ( isset( $_POST['country'] ) ) {
 208                 WC()->customer->set_shipping_country( $_POST['country'] );
 209             }
 210 
 211             if ( isset( $_POST['state'] ) ) {
 212                 WC()->customer->set_shipping_state( $_POST['state'] );
 213             }
 214 
 215             if ( isset( $_POST['postcode'] ) ) {
 216                 WC()->customer->set_shipping_postcode( $_POST['postcode'] );
 217             }
 218 
 219             if ( isset( $_POST['city'] ) ) {
 220                 WC()->customer->set_shipping_city( $_POST['city'] );
 221             }
 222 
 223             if ( isset( $_POST['address'] ) ) {
 224                 WC()->customer->set_shipping_address( $_POST['address'] );
 225             }
 226 
 227             if ( isset( $_POST['address_2'] ) ) {
 228                 WC()->customer->set_shipping_address_2( $_POST['address_2'] );
 229             }
 230         } else {
 231 
 232             if ( isset( $_POST['s_country'] ) ) {
 233                 WC()->customer->set_shipping_country( $_POST['s_country'] );
 234             }
 235 
 236             if ( isset( $_POST['s_state'] ) ) {
 237                 WC()->customer->set_shipping_state( $_POST['s_state'] );
 238             }
 239 
 240             if ( isset( $_POST['s_postcode'] ) ) {
 241                 WC()->customer->set_shipping_postcode( $_POST['s_postcode'] );
 242             }
 243 
 244             if ( isset( $_POST['s_city'] ) ) {
 245                 WC()->customer->set_shipping_city( $_POST['s_city'] );
 246             }
 247 
 248             if ( isset( $_POST['s_address'] ) ) {
 249                 WC()->customer->set_shipping_address( $_POST['s_address'] );
 250             }
 251 
 252             if ( isset( $_POST['s_address_2'] ) ) {
 253                 WC()->customer->set_shipping_address_2( $_POST['s_address_2'] );
 254             }
 255         }
 256 
 257         WC()->cart->calculate_totals();
 258 
 259         do_action( 'woocommerce_checkout_order_review' ); // Display review order table
 260 
 261         die();
 262     }
 263 
 264     /**
 265      * AJAX add to cart
 266      */
 267     public function add_to_cart() {
 268         $product_id        = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
 269         $quantity          = empty( $_POST['quantity'] ) ? 1 : apply_filters( 'woocommerce_stock_amount', $_POST['quantity'] );
 270         $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
 271 
 272         if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity ) ) {
 273 
 274             do_action( 'woocommerce_ajax_added_to_cart', $product_id );
 275 
 276             if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
 277                 wc_add_to_cart_message( $product_id );
 278             }
 279 
 280             // Return fragments
 281             $this->get_refreshed_fragments();
 282 
 283         } else {
 284 
 285             $this->json_headers();
 286 
 287             // If there was an error adding to the cart, redirect to the product page to show any errors
 288             $data = array(
 289                 'error' => true,
 290                 'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
 291             );
 292 
 293             echo json_encode( $data );
 294         }
 295 
 296         die();
 297     }
 298 
 299     /**
 300      * Process ajax checkout form
 301      */
 302     public function checkout() {
 303         if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
 304             define( 'WOOCOMMERCE_CHECKOUT', true );
 305         }
 306 
 307         $woocommerce_checkout = WC()->checkout();
 308         $woocommerce_checkout->process_checkout();
 309 
 310         die(0);
 311     }
 312 
 313     /**
 314      * Feature a product from admin
 315      */
 316     public function feature_product() {
 317         if ( ! current_user_can( 'edit_products' ) ) {
 318             wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce' ) );
 319         }
 320 
 321         if ( ! check_admin_referer( 'woocommerce-feature-product' ) ) {
 322             wp_die( __( 'You have taken too long. Please go back and retry.', 'woocommerce' ) );
 323         }
 324 
 325         $post_id = ! empty( $_GET['product_id'] ) ? (int) $_GET['product_id'] : '';
 326 
 327         if ( ! $post_id || get_post_type( $post_id ) !== 'product' ) {
 328             die;
 329         }
 330 
 331         $featured = get_post_meta( $post_id, '_featured', true );
 332 
 333         if ( 'yes' === $featured ) {
 334             update_post_meta( $post_id, '_featured', 'no' );
 335         } else {
 336             update_post_meta( $post_id, '_featured', 'yes' );
 337         }
 338 
 339         wc_delete_product_transients();
 340 
 341         wp_safe_redirect( remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), wp_get_referer() ) );
 342 
 343         die();
 344     }
 345 
 346     /**
 347      * Mark an order as complete
 348      */
 349     public function mark_order_complete() {
 350         if ( ! current_user_can( 'edit_shop_orders' ) ) {
 351             wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce' ) );
 352         }
 353 
 354         if ( ! check_admin_referer( 'woocommerce-mark-order-complete' ) ) {
 355             wp_die( __( 'You have taken too long. Please go back and retry.', 'woocommerce' ) );
 356         }
 357 
 358         $order_id = isset( $_GET['order_id'] ) && (int) $_GET['order_id'] ? (int) $_GET['order_id'] : '';
 359         if ( ! $order_id ) {
 360             die();
 361         }
 362 
 363         $order = new WC_Order( $order_id );
 364         $order->update_status( 'completed' );
 365 
 366         wp_safe_redirect( wp_get_referer() );
 367 
 368         die();
 369     }
 370 
 371     /**
 372      * Mark an order as processing
 373      */
 374     public function mark_order_processing() {
 375         if ( ! current_user_can( 'edit_shop_orders' ) ) {
 376             wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce' ) );
 377         }
 378 
 379         if ( ! check_admin_referer( 'woocommerce-mark-order-processing' ) ) {
 380             wp_die( __( 'You have taken too long. Please go back and retry.', 'woocommerce' ) );
 381         }
 382 
 383         $order_id = isset( $_GET['order_id'] ) && (int) $_GET['order_id'] ? (int) $_GET['order_id'] : '';
 384         if ( ! $order_id ) {
 385             die();
 386         }
 387 
 388         $order = new WC_Order( $order_id );
 389         $order->update_status( 'processing' );
 390 
 391         wp_safe_redirect( wp_get_referer() );
 392 
 393         die();
 394     }
 395 
 396     /**
 397      * Add a new attribute via ajax function
 398      */
 399     public function add_new_attribute() {
 400 
 401         check_ajax_referer( 'add-attribute', 'security' );
 402 
 403         $this->json_headers();
 404 
 405         $taxonomy = esc_attr( $_POST['taxonomy'] );
 406         $term     = stripslashes( $_POST['term'] );
 407 
 408         if ( taxonomy_exists( $taxonomy ) ) {
 409 
 410             $result = wp_insert_term( $term, $taxonomy );
 411 
 412             if ( is_wp_error( $result ) ) {
 413                 echo json_encode( array(
 414                     'error' => $result->get_error_message()
 415                 ));
 416             } else {
 417                 echo json_encode( array(
 418                     'term_id' => $result['term_id'],
 419                     'name'    => $term,
 420                     'slug'    => sanitize_title( $term ),
 421                 ));
 422             }
 423         }
 424 
 425         die();
 426     }
 427 
 428     /**
 429      * Delete variation via ajax function
 430      */
 431     public function remove_variation() {
 432 
 433         check_ajax_referer( 'delete-variation', 'security' );
 434 
 435         $variation_id = intval( $_POST['variation_id'] );
 436         $variation = get_post( $variation_id );
 437 
 438         if ( $variation && 'product_variation' == $variation->post_type ) {
 439             wp_delete_post( $variation_id );
 440         }
 441 
 442         die();
 443     }
 444 
 445     /**
 446      * Delete variations via ajax function
 447      */
 448     public function remove_variations() {
 449 
 450         check_ajax_referer( 'delete-variations', 'security' );
 451 
 452         $variation_ids = (array) $_POST['variation_ids'];
 453 
 454         foreach ( $variation_ids as $variation_id ) {
 455             $variation = get_post( $variation_id );
 456 
 457             if ( $variation && 'product_variation' == $variation->post_type ) {
 458                 wp_delete_post( $variation_id );
 459             }
 460         }
 461 
 462         die();
 463     }
 464 
 465     /**
 466      * Save attributes via ajax
 467      */
 468     public function save_attributes() {
 469 
 470         check_ajax_referer( 'save-attributes', 'security' );
 471 
 472         // Get post data
 473         parse_str( $_POST['data'], $data );
 474         $post_id = absint( $_POST['post_id'] );
 475 
 476         // Save Attributes
 477         $attributes = array();
 478 
 479         if ( isset( $data['attribute_names'] ) ) {
 480 
 481             $attribute_names  = array_map( 'stripslashes', $data['attribute_names'] );
 482             $attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
 483 
 484             if ( isset( $data['attribute_visibility'] ) ) {
 485                 $attribute_visibility = $data['attribute_visibility'];
 486             }
 487 
 488             if ( isset( $data['attribute_variation'] ) ) {
 489                 $attribute_variation = $data['attribute_variation'];
 490             }
 491 
 492             $attribute_is_taxonomy = $data['attribute_is_taxonomy'];
 493             $attribute_position    = $data['attribute_position'];
 494             $attribute_names_count = sizeof( $attribute_names );
 495 
 496             for ( $i = 0; $i < $attribute_names_count; $i++ ) {
 497                 if ( ! $attribute_names[ $i ] ) {
 498                     continue;
 499                 }
 500 
 501                 $is_visible   = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
 502                 $is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
 503                 $is_taxonomy  = $attribute_is_taxonomy[ $i ] ? 1 : 0;
 504 
 505                 if ( $is_taxonomy ) {
 506 
 507                     if ( isset( $attribute_values[ $i ] ) ) {
 508 
 509                         // Select based attributes - Format values (posted values are slugs)
 510                         if ( is_array( $attribute_values[ $i ] ) ) {
 511                             $values = array_map( 'sanitize_title', $attribute_values[ $i ] );
 512 
 513                         // Text based attributes - Posted values are term names - don't change to slugs
 514                         } else {
 515                             $values = array_map( 'stripslashes', array_map( 'strip_tags', explode( WC_DELIMITER, $attribute_values[ $i ] ) ) );
 516                         }
 517 
 518                         // Remove empty items in the array
 519                         $values = array_filter( $values, 'strlen' );
 520 
 521                     } else {
 522                         $values = array();
 523                     }
 524 
 525                     // Update post terms
 526                     if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
 527                         wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
 528                     }
 529 
 530                     if ( $values ) {
 531                         // Add attribute to array, but don't set values
 532                         $attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
 533                             'name'          => wc_clean( $attribute_names[ $i ] ),
 534                             'value'         => '',
 535                             'position'      => $attribute_position[ $i ],
 536                             'is_visible'    => $is_visible,
 537                             'is_variation'  => $is_variation,
 538                             'is_taxonomy'   => $is_taxonomy
 539                         );
 540                     }
 541 
 542                 } elseif ( isset( $attribute_values[ $i ] ) ) {
 543 
 544                     // Text based, separate by pipe
 545                     $values = implode( ' ' . WC_DELIMITER . ' ', array_map( 'wc_clean', array_map( 'stripslashes', explode( WC_DELIMITER, $attribute_values[ $i ] ) ) ) );
 546 
 547                     // Custom attribute - Add attribute to array and set the values
 548                     $attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
 549                         'name'          => wc_clean( $attribute_names[ $i ] ),
 550                         'value'         => $values,
 551                         'position'      => $attribute_position[ $i ],
 552                         'is_visible'    => $is_visible,
 553                         'is_variation'  => $is_variation,
 554                         'is_taxonomy'   => $is_taxonomy
 555                     );
 556                 }
 557 
 558              }
 559         }
 560 
 561         if ( ! function_exists( 'attributes_cmp' ) ) {
 562             function attributes_cmp( $a, $b ) {
 563                 if ( $a['position'] == $b['position'] ) {
 564                     return 0;
 565                 }
 566 
 567                 return ( $a['position'] < $b['position'] ) ? -1 : 1;
 568             }
 569         }
 570         uasort( $attributes, 'attributes_cmp' );
 571 
 572         update_post_meta( $post_id, '_product_attributes', $attributes );
 573 
 574         die();
 575     }
 576 
 577     /**
 578      * Add variation via ajax function
 579      */
 580     public function add_variation() {
 581 
 582         check_ajax_referer( 'add-variation', 'security' );
 583 
 584         $post_id = intval( $_POST['post_id'] );
 585         $loop = intval( $_POST['loop'] );
 586 
 587         $variation = array(
 588             'post_title'   => 'Product #' . $post_id . ' Variation',
 589             'post_content' => '',
 590             'post_status'  => 'publish',
 591             'post_author'  => get_current_user_id(),
 592             'post_parent'  => $post_id,
 593             'post_type'    => 'product_variation'
 594         );
 595 
 596         $variation_id = wp_insert_post( $variation );
 597 
 598         do_action( 'woocommerce_create_product_variation', $variation_id );
 599 
 600         if ( $variation_id ) {
 601 
 602             $variation_post_status = 'publish';
 603             $variation_data = get_post_meta( $variation_id );
 604             $variation_data['variation_post_id'] = $variation_id;
 605 
 606             // Get attributes
 607             $attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
 608 
 609             // Get tax classes
 610             $tax_classes                 = array_filter(array_map('trim', explode("\n", get_option('woocommerce_tax_classes'))));
 611             $tax_class_options           = array();
 612             $tax_class_options['parent'] =__( 'Same as parent', 'woocommerce' );
 613             $tax_class_options['']       = __( 'Standard', 'woocommerce' );
 614 
 615             if ( $tax_classes ) {
 616                 foreach ( $tax_classes as $class ) {
 617                     $tax_class_options[ sanitize_title( $class ) ] = $class;
 618                 }
 619             }
 620 
 621             // Get parent data
 622             $parent_data = array(
 623                 'id'                => $post_id,
 624                 'attributes'        => $attributes,
 625                 'tax_class_options' => $tax_class_options,
 626                 'sku'               => get_post_meta( $post_id, '_sku', true ),
 627                 'weight'            => get_post_meta( $post_id, '_weight', true ),
 628                 'length'            => get_post_meta( $post_id, '_length', true ),
 629                 'width'             => get_post_meta( $post_id, '_width', true ),
 630                 'height'           => get_post_meta( $post_id, '_height', true ),
 631                 'tax_class'        => get_post_meta( $post_id, '_tax_class', true )
 632             );
 633 
 634             if ( ! $parent_data['weight'] ) {
 635                 $parent_data['weight'] = '0.00';
 636             }
 637 
 638             if ( ! $parent_data['length'] ) {
 639                 $parent_data['length'] = '0';
 640             }
 641 
 642             if ( ! $parent_data['width'] ) {
 643                 $parent_data['width'] = '0';
 644             }
 645 
 646             if ( ! $parent_data['height'] ) {
 647                 $parent_data['height'] = '0';
 648             }
 649 
 650             $_tax_class          = '';
 651             $_downloadable_files = '';
 652             $image_id            = 0;
 653             $variation           = get_post( $variation_id ); // Get the variation object
 654 
 655             include( 'admin/post-types/meta-boxes/views/html-variation-admin.php' );
 656         }
 657 
 658         die();
 659     }
 660 
 661     /**
 662      * Link all variations via ajax function
 663      */
 664     public function link_all_variations() {
 665 
 666         if ( ! defined( 'WC_MAX_LINKED_VARIATIONS' ) ) {
 667             define( 'WC_MAX_LINKED_VARIATIONS', 49 );
 668         }
 669 
 670         check_ajax_referer( 'link-variations', 'security' );
 671 
 672         @set_time_limit(0);
 673 
 674         $post_id = intval( $_POST['post_id'] );
 675 
 676         if ( ! $post_id ) {
 677             die();
 678         }
 679 
 680         $variations = array();
 681         $_product   = get_product( $post_id, array( 'product_type' => 'variable' ) );
 682 
 683         // Put variation attributes into an array
 684         foreach ( $_product->get_attributes() as $attribute ) {
 685 
 686             if ( ! $attribute['is_variation'] ) {
 687                 continue;
 688             }
 689 
 690             $attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] );
 691 
 692             if ( $attribute['is_taxonomy'] ) {
 693                 $options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) );
 694             } else {
 695                 $options = explode( WC_DELIMITER, $attribute['value'] );
 696             }
 697 
 698             $options = array_map( 'sanitize_title', array_map( 'trim', $options ) );
 699 
 700             $variations[ $attribute_field_name ] = $options;
 701         }
 702 
 703         // Quit out if none were found
 704         if ( sizeof( $variations ) == 0 ) {
 705             die();
 706         }
 707 
 708         // Get existing variations so we don't create duplicates
 709         $available_variations = array();
 710 
 711         foreach( $_product->get_children() as $child_id ) {
 712             $child = $_product->get_child( $child_id );
 713 
 714             if ( ! empty( $child->variation_id ) ) {
 715                 $available_variations[] = $child->get_variation_attributes();
 716             }
 717         }
 718 
 719         // Created posts will all have the following data
 720         $variation_post_data = array(
 721             'post_title'   => 'Product #' . $post_id . ' Variation',
 722             'post_content' => '',
 723             'post_status'  => 'publish',
 724             'post_author'  => get_current_user_id(),
 725             'post_parent'  => $post_id,
 726             'post_type'    => 'product_variation'
 727         );
 728 
 729         // Now find all combinations and create posts
 730         if ( ! function_exists( 'array_cartesian' ) ) {
 731 
 732             /**
 733              * @param array $input
 734              * @return array
 735              */
 736             function array_cartesian( $input ) {
 737                 $result = array();
 738 
 739                 while ( list( $key, $values ) = each( $input ) ) {
 740                     // If a sub-array is empty, it doesn't affect the cartesian product
 741                     if ( empty( $values ) ) {
 742                         continue;
 743                     }
 744 
 745                     // Special case: seeding the product array with the values from the first sub-array
 746                     if ( empty( $result ) ) {
 747                         foreach ( $values as $value ) {
 748                             $result[] = array( $key => $value );
 749                         }
 750                     }
 751                     else {
 752                         // Second and subsequent input sub-arrays work like this:
 753                         //   1. In each existing array inside $product, add an item with
 754                         //      key == $key and value == first item in input sub-array
 755                         //   2. Then, for each remaining item in current input sub-array,
 756                         //      add a copy of each existing array inside $product with
 757                         //      key == $key and value == first item in current input sub-array
 758 
 759                         // Store all items to be added to $product here; adding them on the spot
 760                         // inside the foreach will result in an infinite loop
 761                         $append = array();
 762                         foreach ( $result as &$product ) {
 763                             // Do step 1 above. array_shift is not the most efficient, but it
 764                             // allows us to iterate over the rest of the items with a simple
 765                             // foreach, making the code short and familiar.
 766                             $product[ $key ] = array_shift( $values );
 767 
 768                             // $product is by reference (that's why the key we added above
 769                             // will appear in the end result), so make a copy of it here
 770                             $copy = $product;
 771 
 772                             // Do step 2 above.
 773                             foreach ( $values as $item ) {
 774                                 $copy[ $key ] = $item;
 775                                 $append[] = $copy;
 776                             }
 777 
 778                             // Undo the side effecst of array_shift
 779                             array_unshift( $values, $product[ $key ] );
 780                         }
 781 
 782                         // Out of the foreach, we can add to $results now
 783                         $result = array_merge( $result, $append );
 784                     }
 785                 }
 786 
 787                 return $result;
 788             }
 789         }
 790 
 791         $variation_ids       = array();
 792         $added               = 0;
 793         $possible_variations = array_cartesian( $variations );
 794 
 795         foreach ( $possible_variations as $variation ) {
 796 
 797             // Check if variation already exists
 798             if ( in_array( $variation, $available_variations ) ) {
 799                 continue;
 800             }
 801 
 802             $variation_id = wp_insert_post( $variation_post_data );
 803 
 804             $variation_ids[] = $variation_id;
 805 
 806             foreach ( $variation as $key => $value ) {
 807                 update_post_meta( $variation_id, $key, $value );
 808             }
 809 
 810             $added++;
 811 
 812             do_action( 'product_variation_linked', $variation_id );
 813 
 814             if ( $added > WC_MAX_LINKED_VARIATIONS ) {
 815                 break;
 816             }
 817         }
 818 
 819         wc_delete_product_transients( $post_id );
 820 
 821         echo $added;
 822 
 823         die();
 824     }
 825 
 826     /**
 827      * Delete download permissions via ajax function
 828      */
 829     public function revoke_access_to_download() {
 830 
 831         check_ajax_referer( 'revoke-access', 'security' );
 832 
 833         global $wpdb;
 834 
 835         $download_id = $_POST['download_id'];
 836         $product_id  = intval( $_POST['product_id'] );
 837         $order_id    = intval( $_POST['order_id'] );
 838 
 839         $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s;", $order_id, $product_id, $download_id ) );
 840 
 841         do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
 842 
 843         die();
 844     }
 845 
 846     /**
 847      * Grant download permissions via ajax function
 848      */
 849     public function grant_access_to_download() {
 850 
 851         check_ajax_referer( 'grant-access', 'security' );
 852 
 853         global $wpdb;
 854 
 855         $wpdb->hide_errors();
 856 
 857         $order_id     = intval( $_POST['order_id'] );
 858         $product_ids  = $_POST['product_ids'];
 859         $loop         = intval( $_POST['loop'] );
 860         $file_counter = 0;
 861         $order        = new WC_Order( $order_id );
 862 
 863         if ( ! is_array( $product_ids ) ) {
 864             $product_ids = array( $product_ids );
 865         }
 866 
 867         foreach ( $product_ids as $product_id ) {
 868             $product = get_product( $product_id );
 869             $files   = $product->get_files();
 870 
 871             if ( ! $order->billing_email ) {
 872                 die();
 873             }
 874 
 875             if ( $files ) {
 876                 foreach ( $files as $download_id => $file ) {
 877                     if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) {
 878 
 879                         // insert complete - get inserted data
 880                         $download = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $inserted_id ) );
 881 
 882                         $loop ++;
 883                         $file_counter ++;
 884 
 885                         if ( isset( $file['name'] ) ) {
 886                             $file_count = $file['name'];
 887                         } else {
 888                             $file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
 889                         }
 890                         include( 'admin/post-types/meta-boxes/views/html-order-download-permission.php' );
 891                     }
 892                 }
 893             }
 894         }
 895 
 896         die();
 897     }
 898 
 899     /**
 900      * Get customer details via ajax
 901      */
 902     public function get_customer_details() {
 903 
 904         check_ajax_referer( 'get-customer-details', 'security' );
 905 
 906         $this->json_headers();
 907 
 908         $user_id      = (int) trim(stripslashes($_POST['user_id']));
 909         $type_to_load = esc_attr(trim(stripslashes($_POST['type_to_load'])));
 910 
 911         $customer_data = array(
 912             $type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
 913             $type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
 914             $type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
 915             $type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
 916             $type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
 917             $type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
 918             $type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
 919             $type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
 920             $type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
 921             $type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
 922             $type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
 923         );
 924 
 925         $customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data );
 926 
 927         echo json_encode( $customer_data );
 928 
 929         // Quit out
 930         die();
 931     }
 932 
 933     /**
 934      * Add order item via ajax
 935      */
 936     public function add_order_item() {
 937         global $wpdb;
 938 
 939         check_ajax_referer( 'order-item', 'security' );
 940 
 941         $item_to_add = sanitize_text_field( $_POST['item_to_add'] );
 942         $order_id    = absint( $_POST['order_id'] );
 943 
 944         // Find the item
 945         if ( ! is_numeric( $item_to_add ) ) {
 946             die();
 947         }
 948 
 949         $post = get_post( $item_to_add );
 950 
 951         if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
 952             die();
 953         }
 954 
 955         $_product = get_product( $post->ID );
 956         $order    = new WC_Order( $order_id );
 957         $class    = 'new_row';
 958 
 959         // Set values
 960         $item = array();
 961 
 962         $item['product_id']        = $_product->id;
 963         $item['variation_id']      = isset( $_product->variation_id ) ? $_product->variation_id : '';
 964         $item['variation_data']    = isset( $_product->variation_data ) ? $_product->variation_data : '';
 965         $item['name']              = $_product->get_title();
 966         $item['tax_class']         = $_product->get_tax_class();
 967         $item['qty']               = 1;
 968         $item['line_subtotal']     = wc_format_decimal( $_product->get_price_excluding_tax() );
 969         $item['line_subtotal_tax'] = '';
 970         $item['line_total']        = wc_format_decimal( $_product->get_price_excluding_tax() );
 971         $item['line_tax']          = '';
 972 
 973         // Add line item
 974         $item_id = wc_add_order_item( $order_id, array(
 975             'order_item_name'       => $item['name'],
 976             'order_item_type'       => 'line_item'
 977         ) );
 978 
 979         // Add line item meta
 980         if ( $item_id ) {
 981             wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
 982             wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
 983             wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
 984             wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
 985             wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
 986             wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
 987             wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
 988             wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
 989 
 990             // Store variation data in meta
 991             if ( $item['variation_data'] && is_array( $item['variation_data'] ) ) {
 992                 foreach ( $item['variation_data'] as $key => $value ) {
 993                     wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
 994                 }
 995             }
 996 
 997             do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
 998         }
 999 
1000         $item = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
1001 
1002         include( 'admin/post-types/meta-boxes/views/html-order-item.php' );
1003 
1004         // Quit out
1005         die();
1006     }
1007 
1008     /**
1009      * Add order fee via ajax
1010      */
1011     public function add_order_fee() {
1012 
1013         check_ajax_referer( 'order-item', 'security' );
1014 
1015         $order_id = absint( $_POST['order_id'] );
1016         $order    = new WC_Order( $order_id );
1017 
1018         // Add line item
1019         $item_id = wc_add_order_item( $order_id, array(
1020             'order_item_name' => '',
1021             'order_item_type' => 'fee'
1022         ) );
1023 
1024         // Add line item meta
1025         if ( $item_id ) {
1026             wc_add_order_item_meta( $item_id, '_tax_class', '' );
1027             wc_add_order_item_meta( $item_id, '_line_total', '' );
1028             wc_add_order_item_meta( $item_id, '_line_tax', '' );
1029         }
1030 
1031         include( 'admin/post-types/meta-boxes/views/html-order-fee.php' );
1032 
1033         // Quit out
1034         die();
1035     }
1036 
1037     /**
1038      * Remove an order item
1039      */
1040     public function remove_order_item() {
1041         global $wpdb;
1042 
1043         check_ajax_referer( 'order-item', 'security' );
1044 
1045         $order_item_ids = $_POST['order_item_ids'];
1046 
1047         if ( sizeof( $order_item_ids ) > 0 ) {
1048             foreach( $order_item_ids as $id ) {
1049                 wc_delete_order_item( absint( $id ) );
1050             }
1051         }
1052 
1053         die();
1054     }
1055 
1056     /**
1057      * Reduce order item stock
1058      */
1059     public function reduce_order_item_stock() {
1060         global $wpdb;
1061 
1062         check_ajax_referer( 'order-item', 'security' );
1063 
1064         $order_id       = absint( $_POST['order_id'] );
1065         $order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1066         $order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1067         $order          = new WC_Order( $order_id );
1068         $order_items    = $order->get_items();
1069         $return         = array();
1070 
1071         if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1072 
1073             foreach ( $order_items as $item_id => $order_item ) {
1074 
1075                 // Only reduce checked items
1076                 if ( ! in_array( $item_id, $order_item_ids ) ) {
1077                     continue;
1078                 }
1079 
1080                 $_product = $order->get_product_from_item( $order_item );
1081 
1082                 if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1083 
1084                     $old_stock      = $_product->stock;
1085                     $stock_change   = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1086                     $new_quantity   = $_product->reduce_stock( $stock_change );
1087 
1088                     $return[] = sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity );
1089                     $order->add_order_note( sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity) );
1090                     $order->send_stock_notifications( $_product, $new_quantity, $order_item_qty[ $item_id ] );
1091                 }
1092             }
1093 
1094             do_action( 'woocommerce_reduce_order_stock', $order );
1095 
1096             if ( empty( $return ) ) {
1097                 $return[] = __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' );
1098             }
1099 
1100             echo implode( ', ', $return );
1101         }
1102 
1103         die();
1104     }
1105 
1106     /**
1107      * Increase order item stock
1108      */
1109     public function increase_order_item_stock() {
1110         global $wpdb;
1111 
1112         check_ajax_referer( 'order-item', 'security' );
1113 
1114         $order_id       = absint( $_POST['order_id'] );
1115         $order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
1116         $order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
1117         $order          = new WC_Order( $order_id );
1118         $order_items    = $order->get_items();
1119         $return         = array();
1120 
1121         if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
1122 
1123             foreach ( $order_items as $item_id => $order_item ) {
1124 
1125                 // Only reduce checked items
1126                 if ( ! in_array( $item_id, $order_item_ids ) ) {
1127                     continue;
1128                 }
1129 
1130                 $_product = $order->get_product_from_item( $order_item );
1131 
1132                 if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
1133 
1134                     $old_stock    = $_product->stock;
1135                     $stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
1136                     $new_quantity = $_product->increase_stock( $stock_change );
1137 
1138                     $return[] = sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity );
1139                     $order->add_order_note( sprintf( __( 'Item #%s stock increased from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
1140                 }
1141             }
1142 
1143             do_action( 'woocommerce_restore_order_stock', $order );
1144 
1145             if ( empty( $return ) ) {
1146                 $return[] = __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' );
1147             }
1148 
1149             echo implode( ', ', $return );
1150         }
1151 
1152         die();
1153     }
1154 
1155     /**
1156      * Add some meta to a line item
1157      */
1158     public function add_order_item_meta() {
1159         global $wpdb;
1160 
1161         check_ajax_referer( 'order-item', 'security' );
1162 
1163         $meta_id = wc_add_order_item_meta( absint( $_POST['order_item_id'] ), __( 'Name', 'woocommerce' ), __( 'Value', 'woocommerce' ) );
1164 
1165         if ( $meta_id ) {
1166             echo '<tr data-meta_id="' . esc_attr( $meta_id ) . '"><td><input type="text" name="meta_key[' . $meta_id . ']" /><textarea name="meta_value[' . $meta_id . ']"></textarea></td><td width="1%"><button class="remove_order_item_meta button">&times;</button></td></tr>';
1167         }
1168 
1169         die();
1170     }
1171 
1172     /**
1173      * Remove meta from a line item
1174      */
1175     public function remove_order_item_meta() {
1176         global $wpdb;
1177 
1178         check_ajax_referer( 'order-item', 'security' );
1179 
1180         $meta_id = absint( $_POST['meta_id'] );
1181 
1182         $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_id = %d", $meta_id ) );
1183 
1184         die();
1185     }
1186 
1187     /**
1188      * Calc line tax
1189      */
1190     public function calc_line_taxes() {
1191         global $wpdb;
1192 
1193         check_ajax_referer( 'calc-totals', 'security' );
1194 
1195         $this->json_headers();
1196 
1197         $tax      = new WC_Tax();
1198         $taxes    = $tax_rows = $item_taxes = $shipping_taxes = array();
1199         $order_id = absint( $_POST['order_id'] );
1200         $order    = new WC_Order( $order_id );
1201         $country  = strtoupper( esc_attr( $_POST['country'] ) );
1202         $state    = strtoupper( esc_attr( $_POST['state'] ) );
1203         $postcode = strtoupper( esc_attr( $_POST['postcode'] ) );
1204         $city     = sanitize_title( esc_attr( $_POST['city'] ) );
1205         $items    = isset( $_POST['items'] ) ? $_POST['items'] : array();
1206         $shipping = $_POST['shipping'];
1207         $item_tax = 0;
1208 
1209         // Calculate sales tax first
1210         if ( sizeof( $items ) > 0 ) {
1211             foreach( $items as $item_id => $item ) {
1212 
1213                 $item_id       = absint( $item_id );
1214                 $line_subtotal = isset( $item['line_subtotal'] ) ? wc_format_decimal( $item['line_subtotal'] ) : 0;
1215                 $line_total    = wc_format_decimal( $item['line_total'] );
1216                 $tax_class     = sanitize_text_field( $item['tax_class'] );
1217                 $product_id    = $order->get_item_meta( $item_id, '_product_id', true );
1218 
1219                 if ( ! $item_id || '0' == $tax_class ) {
1220                     continue;
1221                 }
1222 
1223                 // Get product details
1224                 if ( get_post_type( $product_id ) == 'product' ) {
1225                     $_product        = get_product( $product_id );
1226                     $item_tax_status = $_product->get_tax_status();
1227                 } else {
1228                     $item_tax_status = 'taxable';
1229                 }
1230 
1231                 // Only calc if taxable
1232                 if ( 'taxable' == $item_tax_status ) {
1233 
1234                     $tax_rates = $tax->find_rates( array(
1235                         'country'   => $country,
1236                         'state'     => $state,
1237                         'postcode'  => $postcode,
1238                         'city'      => $city,
1239                         'tax_class' => $tax_class
1240                     ) );
1241 
1242                     $line_subtotal_taxes = $tax->calc_tax( $line_subtotal, $tax_rates, false );
1243                     $line_taxes          = $tax->calc_tax( $line_total, $tax_rates, false );
1244                     $line_subtotal_tax   = array_sum( $line_subtotal_taxes );
1245                     $line_tax            = array_sum( $line_taxes );
1246 
1247                     if ( $line_subtotal_tax < 0 ) {
1248                         $line_subtotal_tax = 0;
1249                     }
1250 
1251                     if ( $line_tax < 0 ) {
1252                         $line_tax = 0;
1253                     }
1254 
1255                     $item_taxes[ $item_id ] = array(
1256                         'line_subtotal_tax' => wc_format_localized_price( $line_subtotal_tax ),
1257                         'line_tax'          => wc_format_localized_price( $line_tax )
1258                     );
1259 
1260                     $item_tax += $line_tax;
1261 
1262                     // Sum the item taxes
1263                     foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
1264                         $taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1265                     }
1266                 }
1267 
1268             }
1269         }
1270 
1271         // Now calculate shipping tax
1272         $matched_tax_rates = array();
1273 
1274         $tax_rates = $tax->find_rates( array(
1275             'country'   => $country,
1276             'state'     => $state,
1277             'postcode'  => $postcode,
1278             'city'      => $city,
1279             'tax_class' => ''
1280         ) );
1281 
1282         if ( $tax_rates ) {
1283             foreach ( $tax_rates as $key => $rate ) {
1284                 if ( isset( $rate['shipping'] ) && 'yes' == $rate['shipping'] ) {
1285                     $matched_tax_rates[ $key ] = $rate;
1286                 }
1287             }
1288         }
1289 
1290         $shipping_taxes = $tax->calc_shipping_tax( $shipping, $matched_tax_rates );
1291         $shipping_tax   = $tax->round( array_sum( $shipping_taxes ) );
1292 
1293         // Remove old tax rows
1294         $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'tax' )", $order_id ) );
1295 
1296         $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'tax'", $order_id ) );
1297 
1298         // Get tax rates
1299         $rates = $wpdb->get_results( "SELECT tax_rate_id, tax_rate_country, tax_rate_state, tax_rate_name, tax_rate_priority FROM {$wpdb->prefix}woocommerce_tax_rates ORDER BY tax_rate_name" );
1300 
1301         $tax_codes = array();
1302 
1303         foreach( $rates as $rate ) {
1304             $code = array();
1305 
1306             $code[] = $rate->tax_rate_country;
1307             $code[] = $rate->tax_rate_state;
1308             $code[] = $rate->tax_rate_name ? sanitize_title( $rate->tax_rate_name ) : 'TAX';
1309             $code[] = absint( $rate->tax_rate_priority );
1310 
1311             $tax_codes[ $rate->tax_rate_id ] = strtoupper( implode( '-', array_filter( $code ) ) );
1312         }
1313 
1314         // Now merge to keep tax rows
1315         ob_start();
1316 
1317         foreach ( array_keys( $taxes + $shipping_taxes ) as $key ) {
1318 
1319             $item                        = array();
1320             $item['rate_id']             = $key;
1321             $item['name']                = $tax_codes[ $key ];
1322             $item['label']               = $tax->get_rate_label( $key );
1323             $item['compound']            = $tax->is_compound( $key ) ? 1 : 0;
1324             $item['tax_amount']          = wc_format_decimal( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
1325             $item['shipping_tax_amount'] = wc_format_decimal( isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 );
1326 
1327             if ( ! $item['label'] ) {
1328                 $item['label'] = WC()->countries->tax_or_vat();
1329             }
1330 
1331             // Add line item
1332             $item_id = wc_add_order_item( $order_id, array(
1333                 'order_item_name' => $item['name'],
1334                 'order_item_type' => 'tax'
1335             ) );
1336 
1337             // Add line item meta
1338             if ( $item_id ) {
1339                 wc_add_order_item_meta( $item_id, 'rate_id', $item['rate_id'] );
1340                 wc_add_order_item_meta( $item_id, 'label', $item['label'] );
1341                 wc_add_order_item_meta( $item_id, 'compound', $item['compound'] );
1342                 wc_add_order_item_meta( $item_id, 'tax_amount', $item['tax_amount'] );
1343                 wc_add_order_item_meta( $item_id, 'shipping_tax_amount', $item['shipping_tax_amount'] );
1344             }
1345 
1346             include( 'admin/post-types/meta-boxes/views/html-order-tax.php' );
1347         }
1348 
1349         $tax_row_html = ob_get_clean();
1350 
1351         // Return
1352         echo json_encode( array(
1353             'item_tax'     => $item_tax,
1354             'item_taxes'   => $item_taxes,
1355             'shipping_tax' => $shipping_tax,
1356             'tax_row_html' => $tax_row_html
1357         ) );
1358 
1359         // Quit out
1360         die();
1361     }
1362 
1363     /**
1364      * Add order note via ajax
1365      */
1366     public function add_order_note() {
1367 
1368         check_ajax_referer( 'add-order-note', 'security' );
1369 
1370         $post_id   = (int) $_POST['post_id'];
1371         $note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
1372         $note_type = $_POST['note_type'];
1373 
1374         $is_customer_note = $note_type == 'customer' ? 1 : 0;
1375 
1376         if ( $post_id > 0 ) {
1377             $order      = new WC_Order( $post_id );
1378             $comment_id = $order->add_order_note( $note, $is_customer_note );
1379 
1380             echo '<li rel="' . esc_attr( $comment_id ) . '" class="note ';
1381             if ( $is_customer_note ) {
1382                 echo 'customer-note';
1383             }
1384             echo '"><div class="note_content">';
1385             echo wpautop( wptexturize( $note ) );
1386             echo '</div><p class="meta"><a href="#" class="delete_note">'.__( 'Delete note', 'woocommerce' ).'</a></p>';
1387             echo '</li>';
1388         }
1389 
1390         // Quit out
1391         die();
1392     }
1393 
1394     /**
1395      * Delete order note via ajax
1396      */
1397     public function delete_order_note() {
1398 
1399         check_ajax_referer( 'delete-order-note', 'security' );
1400 
1401         $note_id = (int) $_POST['note_id'];
1402 
1403         if ( $note_id > 0 ) {
1404             wp_delete_comment( $note_id );
1405         }
1406 
1407         // Quit out
1408         die();
1409     }
1410 
1411     /**
1412      * Search for products and echo json
1413      *
1414      * @param string $x (default: '')
1415      * @param string $post_types (default: array('product'))
1416      */
1417     public function json_search_products( $x = '', $post_types = array('product') ) {
1418 
1419         check_ajax_referer( 'search-products', 'security' );
1420 
1421         $this->json_headers();
1422 
1423         $term = (string) wc_clean( stripslashes( $_GET['term'] ) );
1424 
1425         if ( empty( $term ) ) {
1426             die();
1427         }
1428 
1429         if ( is_numeric( $term ) ) {
1430 
1431             $args = array(
1432                 'post_type'      => $post_types,
1433                 'post_status'    => 'publish',
1434                 'posts_per_page' => -1,
1435                 'post__in'       => array(0, $term),
1436                 'fields'         => 'ids'
1437             );
1438 
1439             $args2 = array(
1440                 'post_type'      => $post_types,
1441                 'post_status'    => 'publish',
1442                 'posts_per_page' => -1,
1443                 'post_parent'    => $term,
1444                 'fields'         => 'ids'
1445             );
1446 
1447             $args3 = array(
1448                 'post_type'      => $post_types,
1449                 'post_status'    => 'publish',
1450                 'posts_per_page' => -1,
1451                 'meta_query'     => array(
1452                     array(
1453                         'key'     => '_sku',
1454                         'value'   => $term,
1455                         'compare' => 'LIKE'
1456                     )
1457                 ),
1458                 'fields'         => 'ids'
1459             );
1460 
1461             $posts = array_unique( array_merge( get_posts( $args ), get_posts( $args2 ), get_posts( $args3 ) ) );
1462 
1463         } else {
1464 
1465             $args = array(
1466                 'post_type'      => $post_types,
1467                 'post_status'    => 'publish',
1468                 'posts_per_page' => -1,
1469                 's'              => $term,
1470                 'fields'         => 'ids'
1471             );
1472 
1473             $args2 = array(
1474                 'post_type'      => $post_types,
1475                 'post_status'    => 'publish',
1476                 'posts_per_page' => -1,
1477                 'meta_query'     => array(
1478                     array(
1479                     'key'     => '_sku',
1480                     'value'   => $term,
1481                     'compare' => 'LIKE'
1482                     )
1483                 ),
1484                 'fields'         => 'ids'
1485             );
1486 
1487             $posts = array_unique( array_merge( get_posts( $args ), get_posts( $args2 ) ) );
1488 
1489         }
1490 
1491         $found_products = array();
1492 
1493         if ( $posts ) {
1494             foreach ( $posts as $post ) {
1495                 $product = get_product( $post );
1496 
1497                 $found_products[ $post ] = $product->get_formatted_name();
1498             }
1499         }
1500 
1501         $found_products = apply_filters( 'woocommerce_json_search_found_products', $found_products );
1502 
1503         echo json_encode( $found_products );
1504 
1505         die();
1506     }
1507 
1508     /**
1509      * Search for product variations and return json
1510      *
1511      * @access public
1512      * @return void
1513      * @see WC_AJAX::json_search_products()
1514      */
1515     public function json_search_products_and_variations() {
1516         $this->json_search_products( '', array('product', 'product_variation') );
1517     }
1518 
1519     /**
1520      * Search for customers and return json
1521      */
1522     public function json_search_customers() {
1523 
1524         check_ajax_referer( 'search-customers', 'security' );
1525 
1526         $this->json_headers();
1527 
1528         $term = wc_clean( stripslashes( $_GET['term'] ) );
1529 
1530         if ( empty( $term ) ) {
1531             die();
1532         }
1533 
1534         $default = isset( $_GET['default'] ) ? $_GET['default'] : __( 'Guest', 'woocommerce' );
1535 
1536         $found_customers = array( '' => $default );
1537 
1538         add_action( 'pre_user_query', array( $this, 'json_search_customer_name' ) );
1539 
1540         $customers_query = new WP_User_Query( apply_filters( 'woocommerce_json_search_customers_query', array(
1541             'fields'         => 'all',
1542             'orderby'        => 'display_name',
1543             'search'         => '*' . $term . '*',
1544             'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename' )
1545         ) ) );
1546 
1547         remove_action( 'pre_user_query', array( $this, 'json_search_customer_name' ) );
1548 
1549         $customers = $customers_query->get_results();
1550 
1551         if ( $customers ) {
1552             foreach ( $customers as $customer ) {
1553                 $found_customers[ $customer->ID ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
1554             }
1555         }
1556 
1557         echo json_encode( $found_customers );
1558         die();
1559     }
1560 
1561     /**
1562      * Search for downloadable product variations and return json
1563      *
1564      * @access public
1565      * @return void
1566      * @see WC_AJAX::json_search_products()
1567      */
1568     public function json_search_downloadable_products_and_variations() {
1569         $term = (string) wc_clean( stripslashes( $_GET['term'] ) );
1570 
1571         $args = array(
1572             'post_type'      => array( 'product', 'product_variation' ),
1573             'posts_per_page' => -1,
1574             'post_status'    => 'publish',
1575             'order'          => 'ASC',
1576             'orderby'        => 'parent title',
1577             'meta_query'     => array(
1578                 array(
1579                     'key'   => '_downloadable',
1580                     'value' => 'yes'
1581                 )
1582             ),
1583             's'              => $term
1584         );
1585 
1586         $posts = get_posts( $args );
1587         $found_products = array();
1588 
1589         if ( $posts ) {
1590             foreach ( $posts as $post ) {
1591                 $product = get_product( $post->ID );
1592                 $found_products[ $post->ID ] = $product->get_formatted_name();
1593             }
1594         }
1595 
1596         echo json_encode( $found_products );
1597         die();
1598     }
1599 
1600     /**
1601      * When searching using the WP_User_Query, search names (user meta) too
1602      * @param  object $query
1603      * @return object
1604      */
1605     public function json_search_customer_name( $query ) {
1606         global $wpdb;
1607 
1608         $term = wc_clean( stripslashes( $_GET['term'] ) );
1609 
1610         $query->query_from  .= " INNER JOIN {$wpdb->usermeta} AS user_name ON {$wpdb->users}.ID = user_name.user_id AND ( user_name.meta_key = 'first_name' OR user_name.meta_key = 'last_name' ) ";
1611         $query->query_where .= $wpdb->prepare( " OR user_name.meta_value LIKE %s ", '%' . like_escape( $term ) . '%' );
1612     }
1613 
1614     /**
1615      * Ajax request handling for categories ordering
1616      */
1617     public function term_ordering() {
1618         global $wpdb;
1619 
1620         $id       = (int) $_POST['id'];
1621         $next_id  = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null;
1622         $taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null;
1623         $term     = get_term_by('id', $id, $taxonomy);
1624 
1625         if ( ! $id || ! $term || ! $taxonomy ) {
1626             die(0);
1627         }
1628 
1629         wc_reorder_terms( $term, $next_id, $taxonomy );
1630 
1631         $children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
1632 
1633         if ( $term && sizeof( $children ) ) {
1634             echo 'children';
1635             die();
1636         }
1637     }
1638 
1639     /**
1640      * Ajax request handling for product ordering
1641      *
1642      * Based on Simple Page Ordering by 10up (http://wordpress.org/extend/plugins/simple-page-ordering/)
1643      */
1644     public function product_ordering() {
1645         global $wpdb;
1646 
1647         // check permissions again and make sure we have what we need
1648         if ( ! current_user_can('edit_products') || empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
1649             die(-1);
1650         }
1651 
1652         // real post?
1653         if ( ! $post = get_post( $_POST['id'] ) ) {
1654             die(-1);
1655         }
1656 
1657         $this->json_headers();
1658 
1659         $previd  = isset( $_POST['previd'] ) ? $_POST['previd'] : false;
1660         $nextid  = isset( $_POST['nextid'] ) ? $_POST['nextid'] : false;
1661         $new_pos = array(); // store new positions for ajax
1662 
1663         $siblings = $wpdb->get_results( $wpdb->prepare('
1664             SELECT ID, menu_order FROM %1$s AS posts
1665             WHERE   posts.post_type     = \'product\'
1666             AND     posts.post_status   IN ( \'publish\', \'pending\', \'draft\', \'future\', \'private\' )
1667             AND     posts.ID            NOT IN (%2$d)
1668             ORDER BY posts.menu_order ASC, posts.ID DESC
1669         ', $wpdb->posts, $post->ID) );
1670 
1671         $menu_order = 0;
1672 
1673         foreach ( $siblings as $sibling ) {
1674 
1675             // if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
1676             if ( $nextid == $sibling->ID ) {
1677                 $wpdb->update(
1678                     $wpdb->posts,
1679                     array(
1680                         'menu_order' => $menu_order
1681                     ),
1682                     array( 'ID' => $post->ID ),
1683                     array( '%d' ),
1684                     array( '%d' )
1685                 );
1686                 $new_pos[ $post->ID ] = $menu_order;
1687                 $menu_order++;
1688             }
1689 
1690             // if repositioned post has been set, and new items are already in the right order, we can stop
1691             if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $menu_order ) {
1692                 break;
1693             }
1694 
1695             // set the menu order of the current sibling and increment the menu order
1696             $wpdb->update(
1697                 $wpdb->posts,
1698                 array(
1699                     'menu_order' => $menu_order
1700                 ),
1701                 array( 'ID' => $sibling->ID ),
1702                 array( '%d' ),
1703                 array( '%d' )
1704             );
1705             $new_pos[ $sibling->ID ] = $menu_order;
1706             $menu_order++;
1707 
1708             if ( ! $nextid && $previd == $sibling->ID ) {
1709                 $wpdb->update(
1710                     $wpdb->posts,
1711                     array(
1712                         'menu_order' => $menu_order
1713                     ),
1714                     array( 'ID' => $post->ID ),
1715                     array( '%d' ),
1716                     array( '%d' )
1717                 );
1718                 $new_pos[$post->ID] = $menu_order;
1719                 $menu_order++;
1720             }
1721 
1722         }
1723 
1724         die( json_encode( $new_pos ) );
1725     }
1726 }
1727 
1728 new WC_AJAX();
1729 
WooCommerce API documentation generated by ApiGen 2.8.0