PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/woocommerce/includes/wc-product-functions.php

https://bitbucket.org/theshipswakecreative/psw
PHP | 525 lines | 295 code | 80 blank | 150 comment | 42 complexity | c0bd063d874415e6c866ae85d69b2ca7 MD5 | raw file
Possible License(s): LGPL-3.0, Apache-2.0
  1. <?php
  2. /**
  3. * WooCommerce Product Functions
  4. *
  5. * Functions for product specific things.
  6. *
  7. * @author WooThemes
  8. * @category Core
  9. * @package WooCommerce/Functions
  10. * @version 2.1.0
  11. */
  12. /**
  13. * Main function for returning products, uses the WC_Product_Factory class.
  14. *
  15. * @param mixed $the_product Post object or post ID of the product.
  16. * @param array $args (default: array()) Contains all arguments to be used to get this product.
  17. * @return WC_Product
  18. */
  19. function wc_get_product( $the_product = false, $args = array() ) {
  20. return WC()->product_factory->get_product( $the_product, $args );
  21. }
  22. /**
  23. * Update a product's stock amount
  24. *
  25. * @param int $product_id
  26. * @param int $new_stock_level
  27. */
  28. function wc_update_product_stock( $product_id, $new_stock_level ) {
  29. $product = wc_get_product( $product_id );
  30. if ( ! metadata_exists( 'post', $product_id, '_stock' ) || $product->get_stock_quantity() !== $new_stock_level ) {
  31. $product->set_stock( $new_stock_level );
  32. }
  33. }
  34. /**
  35. * Update a product's stock status
  36. *
  37. * @param int $product_id
  38. * @param int $status
  39. */
  40. function wc_update_product_stock_status( $product_id, $status ) {
  41. $product = wc_get_product( $product_id );
  42. $product->set_stock_status( $status );
  43. }
  44. /**
  45. * Returns whether or not SKUS are enabled.
  46. * @return bool
  47. */
  48. function wc_product_sku_enabled() {
  49. return apply_filters( 'wc_product_sku_enabled', true );
  50. }
  51. /**
  52. * Returns whether or not product weights are enabled.
  53. * @return bool
  54. */
  55. function wc_product_weight_enabled() {
  56. return apply_filters( 'wc_product_weight_enabled', true );
  57. }
  58. /**
  59. * Returns whether or not product dimensions (HxWxD) are enabled.
  60. * @return bool
  61. */
  62. function wc_product_dimensions_enabled() {
  63. return apply_filters( 'wc_product_dimensions_enabled', true );
  64. }
  65. /**
  66. * Clear all transients cache for product data.
  67. *
  68. * @param int $post_id (default: 0)
  69. */
  70. function wc_delete_product_transients( $post_id = 0 ) {
  71. // Core transients
  72. $transients_to_clear = array(
  73. 'wc_products_onsale',
  74. 'wc_featured_products'
  75. );
  76. // Transients that include an ID
  77. $post_transient_names = array(
  78. 'wc_product_children_ids_',
  79. 'wc_product_total_stock_'
  80. );
  81. if ( $post_id > 0 ) {
  82. foreach( $post_transient_names as $transient ) {
  83. $transients_to_clear[] = $transient . $post_id;
  84. }
  85. }
  86. // Delete transients
  87. foreach( $transients_to_clear as $transient ) {
  88. delete_transient( $transient );
  89. }
  90. do_action( 'woocommerce_delete_product_transients', $post_id );
  91. }
  92. /**
  93. * Function that returns an array containing the IDs of the products that are on sale.
  94. *
  95. * @since 2.0
  96. * @access public
  97. * @return array
  98. */
  99. function wc_get_product_ids_on_sale() {
  100. global $wpdb;
  101. // Load from cache
  102. $product_ids_on_sale = get_transient( 'wc_products_onsale' );
  103. // Valid cache found
  104. if ( false !== $product_ids_on_sale )
  105. return $product_ids_on_sale;
  106. $on_sale_posts = $wpdb->get_results( "
  107. SELECT post.ID, post.post_parent FROM `$wpdb->posts` AS post
  108. LEFT JOIN `$wpdb->postmeta` AS meta ON post.ID = meta.post_id
  109. LEFT JOIN `$wpdb->postmeta` AS meta2 ON post.ID = meta2.post_id
  110. WHERE post.post_type IN ( 'product', 'product_variation' )
  111. AND post.post_status = 'publish'
  112. AND meta.meta_key = '_sale_price'
  113. AND meta2.meta_key = '_price'
  114. AND CAST( meta.meta_value AS DECIMAL ) >= 0
  115. AND CAST( meta.meta_value AS CHAR ) != ''
  116. AND CAST( meta.meta_value AS DECIMAL ) = CAST( meta2.meta_value AS DECIMAL )
  117. GROUP BY post.ID;
  118. " );
  119. $product_ids_on_sale = array_unique( array_map( 'absint', array_merge( wp_list_pluck( $on_sale_posts, 'ID' ), array_diff( wp_list_pluck( $on_sale_posts, 'post_parent' ), array( 0 ) ) ) ) );
  120. set_transient( 'wc_products_onsale', $product_ids_on_sale, YEAR_IN_SECONDS );
  121. return $product_ids_on_sale;
  122. }
  123. /**
  124. * Function that returns an array containing the IDs of the featured products.
  125. *
  126. * @since 2.1
  127. * @access public
  128. * @return array
  129. */
  130. function wc_get_featured_product_ids() {
  131. // Load from cache
  132. $featured_product_ids = get_transient( 'wc_featured_products' );
  133. // Valid cache found
  134. if ( false !== $featured_product_ids )
  135. return $featured_product_ids;
  136. $featured = get_posts( array(
  137. 'post_type' => array( 'product', 'product_variation' ),
  138. 'posts_per_page' => -1,
  139. 'post_status' => 'publish',
  140. 'meta_query' => array(
  141. array(
  142. 'key' => '_visibility',
  143. 'value' => array('catalog', 'visible'),
  144. 'compare' => 'IN'
  145. ),
  146. array(
  147. 'key' => '_featured',
  148. 'value' => 'yes'
  149. )
  150. ),
  151. 'fields' => 'id=>parent'
  152. ) );
  153. $product_ids = array_keys( $featured );
  154. $parent_ids = array_values( $featured );
  155. $featured_product_ids = array_unique( array_merge( $product_ids, $parent_ids ) );
  156. set_transient( 'wc_featured_products', $featured_product_ids, YEAR_IN_SECONDS );
  157. return $featured_product_ids;
  158. }
  159. /**
  160. * Filter to allow product_cat in the permalinks for products.
  161. *
  162. * @access public
  163. * @param string $permalink The existing permalink URL.
  164. * @param WP_Post $post
  165. * @return string
  166. */
  167. function wc_product_post_type_link( $permalink, $post ) {
  168. // Abort if post is not a product
  169. if ( $post->post_type !== 'product' )
  170. return $permalink;
  171. // Abort early if the placeholder rewrite tag isn't in the generated URL
  172. if ( false === strpos( $permalink, '%' ) )
  173. return $permalink;
  174. // Get the custom taxonomy terms in use by this post
  175. $terms = get_the_terms( $post->ID, 'product_cat' );
  176. if ( empty( $terms ) ) {
  177. // If no terms are assigned to this post, use a string instead (can't leave the placeholder there)
  178. $product_cat = _x( 'uncategorized', 'slug', 'woocommerce' );
  179. } else {
  180. // Replace the placeholder rewrite tag with the first term's slug
  181. $first_term = array_shift( $terms );
  182. $product_cat = $first_term->slug;
  183. }
  184. $find = array(
  185. '%year%',
  186. '%monthnum%',
  187. '%day%',
  188. '%hour%',
  189. '%minute%',
  190. '%second%',
  191. '%post_id%',
  192. '%category%',
  193. '%product_cat%'
  194. );
  195. $replace = array(
  196. date_i18n( 'Y', strtotime( $post->post_date ) ),
  197. date_i18n( 'm', strtotime( $post->post_date ) ),
  198. date_i18n( 'd', strtotime( $post->post_date ) ),
  199. date_i18n( 'H', strtotime( $post->post_date ) ),
  200. date_i18n( 'i', strtotime( $post->post_date ) ),
  201. date_i18n( 's', strtotime( $post->post_date ) ),
  202. $post->ID,
  203. $product_cat,
  204. $product_cat
  205. );
  206. $replace = array_map( 'sanitize_title', $replace );
  207. $permalink = str_replace( $find, $replace, $permalink );
  208. return $permalink;
  209. }
  210. add_filter( 'post_type_link', 'wc_product_post_type_link', 10, 2 );
  211. /**
  212. * Get the placeholder image URL for products etc
  213. *
  214. * @access public
  215. * @return string
  216. */
  217. function wc_placeholder_img_src() {
  218. return apply_filters( 'woocommerce_placeholder_img_src', WC()->plugin_url() . '/assets/images/placeholder.png' );
  219. }
  220. /**
  221. * Get the placeholder image
  222. *
  223. * @access public
  224. * @return string
  225. */
  226. function wc_placeholder_img( $size = 'shop_thumbnail' ) {
  227. $dimensions = wc_get_image_size( $size );
  228. return apply_filters('woocommerce_placeholder_img', '<img src="' . wc_placeholder_img_src() . '" alt="' . __( 'Placeholder', 'woocommerce' ) . '" width="' . esc_attr( $dimensions['width'] ) . '" class="woocommerce-placeholder wp-post-image" height="' . esc_attr( $dimensions['height'] ) . '" />', $size, $dimensions );
  229. }
  230. /**
  231. * Variation Formatting
  232. *
  233. * Gets a formatted version of variation data or item meta
  234. *
  235. * @access public
  236. * @param string $variation
  237. * @param bool $flat (default: false)
  238. * @return string
  239. */
  240. function wc_get_formatted_variation( $variation, $flat = false ) {
  241. $return = '';
  242. if ( is_array( $variation ) ) {
  243. if ( ! $flat ) {
  244. $return = '<dl class="variation">';
  245. }
  246. $variation_list = array();
  247. foreach ( $variation as $name => $value ) {
  248. if ( ! $value ) {
  249. continue;
  250. }
  251. // If this is a term slug, get the term's nice name
  252. if ( taxonomy_exists( esc_attr( str_replace( 'attribute_', '', $name ) ) ) ) {
  253. $term = get_term_by( 'slug', $value, esc_attr( str_replace( 'attribute_', '', $name ) ) );
  254. if ( ! is_wp_error( $term ) && $term->name )
  255. $value = $term->name;
  256. } else {
  257. $value = ucwords( str_replace( '-', ' ', $value ) );
  258. }
  259. if ( $flat ) {
  260. $variation_list[] = wc_attribute_label( str_replace( 'attribute_', '', $name ) ) . ': ' . urldecode( $value );
  261. } else {
  262. $variation_list[] = '<dt>' . wc_attribute_label( str_replace( 'attribute_', '', $name ) ) . ':</dt><dd>' . urldecode( $value ) . '</dd>';
  263. }
  264. }
  265. if ( $flat ) {
  266. $return .= implode( ', ', $variation_list );
  267. } else {
  268. $return .= implode( '', $variation_list );
  269. }
  270. if ( ! $flat ) {
  271. $return .= '</dl>';
  272. }
  273. }
  274. return $return;
  275. }
  276. /**
  277. * Function which handles the start and end of scheduled sales via cron.
  278. *
  279. * @access public
  280. * @return void
  281. */
  282. function wc_scheduled_sales() {
  283. global $wpdb;
  284. // Sales which are due to start
  285. $product_ids = $wpdb->get_col( $wpdb->prepare( "
  286. SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
  287. LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
  288. LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
  289. WHERE postmeta.meta_key = '_sale_price_dates_from'
  290. AND postmeta_2.meta_key = '_price'
  291. AND postmeta_3.meta_key = '_sale_price'
  292. AND postmeta.meta_value > 0
  293. AND postmeta.meta_value < %s
  294. AND postmeta_2.meta_value != postmeta_3.meta_value
  295. ", current_time( 'timestamp' ) ) );
  296. if ( $product_ids ) {
  297. foreach ( $product_ids as $product_id ) {
  298. $sale_price = get_post_meta( $product_id, '_sale_price', true );
  299. if ( $sale_price ) {
  300. update_post_meta( $product_id, '_price', $sale_price );
  301. } else {
  302. // No sale price!
  303. update_post_meta( $product_id, '_sale_price_dates_from', '' );
  304. update_post_meta( $product_id, '_sale_price_dates_to', '' );
  305. }
  306. $parent = wp_get_post_parent_id( $product_id );
  307. // Sync parent
  308. if ( $parent ) {
  309. // We can force variable product prices to sync up by removing their min price meta
  310. delete_post_meta( $parent, '_min_price_variation_id' );
  311. // Grouped products need syncing via a function
  312. $this_product = wc_get_product( $product_id );
  313. if ( $this_product->is_type( 'simple' ) ) {
  314. $this_product->grouped_product_sync();
  315. }
  316. }
  317. }
  318. delete_transient( 'wc_products_onsale' );
  319. }
  320. // Sales which are due to end
  321. $product_ids = $wpdb->get_col( $wpdb->prepare( "
  322. SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
  323. LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
  324. LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
  325. WHERE postmeta.meta_key = '_sale_price_dates_to'
  326. AND postmeta_2.meta_key = '_price'
  327. AND postmeta_3.meta_key = '_regular_price'
  328. AND postmeta.meta_value > 0
  329. AND postmeta.meta_value < %s
  330. AND postmeta_2.meta_value != postmeta_3.meta_value
  331. ", current_time( 'timestamp' ) ) );
  332. if ( $product_ids ) {
  333. foreach ( $product_ids as $product_id ) {
  334. $regular_price = get_post_meta( $product_id, '_regular_price', true );
  335. update_post_meta( $product_id, '_price', $regular_price );
  336. update_post_meta( $product_id, '_sale_price', '' );
  337. update_post_meta( $product_id, '_sale_price_dates_from', '' );
  338. update_post_meta( $product_id, '_sale_price_dates_to', '' );
  339. $parent = wp_get_post_parent_id( $product_id );
  340. // Sync parent
  341. if ( $parent ) {
  342. // We can force variable product price to sync up by removing their min price meta
  343. delete_post_meta( $parent, '_min_variation_price' );
  344. // Grouped products need syncing via a function
  345. $this_product = wc_get_product( $product_id );
  346. if ( $this_product->is_type( 'simple' ) ) {
  347. $this_product->grouped_product_sync();
  348. }
  349. }
  350. }
  351. delete_transient( 'wc_products_onsale' );
  352. }
  353. }
  354. add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' );
  355. /**
  356. * wc_get_attachment_image_attributes function.
  357. *
  358. * @access public
  359. * @param array $attr
  360. * @return array
  361. */
  362. function wc_get_attachment_image_attributes( $attr ) {
  363. if ( strstr( $attr['src'], 'woocommerce_uploads/' ) ) {
  364. $attr['src'] = wc_placeholder_img_src();
  365. }
  366. return $attr;
  367. }
  368. add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' );
  369. /**
  370. * wc_prepare_attachment_for_js function.
  371. *
  372. * @access public
  373. * @param array $response
  374. * @return array
  375. */
  376. function wc_prepare_attachment_for_js( $response ) {
  377. if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) {
  378. $response['full']['url'] = wc_placeholder_img_src();
  379. if ( isset( $response['sizes'] ) ) {
  380. foreach( $response['sizes'] as $size => $value ) {
  381. $response['sizes'][ $size ]['url'] = wc_placeholder_img_src();
  382. }
  383. }
  384. }
  385. return $response;
  386. }
  387. add_filter( 'wp_prepare_attachment_for_js', 'wc_prepare_attachment_for_js' );
  388. /**
  389. * Track product views
  390. */
  391. function wc_track_product_view() {
  392. if ( ! is_singular( 'product' ) )
  393. return;
  394. global $post;
  395. if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) )
  396. $viewed_products = array();
  397. else
  398. $viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] );
  399. if ( ! in_array( $post->ID, $viewed_products ) )
  400. $viewed_products[] = $post->ID;
  401. if ( sizeof( $viewed_products ) > 15 )
  402. array_shift( $viewed_products );
  403. // Store for session only
  404. wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
  405. }
  406. add_action( 'template_redirect', 'wc_track_product_view', 20 );
  407. /**
  408. * Get product types
  409. *
  410. * @since 2.2
  411. * @return array
  412. */
  413. function wc_get_product_types() {
  414. return (array) apply_filters( 'product_type_selector', array(
  415. 'simple' => __( 'Simple product', 'woocommerce' ),
  416. 'grouped' => __( 'Grouped product', 'woocommerce' ),
  417. 'external' => __( 'External/Affiliate product', 'woocommerce' ),
  418. 'variable' => __( 'Variable product', 'woocommerce' )
  419. ) );
  420. }
  421. /**
  422. * Check if product sku is unique.
  423. *
  424. * @since 2.2
  425. * @param int $product_id
  426. * @param string $sku
  427. * @return bool
  428. */
  429. function wc_product_has_unique_sku( $product_id, $sku ) {
  430. global $wpdb;
  431. $sku_found = $wpdb->get_var( $wpdb->prepare( "
  432. SELECT $wpdb->posts.ID
  433. FROM $wpdb->posts
  434. LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )
  435. WHERE $wpdb->posts.post_type IN ( 'product', 'product_variation' )
  436. AND $wpdb->posts.post_status = 'publish'
  437. AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s'
  438. AND $wpdb->postmeta.post_id <> %d LIMIT 1
  439. ", $sku, $product_id ) );
  440. if ( $sku_found ) {
  441. return false;
  442. } else {
  443. return true;
  444. }
  445. }