Source: model/woocommerce.php

  1. <?php
  2. namespace wpbuddy\rich_snippets;
  3. if ( ! defined( 'ABSPATH' ) ) {
  4. exit;
  5. } // Exit if accessed directly
  6. /**
  7. * Class WooCommerce_Model.
  8. *
  9. * Recognizes the WooCommerce plugin and provides new fields.
  10. *
  11. * @package wpbuddy\rich_snippets
  12. *
  13. * @since 2.2.0
  14. */
  15. final class WooCommerce_Model {
  16. /**
  17. * @param $values
  18. *
  19. * @return mixed
  20. */
  21. public static function internal_subselect( $values ) {
  22. if ( ! function_exists( 'WC' ) ) {
  23. return $values;
  24. }
  25. $number_values = [];
  26. $values['http://schema.org/AggregateRating'][] = array(
  27. 'id' => 'woocommerce_rating',
  28. 'label' => esc_html_x( 'WooCommerce: Product Rating', 'subselect field', 'rich-snippets-schema' ),
  29. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'rating' ),
  30. );
  31. $values['http://schema.org/Rating'][] = array(
  32. 'id' => 'woocommerce_rating',
  33. 'label' => esc_html_x( 'WooCommerce: Product Rating', 'subselect field', 'rich-snippets-schema' ),
  34. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'rating' ),
  35. );
  36. $values['http://schema.org/AggregateRating'][] = array(
  37. 'id' => 'woocommerce_review_rating',
  38. 'label' => esc_html_x( 'WooCommerce: Product Review Rating', 'subselect field', 'rich-snippets-schema' ),
  39. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'review_rating' ),
  40. );
  41. $values['http://schema.org/Rating'][] = array(
  42. 'id' => 'woocommerce_review_rating',
  43. 'label' => esc_html_x( 'WooCommerce: Product Review Rating', 'subselect field', 'rich-snippets-schema' ),
  44. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'review_rating' ),
  45. );
  46. $values['http://schema.org/Text'][] = array(
  47. 'id' => 'woocommerce_sku',
  48. 'label' => esc_html_x( 'WooCommerce: Stock Keeping Unit', 'subselect field', 'rich-snippets-schema' ),
  49. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'sku' ),
  50. );
  51. $values['http://schema.org/Text'][] = array(
  52. 'id' => 'woocommerce_currency_code',
  53. 'label' => esc_html_x( 'WooCommerce: Currency Code', 'subselect field', 'rich-snippets-schema' ),
  54. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'currency_code' ),
  55. );
  56. $values['http://schema.org/Offer'][] = array(
  57. 'id' => 'woocommerce_offers',
  58. 'label' => esc_html_x( 'WooCommerce: Offers', 'subselect field', 'rich-snippets-schema' ),
  59. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'offers' ),
  60. );
  61. $number_values[] = array(
  62. 'id' => 'woocommerce_height',
  63. 'label' => esc_html_x( 'WooCommerce: Product Height', 'subselect field', 'rich-snippets-schema' ),
  64. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'height' ),
  65. );
  66. $number_values[] = array(
  67. 'id' => 'woocommerce_width',
  68. 'label' => esc_html_x( 'WooCommerce: Product Width', 'subselect field', 'rich-snippets-schema' ),
  69. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'width' ),
  70. );
  71. $number_values[] = array(
  72. 'id' => 'woocommerce_weight',
  73. 'label' => esc_html_x( 'WooCommerce: Product Weight', 'subselect field', 'rich-snippets-schema' ),
  74. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'weight' ),
  75. );
  76. $number_values[] = array(
  77. 'id' => 'textfield_woocommerce_product_attribute',
  78. 'label' => esc_html_x( 'WooCommerce: Product attribute', 'subselect field', 'rich-snippets-schema' ),
  79. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'attribute' ),
  80. );
  81. $number_values[] = $values['http://schema.org/URL'][] = array(
  82. 'id' => 'textfield_woocommerce_product_attribute',
  83. 'label' => esc_html_x( 'WooCommerce: Product attribute', 'subselect field', 'rich-snippets-schema' ),
  84. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'attribute' ),
  85. );
  86. $values['http://schema.org/Review'][] = array(
  87. 'id' => 'woocommerce_reviews',
  88. 'label' => esc_html_x( 'WooCommerce: Product reviews', 'subselect field', 'rich-snippets-schema' ),
  89. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'reviews' ),
  90. );
  91. $values['http://schema.org/Number'][] = array(
  92. 'id' => 'woocommerce_price',
  93. 'label' => esc_html_x( 'WooCommerce: Product Price', 'subselect field', 'rich-snippets-schema' ),
  94. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'price' ),
  95. );
  96. $values['http://schema.org/Number'][] = array(
  97. 'id' => 'woocommerce_sales_price',
  98. 'label' => esc_html_x( 'WooCommerce: Product Sales Price', 'subselect field', 'rich-snippets-schema' ),
  99. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'sales_price' ),
  100. );
  101. $values['http://schema.org/ItemAvailability'][] = array(
  102. 'id' => 'woocommerce_sales_price',
  103. 'label' => esc_html_x( 'WooCommerce: Availability', 'subselect field', 'rich-snippets-schema' ),
  104. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'availability' ),
  105. );
  106. $values['http://schema.org/Date'][] = array(
  107. 'id' => 'woocommerce_sales_end_date',
  108. 'label' => esc_html_x( 'WooCommerce: Sales End Date', 'subselect field', 'rich-snippets-schema' ),
  109. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'sales_end_date' ),
  110. );
  111. $values['http://schema.org/Date'][] = array(
  112. 'id' => 'woocommerce_sales_start_date',
  113. 'label' => esc_html_x( 'WooCommerce: Sales Start Date', 'subselect field', 'rich-snippets-schema' ),
  114. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'sales_start_date' ),
  115. );
  116. $values['http://schema.org/DateTime'][] = array(
  117. 'id' => 'woocommerce_sales_end_datetime',
  118. 'label' => esc_html_x( 'WooCommerce: Sales End Date and Time', 'subselect field', 'rich-snippets-schema' ),
  119. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'sales_end_datetime' ),
  120. );
  121. $values['http://schema.org/DateTime'][] = array(
  122. 'id' => 'woocommerce_sales_start_date',
  123. 'label' => esc_html_x( 'WooCommerce: Sales Start Date and Time', 'subselect field', 'rich-snippets-schema' ),
  124. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'sales_start_datetime' ),
  125. );
  126. $number_values[] = array(
  127. 'id' => 'woocommerce_stock_number',
  128. 'label' => esc_html_x( 'WooCommerce: Stock Number', 'subselect field', 'rich-snippets-schema' ),
  129. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'stock_number' ),
  130. );
  131. $values['http://schema.org/Text'][] = array(
  132. 'id' => 'woocommerce_weight_unit',
  133. 'label' => esc_html_x( 'WooCommerce: Weight Unit', 'subselect field', 'rich-snippets-schema' ),
  134. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'weight_unit' ),
  135. );
  136. $values['http://schema.org/Text'][] = $values['http://schema.org/Thing'][] = array(
  137. 'id' => 'textfield_woocommerce_product_attribute',
  138. 'label' => esc_html_x( 'WooCommerce: Product attribute', 'subselect field', 'rich-snippets-schema' ),
  139. 'method' => array( 'wpbuddy\rich_snippets\WooCommerce_Model', 'attribute' ),
  140. );
  141. $values['http://schema.org/QuantitativeValue'] = array_merge( $values['http://schema.org/QuantitativeValue'], $number_values );
  142. $values['http://schema.org/Number'] = array_merge( $values['http://schema.org/Number'], $number_values );
  143. $values['http://schema.org/Integer'] = array_merge( $values['http://schema.org/Integer'], $number_values );
  144. return $values;
  145. }
  146. /**
  147. * Returns the value of the current rating.
  148. *
  149. * @param $val
  150. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  151. * @param array $meta_info
  152. *
  153. * @return string
  154. * @since 2.2.0
  155. *
  156. */
  157. public static function rating( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  158. $product = wc_get_product( $meta_info['current_post_id'] );
  159. if ( ! $product instanceof \WC_Product ) {
  160. $rating_value = 0;
  161. $rating_count = 0;
  162. } else {
  163. $rating_value = floatval( $product->get_average_rating( 'raw' ) );
  164. $rating_count = floatval( $product->get_rating_count( 'raw' ) );
  165. }
  166. # force SNIP to not include aggregateRating at all because there are no ratings
  167. # This is because rating_count cannot be zero for Googles Structured Data Tester
  168. if ( $rating_count <= 0 ) {
  169. return '';
  170. }
  171. $rating_snippet = new Rich_Snippet();
  172. $rating_snippet->type = 'AggregateRating';
  173. $rating_snippet->set_props( array(
  174. array(
  175. 'name' => 'ratingCount',
  176. 'value' => $rating_count,
  177. ),
  178. array(
  179. 'name' => 'bestRating',
  180. 'value' => 5,
  181. ),
  182. array(
  183. 'name' => 'ratingValue',
  184. 'value' => $rating_value,
  185. ),
  186. array(
  187. 'name' => 'worstRating',
  188. 'value' => $rating_count <= 0 ? 0 : 1, # worstRating must be 0 if ratingCount is 0
  189. ),
  190. ) );
  191. $rating_snippet->prepare_for_output();
  192. return $rating_snippet;
  193. }
  194. /**
  195. * Returns the value of the current review rating.
  196. *
  197. * @param $val
  198. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  199. * @param array $meta_info
  200. *
  201. * @return string
  202. * @since 2.6.0
  203. *
  204. */
  205. public static function review_rating( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  206. $product = wc_get_product( $meta_info['current_post_id'] );
  207. if ( ! $product instanceof \WC_Product ) {
  208. $rating_value = 0;
  209. $review_count = 0;
  210. } else {
  211. $rating_value = floatval( $product->get_average_rating( 'raw' ) );
  212. $review_count = floatval( $product->get_review_count( 'raw' ) );
  213. }
  214. # force SNIP to not include aggregateRating at all because there are no ratings
  215. # This is because review_count cannot be zero for Googles Structured Data Tester
  216. if ( $review_count <= 0 ) {
  217. return '';
  218. }
  219. $rating_snippet = new Rich_Snippet();
  220. $rating_snippet->type = 'AggregateRating';
  221. $rating_snippet->set_props( array(
  222. array(
  223. 'name' => 'reviewCount',
  224. 'value' => $review_count,
  225. ),
  226. array(
  227. 'name' => 'bestRating',
  228. 'value' => 5,
  229. ),
  230. array(
  231. 'name' => 'ratingValue',
  232. 'value' => $rating_value,
  233. ),
  234. array(
  235. 'name' => 'worstRating',
  236. 'value' => $review_count <= 0 ? 0 : 1, # worstRating must be 0 if ratingCount is 0
  237. ),
  238. ) );
  239. $rating_snippet->prepare_for_output();
  240. return $rating_snippet;
  241. }
  242. /**
  243. * Returns the value of the current SKU.
  244. *
  245. * @param $val
  246. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  247. * @param array $meta_info
  248. *
  249. * @return string
  250. * @since 2.2.0
  251. *
  252. */
  253. public static function sku( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  254. $product = wc_get_product( $meta_info['current_post_id'] );
  255. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  256. return (string) $product->get_sku( 'raw' );
  257. }
  258. return '';
  259. }
  260. /**
  261. * Returns the weight unit.
  262. *
  263. * @param $val
  264. * @param Rich_Snippet $rich_snippet
  265. * @param array $meta_info
  266. *
  267. * @return string
  268. * @since 2.11.0
  269. *
  270. */
  271. public static function weight_unit( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  272. return (string) get_option( 'woocommerce_weight_unit' );
  273. }
  274. /**
  275. * Returns the products end sales date.
  276. *
  277. * @param $val
  278. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  279. * @param array $meta_info
  280. *
  281. * @return string
  282. * @since 2.11.0
  283. *
  284. */
  285. public static function sales_end_date( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  286. $product = wc_get_product( $meta_info['current_post_id'] );
  287. $not_on_sale_time = (string) date_i18n( 'Y-m-d', strtotime( 'NOW + 1 year' ) );
  288. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  289. if ( $product->is_on_sale() && $product->get_date_on_sale_to() ) {
  290. return date_i18n( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() );
  291. }
  292. }
  293. return $not_on_sale_time;
  294. }
  295. /**
  296. * Returns the products end sales datetime.
  297. *
  298. * @param $val
  299. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  300. * @param array $meta_info
  301. *
  302. * @return string
  303. * @since 2.11.0
  304. *
  305. */
  306. public static function sales_end_datetime( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  307. return date_i18n( 'c', strtotime( self::sales_end_date( $val, $rich_snippet, $meta_info ) ) );
  308. }
  309. /**
  310. * Returns the products start sales date.
  311. *
  312. * @param $val
  313. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  314. * @param array $meta_info
  315. *
  316. * @return string
  317. * @since 2.11.0
  318. *
  319. */
  320. public static function sales_start_date( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  321. $product = wc_get_product( $meta_info['current_post_id'] );
  322. $not_on_sale_time = (string) date_i18n( 'Y-m-d', strtotime( 'NOW - 1 DAY' ) );
  323. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  324. if ( $product->is_on_sale() && $product->get_date_on_sale_from() ) {
  325. return date_i18n( 'Y-m-d', $product->get_date_on_sale_from()->getTimestamp() );
  326. }
  327. }
  328. return $not_on_sale_time;
  329. }
  330. /**
  331. * Returns the products start sales date and time.
  332. *
  333. * @param $val
  334. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  335. * @param array $meta_info
  336. *
  337. * @return string
  338. * @since 2.11.0
  339. *
  340. */
  341. public static function sales_start_datetime( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  342. return date_i18n( 'c', strtotime( self::sales_start_date( $val, $rich_snippet, $meta_info ) ) );
  343. }
  344. /**
  345. * Returns the value of the current product price
  346. *
  347. * @param $val
  348. * @param Rich_Snippet $rich_snippet
  349. * @param array $meta_info
  350. *
  351. * @return float|string
  352. * @since 2.11.0
  353. *
  354. */
  355. public static function price( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  356. $product = wc_get_product( $meta_info['current_post_id'] );
  357. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  358. return number_format( floatval( $product->get_price( 'edit' ) ), 2, '.', '' );
  359. }
  360. return '';
  361. }
  362. /**
  363. * Returns the value of the current products availability.
  364. *
  365. * @param $val
  366. * @param Rich_Snippet $rich_snippet
  367. * @param array $meta_info
  368. *
  369. * @return float|string
  370. * @since 2.11.0
  371. */
  372. public static function availability( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  373. return self::get_availability( intval( $meta_info['current_post_id'] ) );
  374. }
  375. /**
  376. * @param int $product_id
  377. *
  378. * @return string
  379. * @since 2.15.2
  380. */
  381. public static function get_availability( int $product_id ): string {
  382. $product = wc_get_product( $product_id );
  383. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  384. switch ( $product->get_stock_status() ) {
  385. case 'onbackorder':
  386. return 'https://schema.org/PreOrder';
  387. case 'instock':
  388. $stock_quantity = $product->get_stock_quantity( 'edit' );
  389. if ( function_exists( 'wc_get_low_stock_amount' ) ) {
  390. $low_stock_amount = intval( wc_get_low_stock_amount( $product ) );
  391. if ( $stock_quantity <= $low_stock_amount ) {
  392. if ( (bool) get_option( 'wpb_rs/setting/wc_availability_use_preorder', false ) ) {
  393. return 'https://schema.org/PreOrder';
  394. } else {
  395. return 'https://schema.org/LimitedAvailability';
  396. }
  397. }
  398. }
  399. return 'http://schema.org/InStock';
  400. case 'outofstock':
  401. return 'http://schema.org/OutOfStock';
  402. }
  403. }
  404. return '';
  405. }
  406. /**
  407. * Returns the value of the current product sales price.
  408. *
  409. * @param $val
  410. * @param Rich_Snippet $rich_snippet
  411. * @param array $meta_info
  412. *
  413. * @return float|string
  414. * @since 2.11.0
  415. *
  416. */
  417. public static function sales_price( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  418. $product = wc_get_product( $meta_info['current_post_id'] );
  419. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  420. return number_format( floatval( $product->get_sale_price( 'edit' ) ), 2, '.', '' );
  421. }
  422. return '';
  423. }
  424. /**
  425. * Returns the value of the stock number.
  426. *
  427. * @param $val
  428. * @param Rich_Snippet $rich_snippet
  429. * @param array $meta_info
  430. *
  431. * @return \stdClass
  432. * @since 2.11.0
  433. *
  434. */
  435. public static function stock_number( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  436. $product = wc_get_product( $meta_info['current_post_id'] );
  437. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  438. $stock = $product->get_manage_stock( 'edit' );
  439. $obj = new \stdClass();
  440. $obj->{'@context'} = 'http://schema.org';
  441. $obj->{'@type'} = 'Offer';
  442. $obj->value = $stock;
  443. return $obj;
  444. }
  445. return new \stdClass();
  446. }
  447. /**
  448. * Returns the currency code.
  449. *
  450. * @param $val
  451. * @param Rich_Snippet $rich_snippet
  452. * @param array $meta_info
  453. *
  454. * @return float|string
  455. * @since 2.11.0
  456. *
  457. */
  458. public static function currency_code( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  459. return get_woocommerce_currency();
  460. }
  461. /**
  462. * Returns the offer for a WooCommerce product.
  463. *
  464. * @param int $post_id
  465. *
  466. * @return \stdClass
  467. * @since 2.2.0
  468. *
  469. */
  470. private static function offer( $post_id ) {
  471. /**
  472. * @var \WC_Product_Variation
  473. */
  474. $product = wc_get_product( $post_id );
  475. if ( ! ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) ) {
  476. return new \stdClass();
  477. }
  478. $obj = new \stdClass();
  479. $obj->{'@context'} = 'http://schema.org';
  480. $obj->{'@type'} = 'Offer';
  481. $obj->availability = self::get_availability( intval( $post_id ) );
  482. $obj->priceCurrency = get_woocommerce_currency();
  483. $obj->price = wc_format_decimal( $product->get_price(), wc_get_price_decimals() );
  484. $obj->url = $product->get_permalink();
  485. $sale_date = $product->get_date_on_sale_to( 'raw' );
  486. if ( ! $sale_date instanceof \DateTime ) {
  487. # If there is no sales date create a fake date to avoid Googles warnings
  488. # @see https://rich-snippets.io/offers-pricevaliduntil-recommended/
  489. $obj->priceValidUntil = (string) date_i18n( 'c', strtotime( 'NOW + 1 year' ) );
  490. } else {
  491. $obj->priceValidUntil = $sale_date->date( 'c' );
  492. }
  493. return $obj;
  494. }
  495. /**
  496. * Returns a snippet of all offers.
  497. *
  498. * @param $val
  499. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  500. * @param array $meta_info
  501. *
  502. * @return \stdClass
  503. *
  504. * @since 2.2.0
  505. */
  506. public static function offers( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  507. $product = wc_get_product( $meta_info['current_post_id'] );
  508. if ( $product instanceof \WC_Product_Variable ) {
  509. $lowest = $product->get_variation_price( 'min', false );
  510. $highest = $product->get_variation_price( 'max', false );
  511. if ( $lowest === $highest ) {
  512. return self::offer( $meta_info['current_post_id'] );
  513. } else {
  514. $obj = new \stdClass();
  515. $obj->{'@context'} = 'http://schema.org';
  516. $obj->{'@type'} = 'AggregateOffer';
  517. $obj->lowPrice = wc_format_decimal( $lowest, wc_get_price_decimals() );
  518. $obj->highPrice = wc_format_decimal( $highest, wc_get_price_decimals() );
  519. $obj->priceCurrency = get_woocommerce_currency();
  520. $obj->offerCount = count( $product->get_children() );
  521. $obj->url = $product->get_permalink();
  522. return $obj;
  523. }
  524. } elseif ( $product instanceof \WC_Product ) {
  525. return self::offer( $meta_info['current_post_id'] );
  526. }
  527. return new \stdClass();
  528. }
  529. /**
  530. * Returns the height of a product.
  531. *
  532. * @param $val
  533. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  534. * @param array $meta_info
  535. *
  536. * @return \stdClass
  537. * @since 2.2.0
  538. *
  539. */
  540. public static function height( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  541. return self::get_product_quantitive_snippet( $meta_info['current_post_id'], 'height' );
  542. }
  543. /**
  544. * Returns the width of a product.
  545. *
  546. * @param $val
  547. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  548. * @param array $meta_info
  549. *
  550. * @return \stdClass
  551. * @since 2.2.0
  552. *
  553. */
  554. public static function width( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  555. return self::get_product_quantitive_snippet( $meta_info['current_post_id'], 'width' );
  556. }
  557. /**
  558. * Get WooCommerce product dimension as a snippet.
  559. *
  560. * @param int $product_id
  561. * @param string $prop width|height|weight
  562. *
  563. * since 2.2.0
  564. *
  565. * @return \stdClass
  566. */
  567. private static function get_product_quantitive_snippet( $product_id, $prop ) {
  568. $product = wc_get_product( $product_id );
  569. $item = new \stdClass();
  570. if ( ! is_subclass_of( $product, 'WC_Data', false ) ) {
  571. return $item;
  572. }
  573. $item->{'@context'} = 'http://schema.org';
  574. $item->{'@type'} = 'QuantitativeValue';
  575. $item->value = method_exists( $product, 'get_' . $prop ) ? $product->{'get_' . $prop}() : '';
  576. $item->value = floatval( $item->value );
  577. $item->unitCode = 'weight' === $prop ? get_option( 'woocommerce_weight_unit' ) : get_option( 'woocommerce_dimension_unit' );
  578. return $item;
  579. }
  580. /**
  581. * Returns the eight of a product.
  582. *
  583. * @param $val
  584. * @param \wpbuddy\rich_snippets\Rich_Snippet $rich_snippet
  585. * @param array $meta_info
  586. *
  587. * @return \stdClass
  588. * @since 2.2.0
  589. *
  590. */
  591. public static function weight( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  592. return self::get_product_quantitive_snippet( $meta_info['current_post_id'], 'weight' );
  593. }
  594. /**
  595. * Reads a product attribute from WooCommerce (serialized data).
  596. *
  597. * @param $val
  598. * @param Rich_Snippet $rich_snippet
  599. * @param array $meta_info
  600. *
  601. * @return string
  602. * @since 2.5.0
  603. *
  604. */
  605. public static function attribute( $val, Rich_Snippet $rich_snippet, array $meta_info ) {
  606. if ( ! function_exists( '\wc_get_product' ) ) {
  607. return '';
  608. }
  609. if ( ! is_scalar( $val ) ) {
  610. return '';
  611. }
  612. if ( empty( $val ) ) {
  613. return '';
  614. }
  615. $product = \wc_get_product( $meta_info['current_post_id'] );
  616. if ( $product instanceof \WC_Product || is_subclass_of( $product, 'WC_Product', false ) ) {
  617. return $product->get_attribute( $val );
  618. }
  619. return '';
  620. }
  621. /**
  622. * Outputs product reviews from WooCommerce.
  623. *
  624. * @param $val
  625. * @param Rich_Snippet $rich_snippet
  626. * @param array $meta_info
  627. *
  628. * @return \stdClass[]
  629. * @since 2.7.0
  630. *
  631. */
  632. public static function reviews( $val, Rich_Snippet $rich_snippet, array $meta_info ): array {
  633. if ( ! function_exists( '\wc_get_product' ) ) {
  634. return [];
  635. }
  636. /**
  637. * @var \WP_Comment[] $comments
  638. */
  639. $args = [
  640. 'post_id' => $meta_info['current_post_id'],
  641. 'include_unapproved' => false,
  642. 'number' => 5,
  643. 'type' => 'review',
  644. 'meta_query' => array(
  645. 'city_clause' => array(
  646. 'key' => 'rating',
  647. 'compare' => 'EXISTS',
  648. ),
  649. ),
  650. ];
  651. /**
  652. * Get comments filter for WooCommerce products.
  653. *
  654. * Allows to filter the comment arguments when WooCommerce product comments are loaded.
  655. *
  656. * @hook wpbuddy/rich_snippets/woocommerce/reviews/args
  657. *
  658. * @param {array} $args The arguments.
  659. * @returns {array} The modified arguments.
  660. *
  661. * @since 2.7.0
  662. */
  663. $comments = get_comments( apply_filters( 'wpbuddy/rich_snippets/woocommerce/reviews/args', $args ) );
  664. if ( ! is_array( $comments ) || count( $comments ) <= 0 ) {
  665. return [];
  666. }
  667. $reviews = [];
  668. foreach ( $comments as $comment ) {
  669. $review = new \stdClass();
  670. $review->{'@context'} = 'http://schema.org';
  671. $review->{'@type'} = 'Review';
  672. $review->author = new \stdClass();
  673. $review->author->{'@context'} = 'http://schema.org';
  674. $review->author->{'@type'} = 'Person';
  675. $review->author->name = $comment->comment_author;
  676. $review->reviewRating = new \stdClass();
  677. $review->reviewRating->{'@context'} = 'http://schema.org';
  678. $review->reviewRating->{'@type'} = 'Rating';
  679. $review->reviewRating->bestRating = 5;
  680. $review->reviewRating->worstRating = 1;
  681. $review->reviewRating->ratingValue = max( 1, absint( get_comment_meta( $comment->comment_ID, 'rating', true ) ) );
  682. $review->reviewBody = strip_tags( $comment->comment_content );
  683. $review->datePublished = date_i18n( 'c', strtotime( $comment->comment_date ) );
  684. $reviews[] = $review;
  685. }
  686. return $reviews;
  687. }
  688. /**
  689. * Adds new loop fields to the dropdown.
  690. *
  691. * @param array $values
  692. *
  693. * @return array
  694. * @since 2.12.0
  695. *
  696. */
  697. public static function wc_loop_fields( $values ) {
  698. if ( function_exists( 'WC' ) ) {
  699. $values['variable_products'] = __( 'Variable products (WooCommerce)', 'rich-snippets-schema' );
  700. }
  701. return $values;
  702. }
  703. /**
  704. * Returns the loop items.
  705. *
  706. * @param array $items
  707. * @param Rich_Snippet $snippet
  708. * @param int $post_id
  709. *
  710. * @return array
  711. * @since 2.12.0
  712. *
  713. */
  714. public static function loop_items( $items, $snippet, $post_id ) {
  715. if ( ! function_exists( 'WC' ) ) {
  716. return $items;
  717. }
  718. if ( 'variable_products' !== $snippet->get_loop_type() ) {
  719. return $items;
  720. }
  721. $product = wc_get_product( $post_id );
  722. if ( $product instanceof \WC_Product_Simple ) {
  723. return [
  724. $product->get_id() => $product
  725. ];
  726. }
  727. if ( $product instanceof \WC_Product_Variable || $product instanceof \WC_Product_Grouped ) {
  728. $ids = $product->get_children();
  729. if ( empty( $ids ) ) {
  730. return $items;
  731. }
  732. $products = get_posts( [
  733. 'include' => $ids,
  734. 'post_type' => [ 'product_variation', 'product' ]
  735. ] );
  736. if ( ! is_array( $products ) ) {
  737. return $items;
  738. }
  739. if ( count( $products ) <= 0 ) {
  740. return $items;
  741. }
  742. $products = array_combine( wp_list_pluck( $products, 'ID' ), $products );
  743. return $products;
  744. }
  745. return $items;
  746. }
  747. }