PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.3
WooCommerce v10.9.3
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / DataStores / StockNotifications / StockNotificationsDataStore.php
woocommerce / src / Internal / DataStores / StockNotifications Last commit date
StockNotificationsDataStore.php 10 months ago StockNotificationsMetaDataStore.php 10 months ago
StockNotificationsDataStore.php
627 lines
1 <?php
2 /**
3 * StockNotificationsDataStore class file.
4 */
5
6 declare( strict_types = 1 );
7
8 namespace Automattic\WooCommerce\Internal\DataStores\StockNotifications;
9
10 use Automattic\Jetpack\Constants;
11 use Automattic\WooCommerce\Internal\StockNotifications\Notification;
12 use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
13 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * The Stock Notifications Data Store.
19 */
20 class StockNotificationsDataStore implements \WC_Object_Data_Store_Interface {
21
22 /**
23 * The database util object to use.
24 *
25 * @var DatabaseUtil
26 */
27 protected DatabaseUtil $database_util;
28
29 /**
30 * Handles custom metadata in the wc_stock_notificationmeta table.
31 *
32 * @var StockNotificationsMetaDataStore
33 */
34 protected StockNotificationsMetaDataStore $data_store_meta;
35
36 /**
37 * Initialize.
38 *
39 * @internal
40 *
41 * @param StockNotificationsMetaDataStore $data_store_meta The data store meta instance to use.
42 * @param DatabaseUtil $database_util The database util instance to use.
43 *
44 * @return void
45 */
46 final public function init( StockNotificationsMetaDataStore $data_store_meta, DatabaseUtil $database_util ) {
47 $this->data_store_meta = $data_store_meta;
48 $this->database_util = $database_util;
49 }
50
51 /**
52 * Get the stock notifications table name.
53 *
54 * @return string
55 */
56 public function get_table_name(): string {
57 global $wpdb;
58 return $wpdb->prefix . 'wc_stock_notifications';
59 }
60
61 /**
62 * Get the stock notifications meta table name.
63 *
64 * @return string
65 */
66 public function get_meta_table_name(): string {
67 return $this->data_store_meta->get_table_name();
68 }
69
70 /**
71 * Get the database schema.
72 *
73 * @return string
74 */
75 public function get_database_schema(): string {
76
77 if ( ! Constants::is_true( 'WOOCOMMERCE_BIS_ALPHA_ENABLED' ) ) {
78 return '';
79 }
80
81 global $wpdb;
82
83 $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : '';
84
85 $table_name = $this->get_table_name();
86 $meta_table_name = $this->get_meta_table_name();
87 $max_index_length = $this->database_util->get_max_index_length();
88
89 $sql = "
90 CREATE TABLE $table_name (
91 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
92 product_id bigint(20) unsigned NOT NULL,
93 user_id bigint(20) unsigned NOT NULL,
94 user_email varchar(100) NOT NULL,
95 status varchar(20) NOT NULL DEFAULT 'pending',
96 date_created_gmt datetime NULL,
97 date_modified_gmt datetime NULL,
98 date_confirmed_gmt datetime NULL,
99 date_last_attempt_gmt datetime NULL,
100 date_notified_gmt datetime NULL,
101 date_cancelled_gmt datetime NULL,
102 cancellation_source varchar(30) NULL,
103 PRIMARY KEY (id),
104 KEY product_status_attempt (product_id, status, date_last_attempt_gmt, id),
105 KEY user_lookup (user_id, product_id, status),
106 KEY email_lookup (user_email, product_id, status)
107 ) $collate;
108 CREATE TABLE $meta_table_name (
109 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
110 notification_id bigint(20) unsigned NOT NULL,
111 meta_key varchar(255) NULL,
112 meta_value longtext NULL,
113 PRIMARY KEY (id),
114 KEY notification_id (notification_id),
115 KEY meta_key (meta_key($max_index_length))
116 ) $collate;
117 ";
118
119 return $sql;
120 }
121
122 /**
123 * Filter the raw meta data.
124 *
125 * This is required due to the use of the WC_Data::read_meta_data() method.
126 * It's a post-specific method that used to filter internal meta data.
127 * For custom tables, technically there is no internal meta data,
128 * so this method is a no-op.
129 *
130 * @param Notification $notification The data object to filter.
131 * @param array $raw_meta_data The raw meta data to filter.
132 * @return array
133 */
134 public function filter_raw_meta_data( &$notification, $raw_meta_data ): array {
135 return $raw_meta_data;
136 }
137
138 /**
139 * Get the internal meta keys.
140 *
141 * Required for the use of the WC_Data::is_internal_meta_key() method.
142 * It's a no-op for custom tables.
143 *
144 * @return array
145 */
146 public function get_internal_meta_keys(): array {
147 return array();
148 }
149
150 /**
151 * Create a new stock notification.
152 *
153 * @param Notification $notification The data object to create.
154 * @return int|\WP_Error The notification ID on success. WP_Error on failure.
155 */
156 public function create( &$notification ) {
157 global $wpdb;
158
159 // Fill in created and modified dates.
160 if ( ! $notification->get_date_created( 'edit' ) ) {
161 $notification->set_date_created( time() );
162 }
163 if ( ! $notification->get_date_modified( 'edit' ) ) {
164 $notification->set_date_modified( time() );
165 }
166
167 $insert = $wpdb->insert(
168 $this->get_table_name(),
169 array(
170 'product_id' => $notification->get_product_id( 'edit' ),
171 'user_id' => $notification->get_user_id( 'edit' ),
172 'user_email' => $notification->get_user_email( 'edit' ),
173 'status' => $notification->get_status( 'edit' ),
174 'date_created_gmt' => gmdate( 'Y-m-d H:i:s', $notification->get_date_created( 'edit' )->getTimestamp() ),
175 'date_modified_gmt' => gmdate( 'Y-m-d H:i:s', $notification->get_date_modified( 'edit' )->getTimestamp() ),
176 'date_confirmed_gmt' => $notification->get_date_confirmed( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_confirmed( 'edit' )->getTimestamp() ) : null,
177 'date_last_attempt_gmt' => $notification->get_date_last_attempt( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_last_attempt( 'edit' )->getTimestamp() ) : null,
178 'date_notified_gmt' => $notification->get_date_notified( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_notified( 'edit' )->getTimestamp() ) : null,
179 'date_cancelled_gmt' => $notification->get_date_cancelled( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_cancelled( 'edit' )->getTimestamp() ) : null,
180 'cancellation_source' => $notification->get_cancellation_source( 'edit' ),
181 ),
182 array(
183 '%d',
184 '%d',
185 '%s',
186 '%s',
187 '%s',
188 '%s',
189 '%s',
190 '%s',
191 '%s',
192 '%s',
193 '%s',
194 )
195 );
196
197 if ( false === $insert ) {
198 return new \WP_Error( 'db_insert_error', 'Could not insert stock notification into the database.' );
199 }
200
201 $notification_id = (int) $wpdb->insert_id;
202 $notification->set_id( $notification_id );
203 $notification->save_meta_data();
204 $notification->apply_changes();
205
206 return $notification->get_id();
207 }
208
209 /**
210 * Read a stock notification.
211 *
212 * @param Notification $notification The data object to read.
213 *
214 * @throws \Exception If the stock notification is not found.
215 *
216 * @return void
217 */
218 public function read( &$notification ) {
219 global $wpdb;
220
221 if ( 0 === $notification->get_id() ) {
222 throw new \Exception( 'Invalid notification ID.' );
223 }
224
225 $data = $wpdb->get_row(
226 $wpdb->prepare(
227 'SELECT * FROM %i WHERE id = %d',
228 $this->get_table_name(),
229 $notification->get_id()
230 )
231 );
232
233 if ( ! $data ) {
234 throw new \Exception( 'Stock notification not found' );
235 }
236
237 $notification->set_props(
238 array(
239 'id' => $data->id,
240 'product_id' => $data->product_id,
241 'user_id' => $data->user_id,
242 'user_email' => $data->user_email,
243 'status' => $data->status,
244 'date_created' => wc_string_to_timestamp( $data->date_created_gmt ),
245 'date_modified' => wc_string_to_timestamp( $data->date_modified_gmt ),
246 'date_confirmed' => wc_string_to_timestamp( $data->date_confirmed_gmt ),
247 'date_last_attempt' => wc_string_to_timestamp( $data->date_last_attempt_gmt ),
248 'date_notified' => wc_string_to_timestamp( $data->date_notified_gmt ),
249 'date_cancelled' => wc_string_to_timestamp( $data->date_cancelled_gmt ),
250 'cancellation_source' => $data->cancellation_source,
251 )
252 );
253
254 $notification->read_meta_data();
255 $notification->set_object_read( true );
256 }
257
258 /**
259 * Update a stock notification.
260 *
261 * @param Notification $notification The data object to update.
262 * @return int|\WP_Error The number of rows updated or WP_Error on failure.
263 */
264 public function update( &$notification ) {
265 global $wpdb;
266
267 if ( 0 === $notification->get_id() ) {
268 return new \WP_Error( 'invalid_stock_notification', 'Invalid notification ID.' );
269 }
270
271 $changes = $notification->get_changes();
272 $result = 0;
273
274 if ( array_intersect( array( 'product_id', 'user_id', 'user_email', 'status', 'date_modified', 'date_confirmed', 'date_last_attempt', 'date_notified', 'date_cancelled', 'cancellation_source' ), array_keys( $changes ) ) ) {
275
276 if ( ! array_key_exists( 'date_modified', $changes ) ) {
277 $notification->set_date_modified( time() );
278 }
279
280 $result = $wpdb->update(
281 $this->get_table_name(),
282 array(
283 'product_id' => $notification->get_product_id( 'edit' ),
284 'user_id' => $notification->get_user_id( 'edit' ),
285 'user_email' => $notification->get_user_email( 'edit' ),
286 'status' => $notification->get_status( 'edit' ),
287 'date_created_gmt' => $notification->get_date_created( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_created( 'edit' )->getTimestamp() ) : null,
288 'date_modified_gmt' => gmdate( 'Y-m-d H:i:s', $notification->get_date_modified( 'edit' )->getTimestamp() ),
289 'date_confirmed_gmt' => $notification->get_date_confirmed( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_confirmed( 'edit' )->getTimestamp() ) : null,
290 'date_last_attempt_gmt' => $notification->get_date_last_attempt( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_last_attempt( 'edit' )->getTimestamp() ) : null,
291 'date_notified_gmt' => $notification->get_date_notified( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_notified( 'edit' )->getTimestamp() ) : null,
292 'date_cancelled_gmt' => $notification->get_date_cancelled( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $notification->get_date_cancelled( 'edit' )->getTimestamp() ) : null,
293 'cancellation_source' => $notification->get_cancellation_source( 'edit' ),
294 ),
295 array( 'id' => $notification->get_id() ),
296 array( '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ),
297 array( '%d' )
298 );
299
300 if ( false === $result ) {
301 return new \WP_Error( 'db_update_error', 'Could not update stock notification in the database.' );
302 }
303
304 if ( 0 === $result ) {
305 return new \WP_Error( 'db_update_error', 'Invalid notification ID.' );
306 }
307 }
308
309 $notification->save_meta_data();
310
311 if ( $changes ) {
312 $notification->apply_changes();
313 }
314
315 return $result;
316 }
317
318 /**
319 * Delete a stock notification.
320 *
321 * @param Notification $notification The data object to delete.
322 * @param array $args Additional arguments.
323 * @return void
324 */
325 public function delete( &$notification, $args = array() ) {
326 global $wpdb;
327
328 $deleted = $wpdb->delete( $this->get_table_name(), array( 'id' => $notification->get_id() ), array( '%d' ) );
329
330 if ( $deleted > 0 ) {
331 $this->data_store_meta->delete_by_notification_id( $notification->get_id() );
332 }
333 }
334
335 /**
336 * Add meta.
337 *
338 * @param Notification $notification The data object to add.
339 * @param \stdClass $meta The meta object to add (containing ->key and ->value).
340 * @return int|false The meta ID or false if the meta was not added.
341 */
342 public function add_meta( &$notification, $meta ) {
343 $add_meta = $this->data_store_meta->add_meta( $notification, $meta );
344 $this->after_meta_change( $notification );
345 return $add_meta ? $add_meta : false;
346 }
347
348 /**
349 * Read meta.
350 *
351 * @param Notification $notification The data object to read.
352 * @return array
353 */
354 public function read_meta( &$notification ): array {
355 $raw_meta_data = $this->data_store_meta->read_meta( $notification );
356 return $this->filter_raw_meta_data( $notification, $raw_meta_data );
357 }
358
359 /**
360 * Update meta.
361 *
362 * @param Notification $notification The data object to update.
363 * @param \stdClass $meta The meta object to update (containing ->id, ->key and ->value).
364 * @return bool
365 */
366 public function update_meta( &$notification, $meta ): bool {
367 $update_meta = $this->data_store_meta->update_meta( $notification, $meta );
368 $this->after_meta_change( $notification );
369 return $update_meta;
370 }
371
372 /**
373 * Delete meta.
374 *
375 * @param Notification $notification The data object to delete.
376 * @param \stdClass $meta The meta object to delete (containing at least ->id).
377 * @return bool
378 */
379 public function delete_meta( &$notification, $meta ): bool {
380 $delete_meta = $this->data_store_meta->delete_meta( $notification, $meta );
381
382 $this->after_meta_change( $notification );
383 return $delete_meta;
384 }
385
386 /**
387 * Perform after meta change operations.
388 *
389 * @param Notification $notification The notification object.
390 * @return bool True if changes were applied, false otherwise.
391 */
392 private function after_meta_change( &$notification ): bool {
393
394 $current_time = time();
395 $current_date_time = new \WC_DateTime( "@$current_time", new \DateTimeZone( 'UTC' ) );
396
397 $should_save =
398 $notification->get_id() > 0
399 && $notification->get_date_modified( 'edit' ) < $current_date_time
400 && empty( $notification->get_changes() );
401
402 if ( $should_save ) {
403 $notification->set_date_modified( $current_time );
404 $saved = $notification->save();
405 return ! is_wp_error( $saved );
406 }
407
408 return false;
409 }
410
411 /**
412 * Query the stock notifications.
413 *
414 * @param array $args The arguments.
415 * @return array<int>|array<Notification>|int An array of notifications or the number of notifications.
416 */
417 public function query( array $args ) {
418 global $wpdb;
419
420 $args = wp_parse_args(
421 $args,
422 array(
423 'status' => '',
424 'product_id' => array(),
425 'user_id' => 0,
426 'user_email' => '',
427 'last_attempt_limit' => 0,
428 'start_date' => 0,
429 'end_date' => 0,
430 'limit' => -1,
431 'offset' => 0,
432 'order_by' => array( 'id' => 'ASC' ),
433 'return' => 'ids', // i.e. 'count', 'ids', 'objects'.
434 )
435 );
436
437 $table = $this->get_table_name();
438 $select = 'id';
439 if ( 'count' === $args['return'] ) {
440 $select = 'COUNT(id)';
441 } elseif ( 'objects' === $args['return'] ) {
442 $select = '*';
443 }
444
445 // WHERE clauses.
446 $where = array();
447 $where_values = array();
448
449 if ( $args['status'] ) {
450 $where[] = 'status = %s';
451 $where_values[] = esc_sql( $args['status'] );
452 }
453
454 if ( ! empty( $args['product_id'] ) ) {
455 $product_ids = array_map( 'absint', (array) $args['product_id'] );
456 $where[] = 'product_id IN (' . implode( ',', array_fill( 0, count( $product_ids ), '%d' ) ) . ')';
457 $where_values = array_merge( $where_values, $product_ids );
458 }
459
460 if ( $args['user_id'] ) {
461 $where[] = 'user_id = %d';
462 $where_values[] = absint( $args['user_id'] );
463 }
464
465 if ( $args['user_email'] ) {
466 $where[] = 'user_email = %s';
467 $where_values[] = esc_sql( $args['user_email'] );
468 }
469
470 if ( $args['last_attempt_limit'] > 0 ) {
471 $where[] = '(date_last_attempt_gmt < %s OR date_last_attempt_gmt IS NULL)';
472 $where_values[] = gmdate( 'Y-m-d H:i:s', $args['last_attempt_limit'] );
473 }
474
475 if ( $args['start_date'] ) {
476 $where[] = 'date_created_gmt >= %s';
477 $where_values[] = esc_sql( $args['start_date'] );
478 }
479
480 if ( $args['end_date'] ) {
481 $where[] = 'date_created_gmt < %s';
482 $where_values[] = esc_sql( $args['end_date'] );
483 }
484
485 // ORDER BY clauses.
486 $order_by = '';
487 $order_by_clauses = array();
488
489 if ( $args['order_by'] && is_array( $args['order_by'] ) ) {
490 foreach ( $args['order_by'] as $what => $how ) {
491 $order_by_clauses[] = $table . '.' . esc_sql( strval( $what ) ) . ' ' . esc_sql( strval( $how ) );
492 }
493 }
494
495 // Assemble the query.
496 $where = implode( ' AND ', $where );
497 $where = $where ? ' WHERE ' . $where : '';
498 $order_by = ! empty( $order_by_clauses ) ? ' ORDER BY ' . implode( ', ', $order_by_clauses ) : '';
499 $limit = $args['limit'] > 0 ? ' LIMIT ' . absint( $args['limit'] ) : '';
500 $offset = $args['offset'] > 0 ? ' OFFSET ' . absint( $args['offset'] ) : '';
501 $sql = "SELECT $select FROM $table $where $order_by $limit $offset";
502
503 // Prepare the query.
504 $prepared_sql = empty( $where_values ) ? $sql : $wpdb->prepare( $sql, $where_values ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
505
506 // Execute the query.
507 if ( 'count' === $args['return'] ) {
508 return (int) $wpdb->get_var( $prepared_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
509 }
510
511 $results = $wpdb->get_results( $prepared_sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
512 if ( empty( $results ) || ! is_array( $results ) ) {
513 return array();
514 }
515
516 if ( 'objects' === $args['return'] ) {
517
518 return array_map(
519 function ( $result ) {
520 return new Notification( $result );
521 },
522 $results
523 );
524 }
525
526 return array_map(
527 function ( $result ) {
528 return absint( $result['id'] );
529 },
530 $results
531 );
532 }
533
534 /**
535 * Check if the product has active notifications.
536 *
537 * @param array<int> $product_ids The product IDs.
538 * @return bool True if the product has active notifications, false otherwise.
539 */
540 public function product_has_active_notifications( array $product_ids ): bool {
541 global $wpdb;
542
543 $product_ids = array_filter( array_map( 'absint', $product_ids ) );
544 if ( empty( $product_ids ) ) {
545 return false;
546 }
547
548 $table = $this->get_table_name();
549 $format = array_fill( 0, count( $product_ids ), '%d' );
550 $query_in = '(' . implode( ',', $format ) . ')';
551 $sql = $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
552 "SELECT 1 FROM %i WHERE product_id IN $query_in AND status = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
553 array( $table, ...$product_ids, NotificationStatus::ACTIVE )
554 );
555 return (int) $wpdb->get_var( $sql ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
556 }
557
558 /**
559 * Check if a notification exists by email.
560 *
561 * @param int $product_id The product ID.
562 * @param string $email The email address.
563 * @return bool True if the notification exists, false otherwise.
564 */
565 public function notification_exists_by_email( int $product_id, string $email ): bool {
566
567 if ( ! is_email( $email ) ) {
568 return false;
569 }
570
571 global $wpdb;
572
573 $table = $this->get_table_name();
574 $sql = $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
575 'SELECT 1 FROM %i WHERE product_id = %d AND user_email = %s AND status IN (%s, %s) LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
576 array( $table, $product_id, $email, NotificationStatus::ACTIVE, NotificationStatus::PENDING )
577 );
578 return (int) $wpdb->get_var( $sql ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
579 }
580
581 /**
582 * Check if a notification exists by user ID.
583 *
584 * @param int $product_id The product ID.
585 * @param int $user_id The user ID.
586 * @return bool True if the notification exists, false otherwise.
587 */
588 public function notification_exists_by_user_id( int $product_id, int $user_id ): bool {
589
590 if ( 0 === $user_id ) {
591 return false;
592 }
593
594 global $wpdb;
595
596 $table = $this->get_table_name();
597 $sql = $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
598 'SELECT 1 FROM %i WHERE product_id = %d AND user_id = %d AND status IN (%s, %s) LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
599 array( $table, $product_id, $user_id, NotificationStatus::ACTIVE, NotificationStatus::PENDING )
600 );
601 return (int) $wpdb->get_var( $sql ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
602 }
603
604 /**
605 * Get distinct notification creation dates.
606 *
607 * @return array
608 */
609 public function get_distinct_dates() {
610
611 global $wpdb;
612
613 $results = $wpdb->get_results(
614 $wpdb->prepare(
615 'SELECT DISTINCT
616 YEAR(date_created_gmt) AS year,
617 MONTH(date_created_gmt) AS month
618 FROM %i
619 ORDER BY year DESC, month DESC',
620 $this->get_table_name()
621 )
622 );
623
624 return $results;
625 }
626 }
627