abstract-wc-product.php 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971
  1. <?php
  2. /**
  3. * WooCommerce product base class.
  4. *
  5. * @package WooCommerce/Abstracts
  6. */
  7. if ( ! defined( 'ABSPATH' ) ) {
  8. exit;
  9. }
  10. /**
  11. * Legacy product contains all deprecated methods for this class and can be
  12. * removed in the future.
  13. */
  14. require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-product.php';
  15. /**
  16. * Abstract Product Class
  17. *
  18. * The WooCommerce product class handles individual product data.
  19. *
  20. * @version 3.0.0
  21. * @package WooCommerce/Abstracts
  22. */
  23. class WC_Product extends WC_Abstract_Legacy_Product {
  24. /**
  25. * This is the name of this object type.
  26. *
  27. * @var string
  28. */
  29. protected $object_type = 'product';
  30. /**
  31. * Post type.
  32. *
  33. * @var string
  34. */
  35. protected $post_type = 'product';
  36. /**
  37. * Cache group.
  38. *
  39. * @var string
  40. */
  41. protected $cache_group = 'products';
  42. /**
  43. * Stores product data.
  44. *
  45. * @var array
  46. */
  47. protected $data = array(
  48. 'name' => '',
  49. 'slug' => '',
  50. 'date_created' => null,
  51. 'date_modified' => null,
  52. 'status' => false,
  53. 'featured' => false,
  54. 'catalog_visibility' => 'visible',
  55. 'description' => '',
  56. 'short_description' => '',
  57. 'sku' => '',
  58. 'price' => '',
  59. 'regular_price' => '',
  60. 'sale_price' => '',
  61. 'date_on_sale_from' => null,
  62. 'date_on_sale_to' => null,
  63. 'total_sales' => '0',
  64. 'tax_status' => 'taxable',
  65. 'tax_class' => '',
  66. 'manage_stock' => false,
  67. 'stock_quantity' => null,
  68. 'stock_status' => 'instock',
  69. 'backorders' => 'no',
  70. 'sold_individually' => false,
  71. 'weight' => '',
  72. 'length' => '',
  73. 'width' => '',
  74. 'height' => '',
  75. 'upsell_ids' => array(),
  76. 'cross_sell_ids' => array(),
  77. 'parent_id' => 0,
  78. 'reviews_allowed' => true,
  79. 'purchase_note' => '',
  80. 'attributes' => array(),
  81. 'default_attributes' => array(),
  82. 'menu_order' => 0,
  83. 'virtual' => false,
  84. 'downloadable' => false,
  85. 'category_ids' => array(),
  86. 'tag_ids' => array(),
  87. 'shipping_class_id' => 0,
  88. 'downloads' => array(),
  89. 'image_id' => '',
  90. 'gallery_image_ids' => array(),
  91. 'download_limit' => -1,
  92. 'download_expiry' => -1,
  93. 'rating_counts' => array(),
  94. 'average_rating' => 0,
  95. 'review_count' => 0,
  96. );
  97. /**
  98. * Supported features such as 'ajax_add_to_cart'.
  99. *
  100. * @var array
  101. */
  102. protected $supports = array();
  103. /**
  104. * Get the product if ID is passed, otherwise the product is new and empty.
  105. * This class should NOT be instantiated, but the wc_get_product() function
  106. * should be used. It is possible, but the wc_get_product() is preferred.
  107. *
  108. * @param int|WC_Product|object $product Product to init.
  109. */
  110. public function __construct( $product = 0 ) {
  111. parent::__construct( $product );
  112. if ( is_numeric( $product ) && $product > 0 ) {
  113. $this->set_id( $product );
  114. } elseif ( $product instanceof self ) {
  115. $this->set_id( absint( $product->get_id() ) );
  116. } elseif ( ! empty( $product->ID ) ) {
  117. $this->set_id( absint( $product->ID ) );
  118. } else {
  119. $this->set_object_read( true );
  120. }
  121. $this->data_store = WC_Data_Store::load( 'product-' . $this->get_type() );
  122. if ( $this->get_id() > 0 ) {
  123. $this->data_store->read( $this );
  124. }
  125. }
  126. /**
  127. * Get internal type. Should return string and *should be overridden* by child classes.
  128. *
  129. * The product_type property is deprecated but is used here for BW compatibility with child classes which may be defining product_type and not have a get_type method.
  130. *
  131. * @since 3.0.0
  132. * @return string
  133. */
  134. public function get_type() {
  135. return isset( $this->product_type ) ? $this->product_type : 'simple';
  136. }
  137. /*
  138. |--------------------------------------------------------------------------
  139. | Getters
  140. |--------------------------------------------------------------------------
  141. |
  142. | Methods for getting data from the product object.
  143. */
  144. /**
  145. * Get product name.
  146. *
  147. * @since 3.0.0
  148. * @param string $context What the value is for. Valid values are view and edit.
  149. * @return string
  150. */
  151. public function get_name( $context = 'view' ) {
  152. return $this->get_prop( 'name', $context );
  153. }
  154. /**
  155. * Get product slug.
  156. *
  157. * @since 3.0.0
  158. * @param string $context What the value is for. Valid values are view and edit.
  159. * @return string
  160. */
  161. public function get_slug( $context = 'view' ) {
  162. return $this->get_prop( 'slug', $context );
  163. }
  164. /**
  165. * Get product created date.
  166. *
  167. * @since 3.0.0
  168. * @param string $context What the value is for. Valid values are view and edit.
  169. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  170. */
  171. public function get_date_created( $context = 'view' ) {
  172. return $this->get_prop( 'date_created', $context );
  173. }
  174. /**
  175. * Get product modified date.
  176. *
  177. * @since 3.0.0
  178. * @param string $context What the value is for. Valid values are view and edit.
  179. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  180. */
  181. public function get_date_modified( $context = 'view' ) {
  182. return $this->get_prop( 'date_modified', $context );
  183. }
  184. /**
  185. * Get product status.
  186. *
  187. * @since 3.0.0
  188. * @param string $context What the value is for. Valid values are view and edit.
  189. * @return string
  190. */
  191. public function get_status( $context = 'view' ) {
  192. return $this->get_prop( 'status', $context );
  193. }
  194. /**
  195. * If the product is featured.
  196. *
  197. * @since 3.0.0
  198. * @param string $context What the value is for. Valid values are view and edit.
  199. * @return boolean
  200. */
  201. public function get_featured( $context = 'view' ) {
  202. return $this->get_prop( 'featured', $context );
  203. }
  204. /**
  205. * Get catalog visibility.
  206. *
  207. * @since 3.0.0
  208. * @param string $context What the value is for. Valid values are view and edit.
  209. * @return string
  210. */
  211. public function get_catalog_visibility( $context = 'view' ) {
  212. return $this->get_prop( 'catalog_visibility', $context );
  213. }
  214. /**
  215. * Get product description.
  216. *
  217. * @since 3.0.0
  218. * @param string $context What the value is for. Valid values are view and edit.
  219. * @return string
  220. */
  221. public function get_description( $context = 'view' ) {
  222. return $this->get_prop( 'description', $context );
  223. }
  224. /**
  225. * Get product short description.
  226. *
  227. * @since 3.0.0
  228. * @param string $context What the value is for. Valid values are view and edit.
  229. * @return string
  230. */
  231. public function get_short_description( $context = 'view' ) {
  232. return $this->get_prop( 'short_description', $context );
  233. }
  234. /**
  235. * Get SKU (Stock-keeping unit) - product unique ID.
  236. *
  237. * @param string $context What the value is for. Valid values are view and edit.
  238. * @return string
  239. */
  240. public function get_sku( $context = 'view' ) {
  241. return $this->get_prop( 'sku', $context );
  242. }
  243. /**
  244. * Returns the product's active price.
  245. *
  246. * @param string $context What the value is for. Valid values are view and edit.
  247. * @return string price
  248. */
  249. public function get_price( $context = 'view' ) {
  250. return $this->get_prop( 'price', $context );
  251. }
  252. /**
  253. * Returns the product's regular price.
  254. *
  255. * @param string $context What the value is for. Valid values are view and edit.
  256. * @return string price
  257. */
  258. public function get_regular_price( $context = 'view' ) {
  259. return $this->get_prop( 'regular_price', $context );
  260. }
  261. /**
  262. * Returns the product's sale price.
  263. *
  264. * @param string $context What the value is for. Valid values are view and edit.
  265. * @return string price
  266. */
  267. public function get_sale_price( $context = 'view' ) {
  268. return $this->get_prop( 'sale_price', $context );
  269. }
  270. /**
  271. * Get date on sale from.
  272. *
  273. * @since 3.0.0
  274. * @param string $context What the value is for. Valid values are view and edit.
  275. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  276. */
  277. public function get_date_on_sale_from( $context = 'view' ) {
  278. return $this->get_prop( 'date_on_sale_from', $context );
  279. }
  280. /**
  281. * Get date on sale to.
  282. *
  283. * @since 3.0.0
  284. * @param string $context What the value is for. Valid values are view and edit.
  285. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  286. */
  287. public function get_date_on_sale_to( $context = 'view' ) {
  288. return $this->get_prop( 'date_on_sale_to', $context );
  289. }
  290. /**
  291. * Get number total of sales.
  292. *
  293. * @since 3.0.0
  294. * @param string $context What the value is for. Valid values are view and edit.
  295. * @return int
  296. */
  297. public function get_total_sales( $context = 'view' ) {
  298. return $this->get_prop( 'total_sales', $context );
  299. }
  300. /**
  301. * Returns the tax status.
  302. *
  303. * @param string $context What the value is for. Valid values are view and edit.
  304. * @return string
  305. */
  306. public function get_tax_status( $context = 'view' ) {
  307. return $this->get_prop( 'tax_status', $context );
  308. }
  309. /**
  310. * Returns the tax class.
  311. *
  312. * @param string $context What the value is for. Valid values are view and edit.
  313. * @return string
  314. */
  315. public function get_tax_class( $context = 'view' ) {
  316. return $this->get_prop( 'tax_class', $context );
  317. }
  318. /**
  319. * Return if product manage stock.
  320. *
  321. * @since 3.0.0
  322. * @param string $context What the value is for. Valid values are view and edit.
  323. * @return boolean
  324. */
  325. public function get_manage_stock( $context = 'view' ) {
  326. return $this->get_prop( 'manage_stock', $context );
  327. }
  328. /**
  329. * Returns number of items available for sale.
  330. *
  331. * @param string $context What the value is for. Valid values are view and edit.
  332. * @return int|null
  333. */
  334. public function get_stock_quantity( $context = 'view' ) {
  335. return $this->get_prop( 'stock_quantity', $context );
  336. }
  337. /**
  338. * Return the stock status.
  339. *
  340. * @param string $context What the value is for. Valid values are view and edit.
  341. * @since 3.0.0
  342. * @return string
  343. */
  344. public function get_stock_status( $context = 'view' ) {
  345. return $this->get_prop( 'stock_status', $context );
  346. }
  347. /**
  348. * Get backorders.
  349. *
  350. * @param string $context What the value is for. Valid values are view and edit.
  351. * @since 3.0.0
  352. * @return string yes no or notify
  353. */
  354. public function get_backorders( $context = 'view' ) {
  355. return $this->get_prop( 'backorders', $context );
  356. }
  357. /**
  358. * Return if should be sold individually.
  359. *
  360. * @param string $context What the value is for. Valid values are view and edit.
  361. * @since 3.0.0
  362. * @return boolean
  363. */
  364. public function get_sold_individually( $context = 'view' ) {
  365. return $this->get_prop( 'sold_individually', $context );
  366. }
  367. /**
  368. * Returns the product's weight.
  369. *
  370. * @param string $context What the value is for. Valid values are view and edit.
  371. * @return string
  372. */
  373. public function get_weight( $context = 'view' ) {
  374. return $this->get_prop( 'weight', $context );
  375. }
  376. /**
  377. * Returns the product length.
  378. *
  379. * @param string $context What the value is for. Valid values are view and edit.
  380. * @return string
  381. */
  382. public function get_length( $context = 'view' ) {
  383. return $this->get_prop( 'length', $context );
  384. }
  385. /**
  386. * Returns the product width.
  387. *
  388. * @param string $context What the value is for. Valid values are view and edit.
  389. * @return string
  390. */
  391. public function get_width( $context = 'view' ) {
  392. return $this->get_prop( 'width', $context );
  393. }
  394. /**
  395. * Returns the product height.
  396. *
  397. * @param string $context What the value is for. Valid values are view and edit.
  398. * @return string
  399. */
  400. public function get_height( $context = 'view' ) {
  401. return $this->get_prop( 'height', $context );
  402. }
  403. /**
  404. * Returns formatted dimensions.
  405. *
  406. * @param bool $formatted True by default for legacy support - will be false/not set in future versions to return the array only. Use wc_format_dimensions for formatted versions instead.
  407. * @return string|array
  408. */
  409. public function get_dimensions( $formatted = true ) {
  410. if ( $formatted ) {
  411. wc_deprecated_argument( 'WC_Product::get_dimensions', '3.0', 'By default, get_dimensions has an argument set to true so that HTML is returned. This is to support the legacy version of the method. To get HTML dimensions, instead use wc_format_dimensions() function. Pass false to this method to return an array of dimensions. This will be the new default behavior in future versions.' );
  412. return apply_filters( 'woocommerce_product_dimensions', wc_format_dimensions( $this->get_dimensions( false ) ), $this );
  413. }
  414. return array(
  415. 'length' => $this->get_length(),
  416. 'width' => $this->get_width(),
  417. 'height' => $this->get_height(),
  418. );
  419. }
  420. /**
  421. * Get upsell IDs.
  422. *
  423. * @since 3.0.0
  424. * @param string $context What the value is for. Valid values are view and edit.
  425. * @return array
  426. */
  427. public function get_upsell_ids( $context = 'view' ) {
  428. return $this->get_prop( 'upsell_ids', $context );
  429. }
  430. /**
  431. * Get cross sell IDs.
  432. *
  433. * @since 3.0.0
  434. * @param string $context What the value is for. Valid values are view and edit.
  435. * @return array
  436. */
  437. public function get_cross_sell_ids( $context = 'view' ) {
  438. return $this->get_prop( 'cross_sell_ids', $context );
  439. }
  440. /**
  441. * Get parent ID.
  442. *
  443. * @since 3.0.0
  444. * @param string $context What the value is for. Valid values are view and edit.
  445. * @return int
  446. */
  447. public function get_parent_id( $context = 'view' ) {
  448. return $this->get_prop( 'parent_id', $context );
  449. }
  450. /**
  451. * Return if reviews is allowed.
  452. *
  453. * @since 3.0.0
  454. * @param string $context What the value is for. Valid values are view and edit.
  455. * @return bool
  456. */
  457. public function get_reviews_allowed( $context = 'view' ) {
  458. return $this->get_prop( 'reviews_allowed', $context );
  459. }
  460. /**
  461. * Get purchase note.
  462. *
  463. * @since 3.0.0
  464. * @param string $context What the value is for. Valid values are view and edit.
  465. * @return string
  466. */
  467. public function get_purchase_note( $context = 'view' ) {
  468. return $this->get_prop( 'purchase_note', $context );
  469. }
  470. /**
  471. * Returns product attributes.
  472. *
  473. * @param string $context What the value is for. Valid values are view and edit.
  474. * @return array
  475. */
  476. public function get_attributes( $context = 'view' ) {
  477. return $this->get_prop( 'attributes', $context );
  478. }
  479. /**
  480. * Get default attributes.
  481. *
  482. * @since 3.0.0
  483. * @param string $context What the value is for. Valid values are view and edit.
  484. * @return array
  485. */
  486. public function get_default_attributes( $context = 'view' ) {
  487. return $this->get_prop( 'default_attributes', $context );
  488. }
  489. /**
  490. * Get menu order.
  491. *
  492. * @since 3.0.0
  493. * @param string $context What the value is for. Valid values are view and edit.
  494. * @return int
  495. */
  496. public function get_menu_order( $context = 'view' ) {
  497. return $this->get_prop( 'menu_order', $context );
  498. }
  499. /**
  500. * Get category ids.
  501. *
  502. * @since 3.0.0
  503. * @param string $context What the value is for. Valid values are view and edit.
  504. * @return array
  505. */
  506. public function get_category_ids( $context = 'view' ) {
  507. return $this->get_prop( 'category_ids', $context );
  508. }
  509. /**
  510. * Get tag ids.
  511. *
  512. * @since 3.0.0
  513. * @param string $context What the value is for. Valid values are view and edit.
  514. * @return array
  515. */
  516. public function get_tag_ids( $context = 'view' ) {
  517. return $this->get_prop( 'tag_ids', $context );
  518. }
  519. /**
  520. * Get virtual.
  521. *
  522. * @since 3.0.0
  523. * @param string $context What the value is for. Valid values are view and edit.
  524. * @return bool
  525. */
  526. public function get_virtual( $context = 'view' ) {
  527. return $this->get_prop( 'virtual', $context );
  528. }
  529. /**
  530. * Returns the gallery attachment ids.
  531. *
  532. * @param string $context What the value is for. Valid values are view and edit.
  533. * @return array
  534. */
  535. public function get_gallery_image_ids( $context = 'view' ) {
  536. return $this->get_prop( 'gallery_image_ids', $context );
  537. }
  538. /**
  539. * Get shipping class ID.
  540. *
  541. * @since 3.0.0
  542. * @param string $context What the value is for. Valid values are view and edit.
  543. * @return int
  544. */
  545. public function get_shipping_class_id( $context = 'view' ) {
  546. return $this->get_prop( 'shipping_class_id', $context );
  547. }
  548. /**
  549. * Get downloads.
  550. *
  551. * @since 3.0.0
  552. * @param string $context What the value is for. Valid values are view and edit.
  553. * @return array
  554. */
  555. public function get_downloads( $context = 'view' ) {
  556. return $this->get_prop( 'downloads', $context );
  557. }
  558. /**
  559. * Get download expiry.
  560. *
  561. * @since 3.0.0
  562. * @param string $context What the value is for. Valid values are view and edit.
  563. * @return int
  564. */
  565. public function get_download_expiry( $context = 'view' ) {
  566. return $this->get_prop( 'download_expiry', $context );
  567. }
  568. /**
  569. * Get downloadable.
  570. *
  571. * @since 3.0.0
  572. * @param string $context What the value is for. Valid values are view and edit.
  573. * @return bool
  574. */
  575. public function get_downloadable( $context = 'view' ) {
  576. return $this->get_prop( 'downloadable', $context );
  577. }
  578. /**
  579. * Get download limit.
  580. *
  581. * @since 3.0.0
  582. * @param string $context What the value is for. Valid values are view and edit.
  583. * @return int
  584. */
  585. public function get_download_limit( $context = 'view' ) {
  586. return $this->get_prop( 'download_limit', $context );
  587. }
  588. /**
  589. * Get main image ID.
  590. *
  591. * @since 3.0.0
  592. * @param string $context What the value is for. Valid values are view and edit.
  593. * @return string
  594. */
  595. public function get_image_id( $context = 'view' ) {
  596. return $this->get_prop( 'image_id', $context );
  597. }
  598. /**
  599. * Get rating count.
  600. *
  601. * @param string $context What the value is for. Valid values are view and edit.
  602. * @return array of counts
  603. */
  604. public function get_rating_counts( $context = 'view' ) {
  605. return $this->get_prop( 'rating_counts', $context );
  606. }
  607. /**
  608. * Get average rating.
  609. *
  610. * @param string $context What the value is for. Valid values are view and edit.
  611. * @return float
  612. */
  613. public function get_average_rating( $context = 'view' ) {
  614. return $this->get_prop( 'average_rating', $context );
  615. }
  616. /**
  617. * Get review count.
  618. *
  619. * @param string $context What the value is for. Valid values are view and edit.
  620. * @return int
  621. */
  622. public function get_review_count( $context = 'view' ) {
  623. return $this->get_prop( 'review_count', $context );
  624. }
  625. /*
  626. |--------------------------------------------------------------------------
  627. | Setters
  628. |--------------------------------------------------------------------------
  629. |
  630. | Functions for setting product data. These should not update anything in the
  631. | database itself and should only change what is stored in the class
  632. | object.
  633. */
  634. /**
  635. * Set product name.
  636. *
  637. * @since 3.0.0
  638. * @param string $name Product name.
  639. */
  640. public function set_name( $name ) {
  641. $this->set_prop( 'name', $name );
  642. }
  643. /**
  644. * Set product slug.
  645. *
  646. * @since 3.0.0
  647. * @param string $slug Product slug.
  648. */
  649. public function set_slug( $slug ) {
  650. $this->set_prop( 'slug', $slug );
  651. }
  652. /**
  653. * Set product created date.
  654. *
  655. * @since 3.0.0
  656. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date.
  657. */
  658. public function set_date_created( $date = null ) {
  659. $this->set_date_prop( 'date_created', $date );
  660. }
  661. /**
  662. * Set product modified date.
  663. *
  664. * @since 3.0.0
  665. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date.
  666. */
  667. public function set_date_modified( $date = null ) {
  668. $this->set_date_prop( 'date_modified', $date );
  669. }
  670. /**
  671. * Set product status.
  672. *
  673. * @since 3.0.0
  674. * @param string $status Product status.
  675. */
  676. public function set_status( $status ) {
  677. $this->set_prop( 'status', $status );
  678. }
  679. /**
  680. * Set if the product is featured.
  681. *
  682. * @since 3.0.0
  683. * @param bool|string $featured Whether the product is featured or not.
  684. */
  685. public function set_featured( $featured ) {
  686. $this->set_prop( 'featured', wc_string_to_bool( $featured ) );
  687. }
  688. /**
  689. * Set catalog visibility.
  690. *
  691. * @since 3.0.0
  692. * @throws WC_Data_Exception Throws exception when invalid data is found.
  693. * @param string $visibility Options: 'hidden', 'visible', 'search' and 'catalog'.
  694. */
  695. public function set_catalog_visibility( $visibility ) {
  696. $options = array_keys( wc_get_product_visibility_options() );
  697. if ( ! in_array( $visibility, $options, true ) ) {
  698. $this->error( 'product_invalid_catalog_visibility', __( 'Invalid catalog visibility option.', 'woocommerce' ) );
  699. }
  700. $this->set_prop( 'catalog_visibility', $visibility );
  701. }
  702. /**
  703. * Set product description.
  704. *
  705. * @since 3.0.0
  706. * @param string $description Product description.
  707. */
  708. public function set_description( $description ) {
  709. $this->set_prop( 'description', $description );
  710. }
  711. /**
  712. * Set product short description.
  713. *
  714. * @since 3.0.0
  715. * @param string $short_description Product short description.
  716. */
  717. public function set_short_description( $short_description ) {
  718. $this->set_prop( 'short_description', $short_description );
  719. }
  720. /**
  721. * Set SKU.
  722. *
  723. * @since 3.0.0
  724. * @throws WC_Data_Exception Throws exception when invalid data is found.
  725. * @param string $sku Product SKU.
  726. */
  727. public function set_sku( $sku ) {
  728. $sku = (string) $sku;
  729. if ( $this->get_object_read() && ! empty( $sku ) && ! wc_product_has_unique_sku( $this->get_id(), $sku ) ) {
  730. $sku_found = wc_get_product_id_by_sku( $sku );
  731. $this->error( 'product_invalid_sku', __( 'Invalid or duplicated SKU.', 'woocommerce' ), 400, array( 'resource_id' => $sku_found ) );
  732. }
  733. $this->set_prop( 'sku', $sku );
  734. }
  735. /**
  736. * Set the product's active price.
  737. *
  738. * @param string $price Price.
  739. */
  740. public function set_price( $price ) {
  741. $this->set_prop( 'price', wc_format_decimal( $price ) );
  742. }
  743. /**
  744. * Set the product's regular price.
  745. *
  746. * @since 3.0.0
  747. * @param string $price Regular price.
  748. */
  749. public function set_regular_price( $price ) {
  750. $this->set_prop( 'regular_price', wc_format_decimal( $price ) );
  751. }
  752. /**
  753. * Set the product's sale price.
  754. *
  755. * @since 3.0.0
  756. * @param string $price sale price.
  757. */
  758. public function set_sale_price( $price ) {
  759. $this->set_prop( 'sale_price', wc_format_decimal( $price ) );
  760. }
  761. /**
  762. * Set date on sale from.
  763. *
  764. * @since 3.0.0
  765. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date.
  766. */
  767. public function set_date_on_sale_from( $date = null ) {
  768. $this->set_date_prop( 'date_on_sale_from', $date );
  769. }
  770. /**
  771. * Set date on sale to.
  772. *
  773. * @since 3.0.0
  774. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date.
  775. */
  776. public function set_date_on_sale_to( $date = null ) {
  777. $this->set_date_prop( 'date_on_sale_to', $date );
  778. }
  779. /**
  780. * Set number total of sales.
  781. *
  782. * @since 3.0.0
  783. * @param int $total Total of sales.
  784. */
  785. public function set_total_sales( $total ) {
  786. $this->set_prop( 'total_sales', absint( $total ) );
  787. }
  788. /**
  789. * Set the tax status.
  790. *
  791. * @since 3.0.0
  792. * @throws WC_Data_Exception Throws exception when invalid data is found.
  793. * @param string $status Tax status.
  794. */
  795. public function set_tax_status( $status ) {
  796. $options = array(
  797. 'taxable',
  798. 'shipping',
  799. 'none',
  800. );
  801. // Set default if empty.
  802. if ( empty( $status ) ) {
  803. $status = 'taxable';
  804. }
  805. if ( ! in_array( $status, $options, true ) ) {
  806. $this->error( 'product_invalid_tax_status', __( 'Invalid product tax status.', 'woocommerce' ) );
  807. }
  808. $this->set_prop( 'tax_status', $status );
  809. }
  810. /**
  811. * Set the tax class.
  812. *
  813. * @since 3.0.0
  814. * @param string $class Tax class.
  815. */
  816. public function set_tax_class( $class ) {
  817. $class = sanitize_title( $class );
  818. $class = 'standard' === $class ? '' : $class;
  819. $valid_classes = $this->get_valid_tax_classes();
  820. if ( ! in_array( $class, $valid_classes ) ) {
  821. $class = '';
  822. }
  823. $this->set_prop( 'tax_class', $class );
  824. }
  825. /**
  826. * Return an array of valid tax classes
  827. *
  828. * @return array valid tax classes
  829. */
  830. protected function get_valid_tax_classes() {
  831. return WC_Tax::get_tax_class_slugs();
  832. }
  833. /**
  834. * Set if product manage stock.
  835. *
  836. * @since 3.0.0
  837. * @param bool $manage_stock Whether or not manage stock is enabled.
  838. */
  839. public function set_manage_stock( $manage_stock ) {
  840. $this->set_prop( 'manage_stock', wc_string_to_bool( $manage_stock ) );
  841. }
  842. /**
  843. * Set number of items available for sale.
  844. *
  845. * @since 3.0.0
  846. * @param float|null $quantity Stock quantity.
  847. */
  848. public function set_stock_quantity( $quantity ) {
  849. $this->set_prop( 'stock_quantity', '' !== $quantity ? wc_stock_amount( $quantity ) : null );
  850. }
  851. /**
  852. * Set stock status.
  853. *
  854. * @param string $status New status.
  855. */
  856. public function set_stock_status( $status = 'instock' ) {
  857. $valid_statuses = wc_get_product_stock_status_options();
  858. if ( isset( $valid_statuses[ $status ] ) ) {
  859. $this->set_prop( 'stock_status', $status );
  860. } else {
  861. $this->set_prop( 'stock_status', 'instock' );
  862. }
  863. }
  864. /**
  865. * Set backorders.
  866. *
  867. * @since 3.0.0
  868. * @param string $backorders Options: 'yes', 'no' or 'notify'.
  869. */
  870. public function set_backorders( $backorders ) {
  871. $this->set_prop( 'backorders', $backorders );
  872. }
  873. /**
  874. * Set if should be sold individually.
  875. *
  876. * @since 3.0.0
  877. * @param bool $sold_individually Whether or not product is sold individually.
  878. */
  879. public function set_sold_individually( $sold_individually ) {
  880. $this->set_prop( 'sold_individually', wc_string_to_bool( $sold_individually ) );
  881. }
  882. /**
  883. * Set the product's weight.
  884. *
  885. * @since 3.0.0
  886. * @param float|string $weight Total weight.
  887. */
  888. public function set_weight( $weight ) {
  889. $this->set_prop( 'weight', '' === $weight ? '' : wc_format_decimal( $weight ) );
  890. }
  891. /**
  892. * Set the product length.
  893. *
  894. * @since 3.0.0
  895. * @param float|string $length Total length.
  896. */
  897. public function set_length( $length ) {
  898. $this->set_prop( 'length', '' === $length ? '' : wc_format_decimal( $length ) );
  899. }
  900. /**
  901. * Set the product width.
  902. *
  903. * @since 3.0.0
  904. * @param float|string $width Total width.
  905. */
  906. public function set_width( $width ) {
  907. $this->set_prop( 'width', '' === $width ? '' : wc_format_decimal( $width ) );
  908. }
  909. /**
  910. * Set the product height.
  911. *
  912. * @since 3.0.0
  913. * @param float|string $height Total height.
  914. */
  915. public function set_height( $height ) {
  916. $this->set_prop( 'height', '' === $height ? '' : wc_format_decimal( $height ) );
  917. }
  918. /**
  919. * Set upsell IDs.
  920. *
  921. * @since 3.0.0
  922. * @param array $upsell_ids IDs from the up-sell products.
  923. */
  924. public function set_upsell_ids( $upsell_ids ) {
  925. $this->set_prop( 'upsell_ids', array_filter( (array) $upsell_ids ) );
  926. }
  927. /**
  928. * Set crosssell IDs.
  929. *
  930. * @since 3.0.0
  931. * @param array $cross_sell_ids IDs from the cross-sell products.
  932. */
  933. public function set_cross_sell_ids( $cross_sell_ids ) {
  934. $this->set_prop( 'cross_sell_ids', array_filter( (array) $cross_sell_ids ) );
  935. }
  936. /**
  937. * Set parent ID.
  938. *
  939. * @since 3.0.0
  940. * @param int $parent_id Product parent ID.
  941. */
  942. public function set_parent_id( $parent_id ) {
  943. $this->set_prop( 'parent_id', absint( $parent_id ) );
  944. }
  945. /**
  946. * Set if reviews is allowed.
  947. *
  948. * @since 3.0.0
  949. * @param bool $reviews_allowed Reviews allowed or not.
  950. */
  951. public function set_reviews_allowed( $reviews_allowed ) {
  952. $this->set_prop( 'reviews_allowed', wc_string_to_bool( $reviews_allowed ) );
  953. }
  954. /**
  955. * Set purchase note.
  956. *
  957. * @since 3.0.0
  958. * @param string $purchase_note Purchase note.
  959. */
  960. public function set_purchase_note( $purchase_note ) {
  961. $this->set_prop( 'purchase_note', $purchase_note );
  962. }
  963. /**
  964. * Set product attributes.
  965. *
  966. * Attributes are made up of:
  967. * id - 0 for product level attributes. ID for global attributes.
  968. * name - Attribute name.
  969. * options - attribute value or array of term ids/names.
  970. * position - integer sort order.
  971. * visible - If visible on frontend.
  972. * variation - If used for variations.
  973. * Indexed by unqiue key to allow clearing old ones after a set.
  974. *
  975. * @since 3.0.0
  976. * @param array $raw_attributes Array of WC_Product_Attribute objects.
  977. */
  978. public function set_attributes( $raw_attributes ) {
  979. $attributes = array_fill_keys( array_keys( $this->get_attributes( 'edit' ) ), null );
  980. foreach ( $raw_attributes as $attribute ) {
  981. if ( is_a( $attribute, 'WC_Product_Attribute' ) ) {
  982. $attributes[ sanitize_title( $attribute->get_name() ) ] = $attribute;
  983. }
  984. }
  985. uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
  986. $this->set_prop( 'attributes', $attributes );
  987. }
  988. /**
  989. * Set default attributes.
  990. *
  991. * @since 3.0.0
  992. * @param array $default_attributes List of default attributes.
  993. */
  994. public function set_default_attributes( $default_attributes ) {
  995. $this->set_prop( 'default_attributes',
  996. array_filter( (array) $default_attributes, 'wc_array_filter_default_attributes' ) );
  997. }
  998. /**
  999. * Set menu order.
  1000. *
  1001. * @since 3.0.0
  1002. * @param int $menu_order Menu order.
  1003. */
  1004. public function set_menu_order( $menu_order ) {
  1005. $this->set_prop( 'menu_order', intval( $menu_order ) );
  1006. }
  1007. /**
  1008. * Set the product categories.
  1009. *
  1010. * @since 3.0.0
  1011. * @param array $term_ids List of terms IDs.
  1012. */
  1013. public function set_category_ids( $term_ids ) {
  1014. $this->set_prop( 'category_ids', array_unique( array_map( 'intval', $term_ids ) ) );
  1015. }
  1016. /**
  1017. * Set the product tags.
  1018. *
  1019. * @since 3.0.0
  1020. * @param array $term_ids List of terms IDs.
  1021. */
  1022. public function set_tag_ids( $term_ids ) {
  1023. $this->set_prop( 'tag_ids', array_unique( array_map( 'intval', $term_ids ) ) );
  1024. }
  1025. /**
  1026. * Set if the product is virtual.
  1027. *
  1028. * @since 3.0.0
  1029. * @param bool|string $virtual Whether product is virtual or not.
  1030. */
  1031. public function set_virtual( $virtual ) {
  1032. $this->set_prop( 'virtual', wc_string_to_bool( $virtual ) );
  1033. }
  1034. /**
  1035. * Set shipping class ID.
  1036. *
  1037. * @since 3.0.0
  1038. * @param int $id Product shipping class id.
  1039. */
  1040. public function set_shipping_class_id( $id ) {
  1041. $this->set_prop( 'shipping_class_id', absint( $id ) );
  1042. }
  1043. /**
  1044. * Set if the product is downloadable.
  1045. *
  1046. * @since 3.0.0
  1047. * @param bool|string $downloadable Whether product is downloadable or not.
  1048. */
  1049. public function set_downloadable( $downloadable ) {
  1050. $this->set_prop( 'downloadable', wc_string_to_bool( $downloadable ) );
  1051. }
  1052. /**
  1053. * Set downloads.
  1054. *
  1055. * @since 3.0.0
  1056. * @param array $downloads_array Array of WC_Product_Download objects or arrays.
  1057. */
  1058. public function set_downloads( $downloads_array ) {
  1059. $downloads = array();
  1060. $errors = array();
  1061. foreach ( $downloads_array as $download ) {
  1062. if ( is_a( $download, 'WC_Product_Download' ) ) {
  1063. $download_object = $download;
  1064. } else {
  1065. $download_object = new WC_Product_Download();
  1066. // If we don't have a previous hash, generate UUID for download.
  1067. if ( empty( $download['download_id'] ) ) {
  1068. $download['download_id'] = wp_generate_uuid4();
  1069. }
  1070. $download_object->set_id( $download['download_id'] );
  1071. $download_object->set_name( $download['name'] );
  1072. $download_object->set_file( $download['file'] );
  1073. }
  1074. // Validate the file extension.
  1075. if ( ! $download_object->is_allowed_filetype() ) {
  1076. if ( $this->get_object_read() ) {
  1077. /* translators: %1$s: Downloadable file */
  1078. $errors[] = sprintf( __( 'The downloadable file %1$s cannot be used as it does not have an allowed file type. Allowed types include: %2$s', 'woocommerce' ), '<code>' . basename( $download_object->get_file() ) . '</code>', '<code>' . implode( ', ', array_keys( $download_object->get_allowed_mime_types() ) ) . '</code>' );
  1079. }
  1080. continue;
  1081. }
  1082. // Validate the file exists.
  1083. if ( ! $download_object->file_exists() ) {
  1084. if ( $this->get_object_read() ) {
  1085. /* translators: %s: Downloadable file */
  1086. $errors[] = sprintf( __( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ), '<code>' . $download_object->get_file() . '</code>' );
  1087. }
  1088. continue;
  1089. }
  1090. $downloads[ $download_object->get_id() ] = $download_object;
  1091. }
  1092. if ( $errors ) {
  1093. $this->error( 'product_invalid_download', $errors[0] );
  1094. }
  1095. $this->set_prop( 'downloads', $downloads );
  1096. }
  1097. /**
  1098. * Set download limit.
  1099. *
  1100. * @since 3.0.0
  1101. * @param int|string $download_limit Product download limit.
  1102. */
  1103. public function set_download_limit( $download_limit ) {
  1104. $this->set_prop( 'download_limit', -1 === (int) $download_limit || '' === $download_limit ? -1 : absint( $download_limit ) );
  1105. }
  1106. /**
  1107. * Set download expiry.
  1108. *
  1109. * @since 3.0.0
  1110. * @param int|string $download_expiry Product download expiry.
  1111. */
  1112. public function set_download_expiry( $download_expiry ) {
  1113. $this->set_prop( 'download_expiry', -1 === (int) $download_expiry || '' === $download_expiry ? -1 : absint( $download_expiry ) );
  1114. }
  1115. /**
  1116. * Set gallery attachment ids.
  1117. *
  1118. * @since 3.0.0
  1119. * @param array $image_ids List of image ids.
  1120. */
  1121. public function set_gallery_image_ids( $image_ids ) {
  1122. $image_ids = wp_parse_id_list( $image_ids );
  1123. if ( $this->get_object_read() ) {
  1124. $image_ids = array_filter( $image_ids, 'wp_attachment_is_image' );
  1125. }
  1126. $this->set_prop( 'gallery_image_ids', $image_ids );
  1127. }
  1128. /**
  1129. * Set main image ID.
  1130. *
  1131. * @since 3.0.0
  1132. * @param int|string $image_id Product image id.
  1133. */
  1134. public function set_image_id( $image_id = '' ) {
  1135. $this->set_prop( 'image_id', $image_id );
  1136. }
  1137. /**
  1138. * Set rating counts. Read only.
  1139. *
  1140. * @param array $counts Product rating counts.
  1141. */
  1142. public function set_rating_counts( $counts ) {
  1143. $this->set_prop( 'rating_counts', array_filter( array_map( 'absint', (array) $counts ) ) );
  1144. }
  1145. /**
  1146. * Set average rating. Read only.
  1147. *
  1148. * @param float $average Product average rating.
  1149. */
  1150. public function set_average_rating( $average ) {
  1151. $this->set_prop( 'average_rating', wc_format_decimal( $average ) );
  1152. }
  1153. /**
  1154. * Set review count. Read only.
  1155. *
  1156. * @param int $count Product review count.
  1157. */
  1158. public function set_review_count( $count ) {
  1159. $this->set_prop( 'review_count', absint( $count ) );
  1160. }
  1161. /*
  1162. |--------------------------------------------------------------------------
  1163. | Other Methods
  1164. |--------------------------------------------------------------------------
  1165. */
  1166. /**
  1167. * Ensure properties are set correctly before save.
  1168. *
  1169. * @since 3.0.0
  1170. */
  1171. public function validate_props() {
  1172. // Before updating, ensure stock props are all aligned. Qty and backorders are not needed if not stock managed.
  1173. if ( ! $this->get_manage_stock() ) {
  1174. $this->set_stock_quantity( '' );
  1175. $this->set_backorders( 'no' );
  1176. // If we are stock managing and we don't have stock, force out of stock status.
  1177. } elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' === $this->get_backorders() ) {
  1178. $this->set_stock_status( 'outofstock' );
  1179. // If we are stock managing, backorders are allowed, and we don't have stock, force on backorder status.
  1180. } elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' !== $this->get_backorders() ) {
  1181. $this->set_stock_status( 'onbackorder' );
  1182. // If the stock level is changing and we do now have enough, force in stock status.
  1183. } elseif ( $this->get_stock_quantity() > get_option( 'woocommerce_notify_no_stock_amount', 0 ) ) {
  1184. $this->set_stock_status( 'instock' );
  1185. }
  1186. }
  1187. /**
  1188. * Save data (either create or update depending on if we are working on an existing product).
  1189. *
  1190. * @since 3.0.0
  1191. * @return int
  1192. */
  1193. public function save() {
  1194. $this->validate_props();
  1195. if ( $this->data_store ) {
  1196. // Trigger action before saving to the DB. Use a pointer to adjust object props before save.
  1197. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
  1198. if ( $this->get_id() ) {
  1199. $this->data_store->update( $this );
  1200. } else {
  1201. $this->data_store->create( $this );
  1202. }
  1203. if ( $this->get_parent_id() ) {
  1204. wc_deferred_product_sync( $this->get_parent_id() );
  1205. }
  1206. }
  1207. return $this->get_id();
  1208. }
  1209. /*
  1210. |--------------------------------------------------------------------------
  1211. | Conditionals
  1212. |--------------------------------------------------------------------------
  1213. */
  1214. /**
  1215. * Check if a product supports a given feature.
  1216. *
  1217. * Product classes should override this to declare support (or lack of support) for a feature.
  1218. *
  1219. * @param string $feature string The name of a feature to test support for.
  1220. * @return bool True if the product supports the feature, false otherwise.
  1221. * @since 2.5.0
  1222. */
  1223. public function supports( $feature ) {
  1224. return apply_filters( 'woocommerce_product_supports', in_array( $feature, $this->supports ), $feature, $this );
  1225. }
  1226. /**
  1227. * Returns whether or not the product post exists.
  1228. *
  1229. * @return bool
  1230. */
  1231. public function exists() {
  1232. return false !== $this->get_status();
  1233. }
  1234. /**
  1235. * Checks the product type.
  1236. *
  1237. * Backwards compatibility with downloadable/virtual.
  1238. *
  1239. * @param string|array $type Array or string of types.
  1240. * @return bool
  1241. */
  1242. public function is_type( $type ) {
  1243. return ( $this->get_type() === $type || ( is_array( $type ) && in_array( $this->get_type(), $type ) ) );
  1244. }
  1245. /**
  1246. * Checks if a product is downloadable.
  1247. *
  1248. * @return bool
  1249. */
  1250. public function is_downloadable() {
  1251. return apply_filters( 'woocommerce_is_downloadable', true === $this->get_downloadable(), $this );
  1252. }
  1253. /**
  1254. * Checks if a product is virtual (has no shipping).
  1255. *
  1256. * @return bool
  1257. */
  1258. public function is_virtual() {
  1259. return apply_filters( 'woocommerce_is_virtual', true === $this->get_virtual(), $this );
  1260. }
  1261. /**
  1262. * Returns whether or not the product is featured.
  1263. *
  1264. * @return bool
  1265. */
  1266. public function is_featured() {
  1267. return true === $this->get_featured();
  1268. }
  1269. /**
  1270. * Check if a product is sold individually (no quantities).
  1271. *
  1272. * @return bool
  1273. */
  1274. public function is_sold_individually() {
  1275. return apply_filters( 'woocommerce_is_sold_individually', true === $this->get_sold_individually(), $this );
  1276. }
  1277. /**
  1278. * Returns whether or not the product is visible in the catalog.
  1279. *
  1280. * @return bool
  1281. */
  1282. public function is_visible() {
  1283. $visible = 'visible' === $this->get_catalog_visibility() || ( is_search() && 'search' === $this->get_catalog_visibility() ) || ( ! is_search() && 'catalog' === $this->get_catalog_visibility() );
  1284. if ( 'trash' === $this->get_status() ) {
  1285. $visible = false;
  1286. } elseif ( 'publish' !== $this->get_status() && ! current_user_can( 'edit_post', $this->get_id() ) ) {
  1287. $visible = false;
  1288. }
  1289. if ( $this->get_parent_id() ) {
  1290. $parent_product = wc_get_product( $this->get_parent_id() );
  1291. if ( $parent_product && 'publish' !== $parent_product->get_status() ) {
  1292. $visible = false;
  1293. }
  1294. }
  1295. if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->is_in_stock() ) {
  1296. $visible = false;
  1297. }
  1298. return apply_filters( 'woocommerce_product_is_visible', $visible, $this->get_id() );
  1299. }
  1300. /**
  1301. * Returns false if the product cannot be bought.
  1302. *
  1303. * @return bool
  1304. */
  1305. public function is_purchasable() {
  1306. return apply_filters( 'woocommerce_is_purchasable', $this->exists() && ( 'publish' === $this->get_status() || current_user_can( 'edit_post', $this->get_id() ) ) && '' !== $this->get_price(), $this );
  1307. }
  1308. /**
  1309. * Returns whether or not the product is on sale.
  1310. *
  1311. * @param string $context What the value is for. Valid values are view and edit.
  1312. * @return bool
  1313. */
  1314. public function is_on_sale( $context = 'view' ) {
  1315. if ( '' !== (string) $this->get_sale_price( $context ) && $this->get_regular_price( $context ) > $this->get_sale_price( $context ) ) {
  1316. $on_sale = true;
  1317. if ( $this->get_date_on_sale_from( $context ) && $this->get_date_on_sale_from( $context )->getTimestamp() > current_time( 'timestamp', true ) ) {
  1318. $on_sale = false;
  1319. }
  1320. if ( $this->get_date_on_sale_to( $context ) && $this->get_date_on_sale_to( $context )->getTimestamp() < current_time( 'timestamp', true ) ) {
  1321. $on_sale = false;
  1322. }
  1323. } else {
  1324. $on_sale = false;
  1325. }
  1326. return 'view' === $context ? apply_filters( 'woocommerce_product_is_on_sale', $on_sale, $this ) : $on_sale;
  1327. }
  1328. /**
  1329. * Returns whether or not the product has dimensions set.
  1330. *
  1331. * @return bool
  1332. */
  1333. public function has_dimensions() {
  1334. return ( $this->get_length() || $this->get_height() || $this->get_width() ) && ! $this->get_virtual();
  1335. }
  1336. /**
  1337. * Returns whether or not the product has weight set.
  1338. *
  1339. * @return bool
  1340. */
  1341. public function has_weight() {
  1342. return $this->get_weight() && ! $this->get_virtual();
  1343. }
  1344. /**
  1345. * Returns whether or not the product can be purchased.
  1346. * This returns true for 'instock' and 'onbackorder' stock statuses.
  1347. *
  1348. * @return bool
  1349. */
  1350. public function is_in_stock() {
  1351. return apply_filters( 'woocommerce_product_is_in_stock', 'outofstock' !== $this->get_stock_status(), $this );
  1352. }
  1353. /**
  1354. * Checks if a product needs shipping.
  1355. *
  1356. * @return bool
  1357. */
  1358. public function needs_shipping() {
  1359. return apply_filters( 'woocommerce_product_needs_shipping', ! $this->is_virtual(), $this );
  1360. }
  1361. /**
  1362. * Returns whether or not the product is taxable.
  1363. *
  1364. * @return bool
  1365. */
  1366. public function is_taxable() {
  1367. return apply_filters( 'woocommerce_product_is_taxable', $this->get_tax_status() === 'taxable' && wc_tax_enabled(), $this );
  1368. }
  1369. /**
  1370. * Returns whether or not the product shipping is taxable.
  1371. *
  1372. * @return bool
  1373. */
  1374. public function is_shipping_taxable() {
  1375. return $this->needs_shipping() && ( $this->get_tax_status() === 'taxable' || $this->get_tax_status() === 'shipping' );
  1376. }
  1377. /**
  1378. * Returns whether or not the product is stock managed.
  1379. *
  1380. * @return bool
  1381. */
  1382. public function managing_stock() {
  1383. if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
  1384. return $this->get_manage_stock();
  1385. }
  1386. return false;
  1387. }
  1388. /**
  1389. * Returns whether or not the product can be backordered.
  1390. *
  1391. * @return bool
  1392. */
  1393. public function backorders_allowed() {
  1394. return apply_filters( 'woocommerce_product_backorders_allowed', ( 'yes' === $this->get_backorders() || 'notify' === $this->get_backorders() ), $this->get_id(), $this );
  1395. }
  1396. /**
  1397. * Returns whether or not the product needs to notify the customer on backorder.
  1398. *
  1399. * @return bool
  1400. */
  1401. public function backorders_require_notification() {
  1402. return apply_filters( 'woocommerce_product_backorders_require_notification', ( $this->managing_stock() && 'notify' === $this->get_backorders() ), $this );
  1403. }
  1404. /**
  1405. * Check if a product is on backorder.
  1406. *
  1407. * @param int $qty_in_cart (default: 0).
  1408. * @return bool
  1409. */
  1410. public function is_on_backorder( $qty_in_cart = 0 ) {
  1411. if ( 'onbackorder' === $this->get_stock_status() ) {
  1412. return true;
  1413. }
  1414. return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_stock_quantity() - $qty_in_cart ) < 0;
  1415. }
  1416. /**
  1417. * Returns whether or not the product has enough stock for the order.
  1418. *
  1419. * @param mixed $quantity Quantity of a product added to an order.
  1420. * @return bool
  1421. */
  1422. public function has_enough_stock( $quantity ) {
  1423. return ! $this->managing_stock() || $this->backorders_allowed() || $this->get_stock_quantity() >= $quantity;
  1424. }
  1425. /**
  1426. * Returns whether or not the product has any visible attributes.
  1427. *
  1428. * @return boolean
  1429. */
  1430. public function has_attributes() {
  1431. foreach ( $this->get_attributes() as $attribute ) {
  1432. if ( $attribute->get_visible() ) {
  1433. return true;
  1434. }
  1435. }
  1436. return false;
  1437. }
  1438. /**
  1439. * Returns whether or not the product has any child product.
  1440. *
  1441. * @return bool
  1442. */
  1443. public function has_child() {
  1444. return 0 < count( $this->get_children() );
  1445. }
  1446. /**
  1447. * Does a child have dimensions?
  1448. *
  1449. * @since 3.0.0
  1450. * @return bool
  1451. */
  1452. public function child_has_dimensions() {
  1453. return false;
  1454. }
  1455. /**
  1456. * Does a child have a weight?
  1457. *
  1458. * @since 3.0.0
  1459. * @return boolean
  1460. */
  1461. public function child_has_weight() {
  1462. return false;
  1463. }
  1464. /**
  1465. * Check if downloadable product has a file attached.
  1466. *
  1467. * @since 1.6.2
  1468. *
  1469. * @param string $download_id file identifier.
  1470. * @return bool Whether downloadable product has a file attached.
  1471. */
  1472. public function has_file( $download_id = '' ) {
  1473. return $this->is_downloadable() && $this->get_file( $download_id );
  1474. }
  1475. /**
  1476. * Returns whether or not the product has additional options that need
  1477. * selecting before adding to cart.
  1478. *
  1479. * @since 3.0.0
  1480. * @return boolean
  1481. */
  1482. public function has_options() {
  1483. return false;
  1484. }
  1485. /*
  1486. |--------------------------------------------------------------------------
  1487. | Non-CRUD Getters
  1488. |--------------------------------------------------------------------------
  1489. */
  1490. /**
  1491. * Get the product's title. For products this is the product name.
  1492. *
  1493. * @return string
  1494. */
  1495. public function get_title() {
  1496. return apply_filters( 'woocommerce_product_title', $this->get_name(), $this );
  1497. }
  1498. /**
  1499. * Product permalink.
  1500. *
  1501. * @return string
  1502. */
  1503. public function get_permalink() {
  1504. return get_permalink( $this->get_id() );
  1505. }
  1506. /**
  1507. * Returns the children IDs if applicable. Overridden by child classes.
  1508. *
  1509. * @return array of IDs
  1510. */
  1511. public function get_children() {
  1512. return array();
  1513. }
  1514. /**
  1515. * If the stock level comes from another product ID, this should be modified.
  1516. *
  1517. * @since 3.0.0
  1518. * @return int
  1519. */
  1520. public function get_stock_managed_by_id() {
  1521. return $this->get_id();
  1522. }
  1523. /**
  1524. * Returns the price in html format.
  1525. *
  1526. * @param string $deprecated Deprecated param.
  1527. *
  1528. * @return string
  1529. */
  1530. public function get_price_html( $deprecated = '' ) {
  1531. if ( '' === $this->get_price() ) {
  1532. $price = apply_filters( 'woocommerce_empty_price_html', '', $this );
  1533. } elseif ( $this->is_on_sale() ) {
  1534. $price = wc_format_sale_price( wc_get_price_to_display( $this, array( 'price' => $this->get_regular_price() ) ), wc_get_price_to_display( $this ) ) . $this->get_price_suffix();
  1535. } else {
  1536. $price = wc_price( wc_get_price_to_display( $this ) ) . $this->get_price_suffix();
  1537. }
  1538. return apply_filters( 'woocommerce_get_price_html', $price, $this );
  1539. }
  1540. /**
  1541. * Get product name with SKU or ID. Used within admin.
  1542. *
  1543. * @return string Formatted product name
  1544. */
  1545. public function get_formatted_name() {
  1546. if ( $this->get_sku() ) {
  1547. $identifier = $this->get_sku();
  1548. } else {
  1549. $identifier = '#' . $this->get_id();
  1550. }
  1551. return sprintf( '%2$s (%1$s)', $identifier, $this->get_name() );
  1552. }
  1553. /**
  1554. * Get min quantity which can be purchased at once.
  1555. *
  1556. * @since 3.0.0
  1557. * @return int
  1558. */
  1559. public function get_min_purchase_quantity() {
  1560. return 1;
  1561. }
  1562. /**
  1563. * Get max quantity which can be purchased at once.
  1564. *
  1565. * @since 3.0.0
  1566. * @return int Quantity or -1 if unlimited.
  1567. */
  1568. public function get_max_purchase_quantity() {
  1569. return $this->is_sold_individually() ? 1 : ( $this->backorders_allowed() || ! $this->managing_stock() ? -1 : $this->get_stock_quantity() );
  1570. }
  1571. /**
  1572. * Get the add to url used mainly in loops.
  1573. *
  1574. * @return string
  1575. */
  1576. public function add_to_cart_url() {
  1577. return apply_filters( 'woocommerce_product_add_to_cart_url', $this->get_permalink(), $this );
  1578. }
  1579. /**
  1580. * Get the add to cart button text for the single page.
  1581. *
  1582. * @return string
  1583. */
  1584. public function single_add_to_cart_text() {
  1585. return apply_filters( 'woocommerce_product_single_add_to_cart_text', __( 'Add to cart', 'woocommerce' ), $this );
  1586. }
  1587. /**
  1588. * Get the add to cart button text.
  1589. *
  1590. * @return string
  1591. */
  1592. public function add_to_cart_text() {
  1593. return apply_filters( 'woocommerce_product_add_to_cart_text', __( 'Read more', 'woocommerce' ), $this );
  1594. }
  1595. /**
  1596. * Get the add to cart button text description - used in aria tags.
  1597. *
  1598. * @since 3.3.0
  1599. * @return string
  1600. */
  1601. public function add_to_cart_description() {
  1602. /* translators: %s: Product title */
  1603. return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( __( 'Read more about &ldquo;%s&rdquo;', 'woocommerce' ), $this->get_name() ), $this );
  1604. }
  1605. /**
  1606. * Returns the main product image.
  1607. *
  1608. * @param string $size (default: 'woocommerce_thumbnail').
  1609. * @param array $attr Image attributes.
  1610. * @param bool $placeholder True to return $placeholder if no image is found, or false to return an empty string.
  1611. * @return string
  1612. */
  1613. public function get_image( $size = 'woocommerce_thumbnail', $attr = array(), $placeholder = true ) {
  1614. if ( has_post_thumbnail( $this->get_id() ) ) {
  1615. $image = get_the_post_thumbnail( $this->get_id(), $size, $attr );
  1616. } elseif ( ( $parent_id = wp_get_post_parent_id( $this->get_id() ) ) && has_post_thumbnail( $parent_id ) ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
  1617. $image = get_the_post_thumbnail( $parent_id, $size, $attr );
  1618. } elseif ( $placeholder ) {
  1619. $image = wc_placeholder_img( $size );
  1620. } else {
  1621. $image = '';
  1622. }
  1623. return apply_filters( 'woocommerce_product_get_image', $image, $this, $size, $attr, $placeholder, $image );
  1624. }
  1625. /**
  1626. * Returns the product shipping class SLUG.
  1627. *
  1628. * @return string
  1629. */
  1630. public function get_shipping_class() {
  1631. if ( $class_id = $this->get_shipping_class_id() ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
  1632. $term = get_term_by( 'id', $class_id, 'product_shipping_class' );
  1633. if ( $term && ! is_wp_error( $term ) ) {
  1634. return $term->slug;
  1635. }
  1636. }
  1637. return '';
  1638. }
  1639. /**
  1640. * Returns a single product attribute as a string.
  1641. *
  1642. * @param string $attribute to get.
  1643. * @return string
  1644. */
  1645. public function get_attribute( $attribute ) {
  1646. $attributes = $this->get_attributes();
  1647. $attribute = sanitize_title( $attribute );
  1648. if ( isset( $attributes[ $attribute ] ) ) {
  1649. $attribute_object = $attributes[ $attribute ];
  1650. } elseif ( isset( $attributes[ 'pa_' . $attribute ] ) ) {
  1651. $attribute_object = $attributes[ 'pa_' . $attribute ];
  1652. } else {
  1653. return '';
  1654. }
  1655. return $attribute_object->is_taxonomy() ? implode( ', ', wc_get_product_terms( $this->get_id(), $attribute_object->get_name(), array( 'fields' => 'names' ) ) ) : wc_implode_text_attributes( $attribute_object->get_options() );
  1656. }
  1657. /**
  1658. * Get the total amount (COUNT) of ratings, or just the count for one rating e.g. number of 5 star ratings.
  1659. *
  1660. * @param int $value Optional. Rating value to get the count for. By default returns the count of all rating values.
  1661. * @return int
  1662. */
  1663. public function get_rating_count( $value = null ) {
  1664. $counts = $this->get_rating_counts();
  1665. if ( is_null( $value ) ) {
  1666. return array_sum( $counts );
  1667. } elseif ( isset( $counts[ $value ] ) ) {
  1668. return absint( $counts[ $value ] );
  1669. } else {
  1670. return 0;
  1671. }
  1672. }
  1673. /**
  1674. * Get a file by $download_id.
  1675. *
  1676. * @param string $download_id file identifier.
  1677. * @return array|false if not found
  1678. */
  1679. public function get_file( $download_id = '' ) {
  1680. $files = $this->get_downloads();
  1681. if ( '' === $download_id ) {
  1682. $file = count( $files ) ? current( $files ) : false;
  1683. } elseif ( isset( $files[ $download_id ] ) ) {
  1684. $file = $files[ $download_id ];
  1685. } else {
  1686. $file = false;
  1687. }
  1688. return apply_filters( 'woocommerce_product_file', $file, $this, $download_id );
  1689. }
  1690. /**
  1691. * Get file download path identified by $download_id.
  1692. *
  1693. * @param string $download_id file identifier.
  1694. * @return string
  1695. */
  1696. public function get_file_download_path( $download_id ) {
  1697. $files = $this->get_downloads();
  1698. $file_path = isset( $files[ $download_id ] ) ? $files[ $download_id ]->get_file() : '';
  1699. // allow overriding based on the particular file being requested.
  1700. return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id );
  1701. }
  1702. /**
  1703. * Get the suffix to display after prices > 0.
  1704. *
  1705. * @param string $price to calculate, left blank to just use get_price().
  1706. * @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax().
  1707. * @return string
  1708. */
  1709. public function get_price_suffix( $price = '', $qty = 1 ) {
  1710. $html = '';
  1711. if ( ( $suffix = get_option( 'woocommerce_price_display_suffix' ) ) && wc_tax_enabled() && 'taxable' === $this->get_tax_status() ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
  1712. if ( '' === $price ) {
  1713. $price = $this->get_price();
  1714. }
  1715. $replacements = array(
  1716. '{price_including_tax}' => wc_price( wc_get_price_including_tax( $this, array( 'qty' => $qty, 'price' => $price ) ) ), // @phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine, WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
  1717. '{price_excluding_tax}' => wc_price( wc_get_price_excluding_tax( $this, array( 'qty' => $qty, 'price' => $price ) ) ), // @phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
  1718. );
  1719. $html = str_replace( array_keys( $replacements ), array_values( $replacements ), ' <small class="woocommerce-price-suffix">' . wp_kses_post( $suffix ) . '</small>' );
  1720. }
  1721. return apply_filters( 'woocommerce_get_price_suffix', $html, $this, $price, $qty );
  1722. }
  1723. /**
  1724. * Returns the availability of the product.
  1725. *
  1726. * @return string[]
  1727. */
  1728. public function get_availability() {
  1729. return apply_filters( 'woocommerce_get_availability', array(
  1730. 'availability' => $this->get_availability_text(),
  1731. 'class' => $this->get_availability_class(),
  1732. ), $this );
  1733. }
  1734. /**
  1735. * Get availability text based on stock status.
  1736. *
  1737. * @return string
  1738. */
  1739. protected function get_availability_text() {
  1740. if ( ! $this->is_in_stock() ) {
  1741. $availability = __( 'Out of stock', 'woocommerce' );
  1742. } elseif ( $this->managing_stock() && $this->is_on_backorder( 1 ) ) {
  1743. $availability = $this->backorders_require_notification() ? __( 'Available on backorder', 'woocommerce' ) : '';
  1744. } elseif ( $this->managing_stock() ) {
  1745. $availability = wc_format_stock_for_display( $this );
  1746. } else {
  1747. $availability = '';
  1748. }
  1749. return apply_filters( 'woocommerce_get_availability_text', $availability, $this );
  1750. }
  1751. /**
  1752. * Get availability classname based on stock status.
  1753. *
  1754. * @return string
  1755. */
  1756. protected function get_availability_class() {
  1757. if ( ! $this->is_in_stock() ) {
  1758. $class = 'out-of-stock';
  1759. } elseif ( $this->managing_stock() && $this->is_on_backorder( 1 ) ) {
  1760. $class = 'available-on-backorder';
  1761. } else {
  1762. $class = 'in-stock';
  1763. }
  1764. return apply_filters( 'woocommerce_get_availability_class', $class, $this );
  1765. }
  1766. }