class-wc-customer-download.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. /**
  3. * Class for customer download permissions.
  4. *
  5. * @package WooCommerce/Classes
  6. * @version 3.0.0
  7. * @since 3.0.0
  8. */
  9. defined( 'ABSPATH' ) || exit;
  10. /**
  11. * Customer download class.
  12. */
  13. class WC_Customer_Download extends WC_Data implements ArrayAccess {
  14. /**
  15. * This is the name of this object type.
  16. *
  17. * @var string
  18. */
  19. protected $object_type = 'customer_download';
  20. /**
  21. * Download Data array.
  22. *
  23. * @since 3.0.0
  24. * @var array
  25. */
  26. protected $data = array(
  27. 'download_id' => '',
  28. 'product_id' => 0,
  29. 'user_id' => 0,
  30. 'user_email' => '',
  31. 'order_id' => 0,
  32. 'order_key' => '',
  33. 'downloads_remaining' => '',
  34. 'access_granted' => null,
  35. 'access_expires' => null,
  36. 'download_count' => 0,
  37. );
  38. /**
  39. * Constructor.
  40. *
  41. * @param int|object|array $download Download ID, instance or data.
  42. */
  43. public function __construct( $download = 0 ) {
  44. parent::__construct( $download );
  45. if ( is_numeric( $download ) && $download > 0 ) {
  46. $this->set_id( $download );
  47. } elseif ( $download instanceof self ) {
  48. $this->set_id( $download->get_id() );
  49. } elseif ( is_object( $download ) && ! empty( $download->permission_id ) ) {
  50. $this->set_id( $download->permission_id );
  51. $this->set_props( (array) $download );
  52. $this->set_object_read( true );
  53. } else {
  54. $this->set_object_read( true );
  55. }
  56. $this->data_store = WC_Data_Store::load( 'customer-download' );
  57. if ( $this->get_id() > 0 ) {
  58. $this->data_store->read( $this );
  59. }
  60. }
  61. /*
  62. |--------------------------------------------------------------------------
  63. | Getters
  64. |--------------------------------------------------------------------------
  65. */
  66. /**
  67. * Get download id.
  68. *
  69. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  70. * @return string
  71. */
  72. public function get_download_id( $context = 'view' ) {
  73. return $this->get_prop( 'download_id', $context );
  74. }
  75. /**
  76. * Get product id.
  77. *
  78. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  79. * @return integer
  80. */
  81. public function get_product_id( $context = 'view' ) {
  82. return $this->get_prop( 'product_id', $context );
  83. }
  84. /**
  85. * Get user id.
  86. *
  87. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  88. * @return integer
  89. */
  90. public function get_user_id( $context = 'view' ) {
  91. return $this->get_prop( 'user_id', $context );
  92. }
  93. /**
  94. * Get user_email.
  95. *
  96. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  97. * @return string
  98. */
  99. public function get_user_email( $context = 'view' ) {
  100. return $this->get_prop( 'user_email', $context );
  101. }
  102. /**
  103. * Get order_id.
  104. *
  105. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  106. * @return integer
  107. */
  108. public function get_order_id( $context = 'view' ) {
  109. return $this->get_prop( 'order_id', $context );
  110. }
  111. /**
  112. * Get order_key.
  113. *
  114. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  115. * @return string
  116. */
  117. public function get_order_key( $context = 'view' ) {
  118. return $this->get_prop( 'order_key', $context );
  119. }
  120. /**
  121. * Get downloads_remaining.
  122. *
  123. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  124. * @return integer|string
  125. */
  126. public function get_downloads_remaining( $context = 'view' ) {
  127. return $this->get_prop( 'downloads_remaining', $context );
  128. }
  129. /**
  130. * Get access_granted.
  131. *
  132. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  133. * @return WC_DateTime|null Object if the date is set or null if there is no date.
  134. */
  135. public function get_access_granted( $context = 'view' ) {
  136. return $this->get_prop( 'access_granted', $context );
  137. }
  138. /**
  139. * Get access_expires.
  140. *
  141. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  142. * @return WC_DateTime|null Object if the date is set or null if there is no date.
  143. */
  144. public function get_access_expires( $context = 'view' ) {
  145. return $this->get_prop( 'access_expires', $context );
  146. }
  147. /**
  148. * Get download_count.
  149. *
  150. * @param string $context What the value is for. Valid values are 'view' and 'edit'.
  151. * @return integer
  152. */
  153. public function get_download_count( $context = 'view' ) {
  154. // Check for count of download logs.
  155. $data_store = WC_Data_Store::load( 'customer-download-log' );
  156. $download_log_ids = $data_store->get_download_logs_for_permission( $this->get_id() );
  157. $download_log_count = 0;
  158. if ( ! empty( $download_log_ids ) ) {
  159. $download_log_count = count( $download_log_ids );
  160. }
  161. // Check download count in prop.
  162. $download_count_prop = $this->get_prop( 'download_count', $context );
  163. // Return the larger of the two in case they differ.
  164. // If logs are removed for some reason, we should still respect the
  165. // count stored in the prop.
  166. return max( $download_log_count, $download_count_prop );
  167. }
  168. /*
  169. |--------------------------------------------------------------------------
  170. | Setters
  171. |--------------------------------------------------------------------------
  172. */
  173. /**
  174. * Set download id.
  175. *
  176. * @param string $value Download ID.
  177. */
  178. public function set_download_id( $value ) {
  179. $this->set_prop( 'download_id', $value );
  180. }
  181. /**
  182. * Set product id.
  183. *
  184. * @param int $value Product ID.
  185. */
  186. public function set_product_id( $value ) {
  187. $this->set_prop( 'product_id', absint( $value ) );
  188. }
  189. /**
  190. * Set user id.
  191. *
  192. * @param int $value User ID.
  193. */
  194. public function set_user_id( $value ) {
  195. $this->set_prop( 'user_id', absint( $value ) );
  196. }
  197. /**
  198. * Set user_email.
  199. *
  200. * @param int $value User email.
  201. */
  202. public function set_user_email( $value ) {
  203. $this->set_prop( 'user_email', sanitize_email( $value ) );
  204. }
  205. /**
  206. * Set order_id.
  207. *
  208. * @param int $value Order ID.
  209. */
  210. public function set_order_id( $value ) {
  211. $this->set_prop( 'order_id', absint( $value ) );
  212. }
  213. /**
  214. * Set order_key.
  215. *
  216. * @param string $value Order key.
  217. */
  218. public function set_order_key( $value ) {
  219. $this->set_prop( 'order_key', $value );
  220. }
  221. /**
  222. * Set downloads_remaining.
  223. *
  224. * @param integer|string $value Amount of downloads remaining.
  225. */
  226. public function set_downloads_remaining( $value ) {
  227. $this->set_prop( 'downloads_remaining', '' === $value ? '' : absint( $value ) );
  228. }
  229. /**
  230. * Set access_granted.
  231. *
  232. * @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.
  233. */
  234. public function set_access_granted( $date = null ) {
  235. $this->set_date_prop( 'access_granted', $date );
  236. }
  237. /**
  238. * Set access_expires.
  239. *
  240. * @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.
  241. */
  242. public function set_access_expires( $date = null ) {
  243. $this->set_date_prop( 'access_expires', $date );
  244. }
  245. /**
  246. * Set download_count.
  247. *
  248. * @param int $value Download count.
  249. */
  250. public function set_download_count( $value ) {
  251. $this->set_prop( 'download_count', absint( $value ) );
  252. }
  253. /**
  254. * Track a download on this permission.
  255. *
  256. * @since 3.3.0
  257. * @throws Exception When permission ID is invalid.
  258. * @param int $user_id Id of the user performing the download.
  259. * @param string $user_ip_address IP Address of the user performing the download.
  260. */
  261. public function track_download( $user_id = null, $user_ip_address = null ) {
  262. global $wpdb;
  263. // Must have a permission_id to track download log.
  264. if ( ! ( $this->get_id() > 0 ) ) {
  265. throw new Exception( __( 'Invalid permission ID.', 'woocommerce' ) );
  266. }
  267. // Increment download count, and decrement downloads remaining.
  268. // Use SQL to avoid possible issues with downloads in quick succession.
  269. // If downloads_remaining is blank, leave it blank (unlimited).
  270. // Also, ensure downloads_remaining doesn't drop below zero.
  271. $query = $wpdb->prepare(
  272. "
  273. UPDATE {$wpdb->prefix}woocommerce_downloadable_product_permissions
  274. SET download_count = download_count + 1,
  275. downloads_remaining = IF( downloads_remaining = '', '', GREATEST( 0, downloads_remaining - 1 ) )
  276. WHERE permission_id = %d",
  277. $this->get_id()
  278. );
  279. $wpdb->query( $query ); // WPCS: unprepared SQL ok.
  280. // Re-read this download from the data store to pull updated counts.
  281. $this->data_store->read( $this );
  282. // Track download in download log.
  283. $download_log = new WC_Customer_Download_Log();
  284. $download_log->set_timestamp( current_time( 'timestamp', true ) );
  285. $download_log->set_permission_id( $this->get_id() );
  286. if ( ! is_null( $user_id ) ) {
  287. $download_log->set_user_id( $user_id );
  288. }
  289. if ( ! is_null( $user_ip_address ) ) {
  290. $download_log->set_user_ip_address( $user_ip_address );
  291. }
  292. $download_log->save();
  293. }
  294. /*
  295. |--------------------------------------------------------------------------
  296. | CRUD methods
  297. |--------------------------------------------------------------------------
  298. */
  299. /**
  300. * Save data to the database.
  301. *
  302. * @since 3.0.0
  303. * @return int Item ID
  304. */
  305. public function save() {
  306. if ( $this->data_store ) {
  307. // Trigger action before saving to the DB. Use a pointer to adjust object props before save.
  308. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
  309. if ( $this->get_id() ) {
  310. $this->data_store->update( $this );
  311. } else {
  312. $this->data_store->create( $this );
  313. }
  314. }
  315. return $this->get_id();
  316. }
  317. /*
  318. |--------------------------------------------------------------------------
  319. | ArrayAccess/Backwards compatibility.
  320. |--------------------------------------------------------------------------
  321. */
  322. /**
  323. * OffsetGet.
  324. *
  325. * @param string $offset Offset.
  326. * @return mixed
  327. */
  328. public function offsetGet( $offset ) {
  329. if ( is_callable( array( $this, "get_$offset" ) ) ) {
  330. return $this->{"get_$offset"}();
  331. }
  332. }
  333. /**
  334. * OffsetSet.
  335. *
  336. * @param string $offset Offset.
  337. * @param mixed $value Value.
  338. */
  339. public function offsetSet( $offset, $value ) {
  340. if ( is_callable( array( $this, "set_$offset" ) ) ) {
  341. $this->{"set_$offset"}( $value );
  342. }
  343. }
  344. /**
  345. * OffsetUnset
  346. *
  347. * @param string $offset Offset.
  348. */
  349. public function offsetUnset( $offset ) {
  350. if ( is_callable( array( $this, "set_$offset" ) ) ) {
  351. $this->{"set_$offset"}( '' );
  352. }
  353. }
  354. /**
  355. * OffsetExists.
  356. *
  357. * @param string $offset Offset.
  358. * @return bool
  359. */
  360. public function offsetExists( $offset ) {
  361. return in_array( $offset, array_keys( $this->data ), true );
  362. }
  363. /**
  364. * Magic __isset method for backwards compatibility. Legacy properties which could be accessed directly in the past.
  365. *
  366. * @param string $key Key name.
  367. * @return bool
  368. */
  369. public function __isset( $key ) {
  370. return in_array( $key, array_keys( $this->data ), true );
  371. }
  372. /**
  373. * Magic __get method for backwards compatibility. Maps legacy vars to new getters.
  374. *
  375. * @param string $key Key name.
  376. * @return mixed
  377. */
  378. public function __get( $key ) {
  379. if ( is_callable( array( $this, "get_$key" ) ) ) {
  380. return $this->{"get_$key"}( '' );
  381. }
  382. }
  383. }