Ei kuvausta

abstract-wc-product.php 57KB

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