/includes/class-wc-shortcodes.php

  1. <?php 
  2.  
  3. if ( ! defined( 'ABSPATH' ) ) { 
  4. exit; // Exit if accessed directly 
  5.  
  6. /** 
  7. * WC_Shortcodes class 
  8. * 
  9. * @class WC_Shortcodes 
  10. * @version 2.1.0 
  11. * @package WooCommerce/Classes 
  12. * @category Class 
  13. * @author WooThemes 
  14. */ 
  15. class WC_Shortcodes { 
  16.  
  17. /** 
  18. * Init shortcodes. 
  19. */ 
  20. public static function init() { 
  21. $shortcodes = array( 
  22. 'product' => __CLASS__ . '::product',  
  23. 'product_page' => __CLASS__ . '::product_page',  
  24. 'product_category' => __CLASS__ . '::product_category',  
  25. 'product_categories' => __CLASS__ . '::product_categories',  
  26. 'add_to_cart' => __CLASS__ . '::product_add_to_cart',  
  27. 'add_to_cart_url' => __CLASS__ . '::product_add_to_cart_url',  
  28. 'products' => __CLASS__ . '::products',  
  29. 'recent_products' => __CLASS__ . '::recent_products',  
  30. 'sale_products' => __CLASS__ . '::sale_products',  
  31. 'best_selling_products' => __CLASS__ . '::best_selling_products',  
  32. 'top_rated_products' => __CLASS__ . '::top_rated_products',  
  33. 'featured_products' => __CLASS__ . '::featured_products',  
  34. 'product_attribute' => __CLASS__ . '::product_attribute',  
  35. 'related_products' => __CLASS__ . '::related_products',  
  36. 'shop_messages' => __CLASS__ . '::shop_messages',  
  37. 'woocommerce_order_tracking' => __CLASS__ . '::order_tracking',  
  38. 'woocommerce_cart' => __CLASS__ . '::cart',  
  39. 'woocommerce_checkout' => __CLASS__ . '::checkout',  
  40. 'woocommerce_my_account' => __CLASS__ . '::my_account',  
  41. ); 
  42.  
  43. foreach ( $shortcodes as $shortcode => $function ) { 
  44. add_shortcode( apply_filters( "{$shortcode}_shortcode_tag", $shortcode ), $function ); 
  45.  
  46. // Alias for pre 2.1 compatibility 
  47. add_shortcode( 'woocommerce_messages', __CLASS__ . '::shop_messages' ); 
  48.  
  49. /** 
  50. * Shortcode Wrapper. 
  51. * 
  52. * @param string[] $function 
  53. * @param array $atts (default: array()) 
  54. * @return string 
  55. */ 
  56. public static function shortcode_wrapper( 
  57. $function,  
  58. $atts = array(),  
  59. $wrapper = array( 
  60. 'class' => 'woocommerce',  
  61. 'before' => null,  
  62. 'after' => null,  
  63. ) { 
  64. ob_start(); 
  65.  
  66. echo empty( $wrapper['before'] ) ? '<div class="' . esc_attr( $wrapper['class'] ) . '">' : $wrapper['before']; 
  67. call_user_func( $function, $atts ); 
  68. echo empty( $wrapper['after'] ) ? '</div>' : $wrapper['after']; 
  69.  
  70. return ob_get_clean(); 
  71.  
  72. /** 
  73. * Loop over found products. 
  74. * @param array $query_args 
  75. * @param array $atts 
  76. * @param string $loop_name 
  77. * @return string 
  78. */ 
  79. private static function product_loop( $query_args, $atts, $loop_name ) { 
  80. global $woocommerce_loop; 
  81.  
  82. $columns = absint( $atts['columns'] ); 
  83. $woocommerce_loop['columns'] = $columns; 
  84. $woocommerce_loop['name'] = $loop_name; 
  85. $query_args = apply_filters( 'woocommerce_shortcode_products_query', $query_args, $atts, $loop_name ); 
  86. $transient_name = 'wc_loop' . substr( md5( json_encode( $query_args ) . $loop_name ), 28 ) . WC_Cache_Helper::get_transient_version( 'product_query' ); 
  87. $products = get_transient( $transient_name ); 
  88.  
  89. if ( false === $products || ! is_a( $products, 'WP_Query' ) ) { 
  90. $products = new WP_Query( $query_args ); 
  91. set_transient( $transient_name, $products, DAY_IN_SECONDS * 30 ); 
  92.  
  93. ob_start(); 
  94.  
  95. if ( $products->have_posts() ) { 
  96. ?> 
  97.  
  98. <?php do_action( "woocommerce_shortcode_before_{$loop_name}_loop", $atts ); ?> 
  99.  
  100. <?php woocommerce_product_loop_start(); ?> 
  101.  
  102. <?php while ( $products->have_posts() ) : $products->the_post(); ?> 
  103.  
  104. <?php wc_get_template_part( 'content', 'product' ); ?> 
  105.  
  106. <?php endwhile; // end of the loop. ?> 
  107.  
  108. <?php woocommerce_product_loop_end(); ?> 
  109.  
  110. <?php do_action( "woocommerce_shortcode_after_{$loop_name}_loop", $atts ); ?> 
  111.  
  112. <?php 
  113. } else { 
  114. do_action( "woocommerce_shortcode_{$loop_name}_loop_no_results", $atts ); 
  115.  
  116. woocommerce_reset_loop(); 
  117. wp_reset_postdata(); 
  118.  
  119. return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>'; 
  120.  
  121. /** 
  122. * Cart page shortcode. 
  123. * 
  124. * @return string 
  125. */ 
  126. public static function cart() { 
  127. return is_null( WC()->cart ) ? '' : self::shortcode_wrapper( array( 'WC_Shortcode_Cart', 'output' ) ); 
  128.  
  129. /** 
  130. * Checkout page shortcode. 
  131. * 
  132. * @param mixed $atts 
  133. * @return string 
  134. */ 
  135. public static function checkout( $atts ) { 
  136. return self::shortcode_wrapper( array( 'WC_Shortcode_Checkout', 'output' ), $atts ); 
  137.  
  138. /** 
  139. * Order tracking page shortcode. 
  140. * 
  141. * @param mixed $atts 
  142. * @return string 
  143. */ 
  144. public static function order_tracking( $atts ) { 
  145. return self::shortcode_wrapper( array( 'WC_Shortcode_Order_Tracking', 'output' ), $atts ); 
  146.  
  147. /** 
  148. * My account page shortcode. 
  149. * 
  150. * @param mixed $atts 
  151. * @return string 
  152. */ 
  153. public static function my_account( $atts ) { 
  154. return self::shortcode_wrapper( array( 'WC_Shortcode_My_Account', 'output' ), $atts ); 
  155.  
  156. /** 
  157. * List products in a category shortcode. 
  158. * 
  159. * @param array $atts 
  160. * @return string 
  161. */ 
  162. public static function product_category( $atts ) { 
  163. $atts = shortcode_atts( array( 
  164. 'per_page' => '12',  
  165. 'columns' => '4',  
  166. 'orderby' => 'menu_order title',  
  167. 'order' => 'asc',  
  168. 'category' => '', // Slugs 
  169. 'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'. 
  170. ), $atts, 'product_category' ); 
  171.  
  172. if ( ! $atts['category'] ) { 
  173. return ''; 
  174.  
  175. // Default ordering args 
  176. $ordering_args = WC()->query->get_catalog_ordering_args( $atts['orderby'], $atts['order'] ); 
  177. $meta_query = WC()->query->get_meta_query(); 
  178. $query_args = array( 
  179. 'post_type' => 'product',  
  180. 'post_status' => 'publish',  
  181. 'ignore_sticky_posts' => 1,  
  182. 'orderby' => $ordering_args['orderby'],  
  183. 'order' => $ordering_args['order'],  
  184. 'posts_per_page' => $atts['per_page'],  
  185. 'meta_query' => $meta_query,  
  186. 'tax_query' => WC()->query->get_tax_query(),  
  187. ); 
  188.  
  189. $query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] ); 
  190.  
  191. if ( isset( $ordering_args['meta_key'] ) ) { 
  192. $query_args['meta_key'] = $ordering_args['meta_key']; 
  193.  
  194. $return = self::product_loop( $query_args, $atts, 'product_cat' ); 
  195.  
  196. // Remove ordering query arguments 
  197. WC()->query->remove_ordering_args(); 
  198.  
  199. return $return; 
  200.  
  201.  
  202. /** 
  203. * List all (or limited) product categories. 
  204. * 
  205. * @param array $atts 
  206. * @return string 
  207. */ 
  208. public static function product_categories( $atts ) { 
  209. global $woocommerce_loop; 
  210.  
  211. $atts = shortcode_atts( array( 
  212. 'number' => null,  
  213. 'orderby' => 'name',  
  214. 'order' => 'ASC',  
  215. 'columns' => '4',  
  216. 'hide_empty' => 1,  
  217. 'parent' => '',  
  218. 'ids' => '',  
  219. ), $atts, 'product_categories' ); 
  220.  
  221. $ids = array_filter( array_map( 'trim', explode( ', ', $atts['ids'] ) ) ); 
  222. $hide_empty = ( true === $atts['hide_empty'] || 'true' === $atts['hide_empty'] || 1 === $atts['hide_empty'] || '1' === $atts['hide_empty'] ) ? 1 : 0; 
  223.  
  224. // get terms and workaround WP bug with parents/pad counts 
  225. $args = array( 
  226. 'orderby' => $atts['orderby'],  
  227. 'order' => $atts['order'],  
  228. 'hide_empty' => $hide_empty,  
  229. 'include' => $ids,  
  230. 'pad_counts' => true,  
  231. 'child_of' => $atts['parent'],  
  232. ); 
  233.  
  234. $product_categories = get_terms( 'product_cat', $args ); 
  235.  
  236. if ( '' !== $atts['parent'] ) { 
  237. $product_categories = wp_list_filter( $product_categories, array( 'parent' => $atts['parent'] ) ); 
  238.  
  239. if ( $hide_empty ) { 
  240. foreach ( $product_categories as $key => $category ) { 
  241. if ( 0 == $category->count ) { 
  242. unset( $product_categories[ $key ] ); 
  243.  
  244. if ( $atts['number'] ) { 
  245. $product_categories = array_slice( $product_categories, 0, $atts['number'] ); 
  246.  
  247. $columns = absint( $atts['columns'] ); 
  248. $woocommerce_loop['columns'] = $columns; 
  249.  
  250. ob_start(); 
  251.  
  252. if ( $product_categories ) { 
  253. woocommerce_product_loop_start(); 
  254.  
  255. foreach ( $product_categories as $category ) { 
  256. wc_get_template( 'content-product_cat.php', array( 
  257. 'category' => $category,  
  258. ) ); 
  259.  
  260. woocommerce_product_loop_end(); 
  261.  
  262. woocommerce_reset_loop(); 
  263.  
  264. return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>'; 
  265.  
  266. /** 
  267. * Recent Products shortcode. 
  268. * 
  269. * @param array $atts 
  270. * @return string 
  271. */ 
  272. public static function recent_products( $atts ) { 
  273. $atts = shortcode_atts( array( 
  274. 'per_page' => '12',  
  275. 'columns' => '4',  
  276. 'orderby' => 'date',  
  277. 'order' => 'desc',  
  278. 'category' => '', // Slugs 
  279. 'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'. 
  280. ), $atts, 'recent_products' ); 
  281.  
  282. $query_args = array( 
  283. 'post_type' => 'product',  
  284. 'post_status' => 'publish',  
  285. 'ignore_sticky_posts' => 1,  
  286. 'posts_per_page' => $atts['per_page'],  
  287. 'orderby' => $atts['orderby'],  
  288. 'order' => $atts['order'],  
  289. 'meta_query' => WC()->query->get_meta_query(),  
  290. 'tax_query' => WC()->query->get_tax_query(),  
  291. ); 
  292.  
  293. $query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] ); 
  294.  
  295. return self::product_loop( $query_args, $atts, 'recent_products' ); 
  296.  
  297. /** 
  298. * List multiple products shortcode. 
  299. * 
  300. * @param array $atts 
  301. * @return string 
  302. */ 
  303. public static function products( $atts ) { 
  304. $atts = shortcode_atts( array( 
  305. 'columns' => '4',  
  306. 'orderby' => 'title',  
  307. 'order' => 'asc',  
  308. 'ids' => '',  
  309. 'skus' => '',  
  310. ), $atts, 'products' ); 
  311.  
  312. $query_args = array( 
  313. 'post_type' => 'product',  
  314. 'post_status' => 'publish',  
  315. 'ignore_sticky_posts' => 1,  
  316. 'orderby' => $atts['orderby'],  
  317. 'order' => $atts['order'],  
  318. 'posts_per_page' => -1,  
  319. 'meta_query' => WC()->query->get_meta_query(),  
  320. 'tax_query' => WC()->query->get_tax_query(),  
  321. ); 
  322.  
  323. if ( ! empty( $atts['skus'] ) ) { 
  324. $query_args['meta_query'][] = array( 
  325. 'key' => '_sku',  
  326. 'value' => array_map( 'trim', explode( ', ', $atts['skus'] ) ),  
  327. 'compare' => 'IN',  
  328. ); 
  329.  
  330. if ( ! empty( $atts['ids'] ) ) { 
  331. $query_args['post__in'] = array_map( 'trim', explode( ', ', $atts['ids'] ) ); 
  332.  
  333. return self::product_loop( $query_args, $atts, 'products' ); 
  334.  
  335. /** 
  336. * Display a single product. 
  337. * 
  338. * @param array $atts 
  339. * @return string 
  340. */ 
  341. public static function product( $atts ) { 
  342. if ( empty( $atts ) ) { 
  343. return ''; 
  344.  
  345. $meta_query = WC()->query->get_meta_query(); 
  346.  
  347. $args = array( 
  348. 'post_type' => 'product',  
  349. 'posts_per_page' => 1,  
  350. 'no_found_rows' => 1,  
  351. 'post_status' => 'publish',  
  352. 'meta_query' => $meta_query,  
  353. 'tax_query' => WC()->query->get_tax_query(),  
  354. ); 
  355.  
  356. if ( isset( $atts['sku'] ) ) { 
  357. $args['meta_query'][] = array( 
  358. 'key' => '_sku',  
  359. 'value' => $atts['sku'],  
  360. 'compare' => '=',  
  361. ); 
  362.  
  363. if ( isset( $atts['id'] ) ) { 
  364. $args['p'] = $atts['id']; 
  365.  
  366. ob_start(); 
  367.  
  368. $products = new WP_Query( apply_filters( 'woocommerce_shortcode_products_query', $args, $atts ) ); 
  369.  
  370. if ( $products->have_posts() ) : ?> 
  371.  
  372. <?php woocommerce_product_loop_start(); ?> 
  373.  
  374. <?php while ( $products->have_posts() ) : $products->the_post(); ?> 
  375.  
  376. <?php wc_get_template_part( 'content', 'product' ); ?> 
  377.  
  378. <?php endwhile; // end of the loop. ?> 
  379.  
  380. <?php woocommerce_product_loop_end(); ?> 
  381.  
  382. <?php endif; 
  383.  
  384. wp_reset_postdata(); 
  385.  
  386. $css_class = 'woocommerce'; 
  387.  
  388. if ( isset( $atts['class'] ) ) { 
  389. $css_class .= ' ' . $atts['class']; 
  390.  
  391. return '<div class="' . esc_attr( $css_class ) . '">' . ob_get_clean() . '</div>'; 
  392.  
  393. /** 
  394. * Display a single product price + cart button. 
  395. * 
  396. * @param array $atts 
  397. * @return string 
  398. */ 
  399. public static function product_add_to_cart( $atts ) { 
  400. global $wpdb, $post; 
  401.  
  402. if ( empty( $atts ) ) { 
  403. return ''; 
  404.  
  405. $atts = shortcode_atts( array( 
  406. 'id' => '',  
  407. 'class' => '',  
  408. 'quantity' => '1',  
  409. 'sku' => '',  
  410. 'style' => 'border:4px solid #ccc; padding: 12px;',  
  411. 'show_price' => 'true',  
  412. ), $atts, 'product_add_to_cart' ); 
  413.  
  414. if ( ! empty( $atts['id'] ) ) { 
  415. $product_data = get_post( $atts['id'] ); 
  416. } elseif ( ! empty( $atts['sku'] ) ) { 
  417. $product_id = wc_get_product_id_by_sku( $atts['sku'] ); 
  418. $product_data = get_post( $product_id ); 
  419. } else { 
  420. return ''; 
  421.  
  422. $product = is_object( $product_data ) && in_array( $product_data->post_type, array( 'product', 'product_variation' ) ) ? wc_setup_product_data( $product_data ) : false; 
  423.  
  424. if ( ! $product ) { 
  425. return ''; 
  426.  
  427. $styles = empty( $atts['style'] ) ? '' : ' style="' . esc_attr( $atts['style'] ) . '"'; 
  428.  
  429. ob_start(); 
  430. ?> 
  431. <p class="product woocommerce add_to_cart_inline <?php echo esc_attr( $atts['class'] ); ?>"<?php echo $styles; ?>> 
  432.  
  433. <?php if ( 'true' == $atts['show_price'] ) : ?> 
  434. <?php echo $product->get_price_html(); ?> 
  435. <?php endif; ?> 
  436.  
  437. <?php woocommerce_template_loop_add_to_cart( array( 'quantity' => $atts['quantity'] ) ); ?> 
  438.  
  439. </p><?php 
  440.  
  441. // Restore Product global in case this is shown inside a product post 
  442. wc_setup_product_data( $post ); 
  443.  
  444. return ob_get_clean(); 
  445.  
  446. /** 
  447. * Get the add to cart URL for a product. 
  448. * 
  449. * @param array $atts 
  450. * @return string 
  451. */ 
  452. public static function product_add_to_cart_url( $atts ) { 
  453. global $wpdb; 
  454.  
  455. if ( empty( $atts ) ) { 
  456. return ''; 
  457.  
  458. if ( isset( $atts['id'] ) ) { 
  459. $product_data = get_post( $atts['id'] ); 
  460. } elseif ( isset( $atts['sku'] ) ) { 
  461. $product_id = wc_get_product_id_by_sku( $atts['sku'] ); 
  462. $product_data = get_post( $product_id ); 
  463. } else { 
  464. return ''; 
  465.  
  466. $product = is_object( $product_data ) && in_array( $product_data->post_type, array( 'product', 'product_variation' ) ) ? wc_setup_product_data( $product_data ) : false; 
  467.  
  468. if ( ! $product ) { 
  469. return ''; 
  470.  
  471. $_product = wc_get_product( $product_data ); 
  472.  
  473. return esc_url( $_product->add_to_cart_url() ); 
  474.  
  475. /** 
  476. * List all products on sale. 
  477. * 
  478. * @param array $atts 
  479. * @return string 
  480. */ 
  481. public static function sale_products( $atts ) { 
  482. $atts = shortcode_atts( array( 
  483. 'per_page' => '12',  
  484. 'columns' => '4',  
  485. 'orderby' => 'title',  
  486. 'order' => 'asc',  
  487. 'category' => '', // Slugs 
  488. 'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'. 
  489. ), $atts, 'sale_products' ); 
  490.  
  491. $query_args = array( 
  492. 'posts_per_page' => $atts['per_page'],  
  493. 'orderby' => $atts['orderby'],  
  494. 'order' => $atts['order'],  
  495. 'no_found_rows' => 1,  
  496. 'post_status' => 'publish',  
  497. 'post_type' => 'product',  
  498. 'meta_query' => WC()->query->get_meta_query(),  
  499. 'tax_query' => WC()->query->get_tax_query(),  
  500. 'post__in' => array_merge( array( 0 ), wc_get_product_ids_on_sale() ),  
  501. ); 
  502.  
  503. $query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] ); 
  504.  
  505. return self::product_loop( $query_args, $atts, 'sale_products' ); 
  506.  
  507. /** 
  508. * List best selling products on sale. 
  509. * 
  510. * @param array $atts 
  511. * @return string 
  512. */ 
  513. public static function best_selling_products( $atts ) { 
  514. $atts = shortcode_atts( array( 
  515. 'per_page' => '12',  
  516. 'columns' => '4',  
  517. 'category' => '', // Slugs 
  518. 'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'. 
  519. ), $atts, 'best_selling_products' ); 
  520.  
  521. $query_args = array( 
  522. 'post_type' => 'product',  
  523. 'post_status' => 'publish',  
  524. 'ignore_sticky_posts' => 1,  
  525. 'posts_per_page' => $atts['per_page'],  
  526. 'meta_key' => 'total_sales',  
  527. 'orderby' => 'meta_value_num',  
  528. 'meta_query' => WC()->query->get_meta_query(),  
  529. 'tax_query' => WC()->query->get_tax_query(),  
  530. ); 
  531.  
  532. $query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] ); 
  533.  
  534. return self::product_loop( $query_args, $atts, 'best_selling_products' ); 
  535.  
  536. /** 
  537. * List top rated products on sale. 
  538. * 
  539. * @param array $atts 
  540. * @return string 
  541. */ 
  542. public static function top_rated_products( $atts ) { 
  543. $atts = shortcode_atts( array( 
  544. 'per_page' => '12',  
  545. 'columns' => '4',  
  546. 'orderby' => 'title',  
  547. 'order' => 'asc',  
  548. 'category' => '', // Slugs 
  549. 'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'. 
  550. ), $atts, 'top_rated_products' ); 
  551.  
  552. $query_args = array( 
  553. 'post_type' => 'product',  
  554. 'post_status' => 'publish',  
  555. 'ignore_sticky_posts' => 1,  
  556. 'orderby' => $atts['orderby'],  
  557. 'order' => $atts['order'],  
  558. 'posts_per_page' => $atts['per_page'],  
  559. 'meta_query' => WC()->query->get_meta_query(),  
  560. 'tax_query' => WC()->query->get_tax_query(),  
  561. ); 
  562.  
  563. $query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] ); 
  564.  
  565. add_filter( 'posts_clauses', array( __CLASS__, 'order_by_rating_post_clauses' ) ); 
  566.  
  567. $return = self::product_loop( $query_args, $atts, 'top_rated_products' ); 
  568.  
  569. remove_filter( 'posts_clauses', array( __CLASS__, 'order_by_rating_post_clauses' ) ); 
  570.  
  571. return $return; 
  572.  
  573. /** 
  574. * Output featured products. 
  575. * 
  576. * @param array $atts 
  577. * @return string 
  578. */ 
  579. public static function featured_products( $atts ) { 
  580. $atts = shortcode_atts( array( 
  581. 'per_page' => '12',  
  582. 'columns' => '4',  
  583. 'orderby' => 'date',  
  584. 'order' => 'desc',  
  585. 'category' => '', // Slugs 
  586. 'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'. 
  587. ), $atts, 'featured_products' ); 
  588.  
  589. $meta_query = WC()->query->get_meta_query(); 
  590. $tax_query = WC()->query->get_tax_query(); 
  591. $tax_query[] = array( 
  592. 'taxonomy' => 'product_visibility',  
  593. 'field' => 'name',  
  594. 'terms' => 'featured',  
  595. 'operator' => 'IN',  
  596. ); 
  597.  
  598. $query_args = array( 
  599. 'post_type' => 'product',  
  600. 'post_status' => 'publish',  
  601. 'ignore_sticky_posts' => 1,  
  602. 'posts_per_page' => $atts['per_page'],  
  603. 'orderby' => $atts['orderby'],  
  604. 'order' => $atts['order'],  
  605. 'meta_query' => $meta_query,  
  606. 'tax_query' => $tax_query,  
  607. ); 
  608.  
  609. $query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] ); 
  610.  
  611. return self::product_loop( $query_args, $atts, 'featured_products' ); 
  612.  
  613. /** 
  614. * Show a single product page. 
  615. * 
  616. * @param array $atts 
  617. * @return string 
  618. */ 
  619. public static function product_page( $atts ) { 
  620. if ( empty( $atts ) ) { 
  621. return ''; 
  622.  
  623. if ( ! isset( $atts['id'] ) && ! isset( $atts['sku'] ) ) { 
  624. return ''; 
  625.  
  626. $args = array( 
  627. 'posts_per_page' => 1,  
  628. 'post_type' => 'product',  
  629. 'post_status' => 'publish',  
  630. 'ignore_sticky_posts' => 1,  
  631. 'no_found_rows' => 1,  
  632. ); 
  633.  
  634. if ( isset( $atts['sku'] ) ) { 
  635. $args['meta_query'][] = array( 
  636. 'key' => '_sku',  
  637. 'value' => sanitize_text_field( $atts['sku'] ),  
  638. 'compare' => '=',  
  639. ); 
  640.  
  641. $args['post_type'] = array( 'product', 'product_variation' ); 
  642.  
  643. if ( isset( $atts['id'] ) ) { 
  644. $args['p'] = absint( $atts['id'] ); 
  645.  
  646. $single_product = new WP_Query( $args ); 
  647.  
  648. $preselected_id = '0'; 
  649.  
  650. // check if sku is a variation 
  651. if ( isset( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) { 
  652.  
  653. $variation = new WC_Product_Variation( $single_product->post->ID ); 
  654. $attributes = $variation->get_attributes(); 
  655.  
  656. // set preselected id to be used by JS to provide context 
  657. $preselected_id = $single_product->post->ID; 
  658.  
  659. // get the parent product object 
  660. $args = array( 
  661. 'posts_per_page' => 1,  
  662. 'post_type' => 'product',  
  663. 'post_status' => 'publish',  
  664. 'ignore_sticky_posts' => 1,  
  665. 'no_found_rows' => 1,  
  666. 'p' => $single_product->post->post_parent,  
  667. ); 
  668.  
  669. $single_product = new WP_Query( $args ); 
  670. ?> 
  671. <script type="text/javascript"> 
  672. jQuery( document ).ready( function( $ ) { 
  673. var $variations_form = $( '[data-product-page-preselected-id="<?php echo esc_attr( $preselected_id ); ?>"]' ).find( 'form.variations_form' ); 
  674.  
  675. <?php foreach ( $attributes as $attr => $value ) { ?> 
  676. $variations_form.find( 'select[name="<?php echo esc_attr( $attr ); ?>"]' ).val( '<?php echo esc_js( $value ); ?>' ); 
  677. <?php } ?> 
  678. }); 
  679. </script> 
  680. <?php 
  681.  
  682. ob_start(); 
  683.  
  684. while ( $single_product->have_posts() ) : 
  685. $single_product->the_post(); 
  686. wp_enqueue_script( 'wc-single-product' ); 
  687. ?> 
  688.  
  689. <div class="single-product" data-product-page-preselected-id="<?php echo esc_attr( $preselected_id ); ?>"> 
  690.  
  691. <?php wc_get_template_part( 'content', 'single-product' ); ?> 
  692.  
  693. </div> 
  694.  
  695. <?php endwhile; // end of the loop. 
  696.  
  697. wp_reset_postdata(); 
  698.  
  699. return '<div class="woocommerce">' . ob_get_clean() . '</div>'; 
  700.  
  701. /** 
  702. * Show messages. 
  703. * 
  704. * @return string 
  705. */ 
  706. public static function shop_messages() { 
  707. ob_start(); 
  708. wc_print_notices(); 
  709. return '<div class="woocommerce">' . ob_get_clean() . '</div>'; 
  710.  
  711. /** 
  712. * woocommerce_order_by_rating_post_clauses function. 
  713. * 
  714. * @param array $args 
  715. * @return array 
  716. */ 
  717. public static function order_by_rating_post_clauses( $args ) { 
  718. global $wpdb; 
  719.  
  720. $args['where'] .= " AND $wpdb->commentmeta.meta_key = 'rating' "; 
  721. $args['join'] .= "LEFT JOIN $wpdb->comments ON($wpdb->posts.ID = $wpdb->comments.comment_post_ID) LEFT JOIN $wpdb->commentmeta ON($wpdb->comments.comment_ID = $wpdb->commentmeta.comment_id)"; 
  722. $args['orderby'] = "$wpdb->commentmeta.meta_value DESC"; 
  723. $args['groupby'] = "$wpdb->posts.ID"; 
  724.  
  725. return $args; 
  726.  
  727. /** 
  728. * List products with an attribute shortcode. 
  729. * Example [product_attribute attribute='color' filter='black']. 
  730. * 
  731. * @param array $atts 
  732. * @return string 
  733. */ 
  734. public static function product_attribute( $atts ) { 
  735. $atts = shortcode_atts( array( 
  736. 'per_page' => '12',  
  737. 'columns' => '4',  
  738. 'orderby' => 'title',  
  739. 'order' => 'asc',  
  740. 'attribute' => '',  
  741. 'filter' => '',  
  742. ), $atts, 'product_attribute' ); 
  743.  
  744. $query_args = array( 
  745. 'post_type' => 'product',  
  746. 'post_status' => 'publish',  
  747. 'ignore_sticky_posts' => 1,  
  748. 'posts_per_page' => $atts['per_page'],  
  749. 'orderby' => $atts['orderby'],  
  750. 'order' => $atts['order'],  
  751. 'meta_query' => WC()->query->get_meta_query(),  
  752. 'tax_query' => WC()->query->get_tax_query(),  
  753. ); 
  754.  
  755. $query_args['tax_query'][] = array( 
  756. 'taxonomy' => strstr( $atts['attribute'], 'pa_' ) ? sanitize_title( $atts['attribute'] ) : 'pa_' . sanitize_title( $atts['attribute'] ),  
  757. 'terms' => array_map( 'sanitize_title', explode( ', ', $atts['filter'] ) ),  
  758. 'field' => 'slug',  
  759. ); 
  760.  
  761. return self::product_loop( $query_args, $atts, 'product_attribute' ); 
  762.  
  763. /** 
  764. * List related products. 
  765. * @param array $atts 
  766. * @return string 
  767. */ 
  768. public static function related_products( $atts ) { 
  769. $atts = shortcode_atts( array( 
  770. 'per_page' => '4',  
  771. 'columns' => '4',  
  772. 'orderby' => 'rand',  
  773. ), $atts, 'related_products' ); 
  774.  
  775. ob_start(); 
  776.  
  777. // Rename arg 
  778. $atts['posts_per_page'] = absint( $atts['per_page'] ); 
  779.  
  780. woocommerce_related_products( $atts ); 
  781.  
  782. return ob_get_clean(); 
  783.  
  784. /** 
  785. * Adds a tax_query index to the query to filter by category. 
  786. * 
  787. * @param array $args 
  788. * @param string $category 
  789. * @param string $operator 
  790. * @return array; 
  791. * @access private 
  792. */ 
  793. private static function _maybe_add_category_args( $args, $category, $operator ) { 
  794. if ( ! empty( $category ) ) { 
  795. if ( empty( $args['tax_query'] ) ) { 
  796. $args['tax_query'] = array(); 
  797. $args['tax_query'][] = array( 
  798. array( 
  799. 'taxonomy' => 'product_cat',  
  800. 'terms' => array_map( 'sanitize_title', explode( ', ', $category ) ),  
  801. 'field' => 'slug',  
  802. 'operator' => $operator,  
  803. ),  
  804. ); 
  805.  
  806. return $args; 
.