abstract-wc-order-data-store-cpt.php
6 years ago
abstract-wc-order-item-type-data-store.php
6 years ago
class-wc-coupon-data-store-cpt.php
6 years ago
class-wc-customer-data-store-session.php
7 years ago
class-wc-customer-data-store.php
6 years ago
class-wc-customer-download-data-store.php
6 years ago
class-wc-customer-download-log-data-store.php
6 years ago
class-wc-data-store-wp.php
6 years ago
class-wc-order-data-store-cpt.php
6 years ago
class-wc-order-item-coupon-data-store.php
8 years ago
class-wc-order-item-data-store.php
6 years ago
class-wc-order-item-fee-data-store.php
8 years ago
class-wc-order-item-product-data-store.php
8 years ago
class-wc-order-item-shipping-data-store.php
8 years ago
class-wc-order-item-tax-data-store.php
6 years ago
class-wc-order-refund-data-store-cpt.php
6 years ago
class-wc-payment-token-data-store.php
6 years ago
class-wc-product-data-store-cpt.php
6 years ago
class-wc-product-grouped-data-store-cpt.php
7 years ago
class-wc-product-variable-data-store-cpt.php
6 years ago
class-wc-product-variation-data-store-cpt.php
6 years ago
class-wc-shipping-zone-data-store.php
6 years ago
class-wc-webhook-data-store.php
6 years ago
class-wc-webhook-data-store.php
448 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Webhook Data Store |
| 4 | * |
| 5 | * @version 3.3.0 |
| 6 | * @package WooCommerce/Classes/Data_Store |
| 7 | */ |
| 8 | |
| 9 | if ( ! defined( 'ABSPATH' ) ) { |
| 10 | exit; |
| 11 | } |
| 12 | |
| 13 | /** |
| 14 | * Webhook data store class. |
| 15 | */ |
| 16 | class WC_Webhook_Data_Store implements WC_Webhook_Data_Store_Interface { |
| 17 | |
| 18 | /** |
| 19 | * Create a new webhook in the database. |
| 20 | * |
| 21 | * @since 3.3.0 |
| 22 | * @param WC_Webhook $webhook Webhook instance. |
| 23 | */ |
| 24 | public function create( &$webhook ) { |
| 25 | global $wpdb; |
| 26 | |
| 27 | $changes = $webhook->get_changes(); |
| 28 | if ( isset( $changes['date_created'] ) ) { |
| 29 | $date_created = $webhook->get_date_created()->date( 'Y-m-d H:i:s' ); |
| 30 | $date_created_gmt = gmdate( 'Y-m-d H:i:s', $webhook->get_date_created()->getTimestamp() ); |
| 31 | } else { |
| 32 | $date_created = current_time( 'mysql' ); |
| 33 | $date_created_gmt = current_time( 'mysql', 1 ); |
| 34 | $webhook->set_date_created( $date_created ); |
| 35 | } |
| 36 | |
| 37 | // Pending delivery by default if not set while creating a new webhook. |
| 38 | if ( ! isset( $changes['pending_delivery'] ) ) { |
| 39 | $webhook->set_pending_delivery( true ); |
| 40 | } |
| 41 | |
| 42 | $data = array( |
| 43 | 'status' => $webhook->get_status( 'edit' ), |
| 44 | 'name' => $webhook->get_name( 'edit' ), |
| 45 | 'user_id' => $webhook->get_user_id( 'edit' ), |
| 46 | 'delivery_url' => $webhook->get_delivery_url( 'edit' ), |
| 47 | 'secret' => $webhook->get_secret( 'edit' ), |
| 48 | 'topic' => $webhook->get_topic( 'edit' ), |
| 49 | 'date_created' => $date_created, |
| 50 | 'date_created_gmt' => $date_created_gmt, |
| 51 | 'api_version' => $this->get_api_version_number( $webhook->get_api_version( 'edit' ) ), |
| 52 | 'failure_count' => $webhook->get_failure_count( 'edit' ), |
| 53 | 'pending_delivery' => $webhook->get_pending_delivery( 'edit' ), |
| 54 | ); |
| 55 | |
| 56 | $wpdb->insert( $wpdb->prefix . 'wc_webhooks', $data ); // WPCS: DB call ok. |
| 57 | |
| 58 | $webhook_id = $wpdb->insert_id; |
| 59 | $webhook->set_id( $webhook_id ); |
| 60 | $webhook->apply_changes(); |
| 61 | |
| 62 | $this->delete_transients( $webhook->get_status( 'edit' ) ); |
| 63 | WC_Cache_Helper::invalidate_cache_group( 'webhooks' ); |
| 64 | do_action( 'woocommerce_new_webhook', $webhook_id, $webhook ); |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Read a webhook from the database. |
| 69 | * |
| 70 | * @since 3.3.0 |
| 71 | * @param WC_Webhook $webhook Webhook instance. |
| 72 | * @throws Exception When webhook is invalid. |
| 73 | */ |
| 74 | public function read( &$webhook ) { |
| 75 | global $wpdb; |
| 76 | |
| 77 | $data = wp_cache_get( $webhook->get_id(), 'webhooks' ); |
| 78 | |
| 79 | if ( false === $data ) { |
| 80 | $data = $wpdb->get_row( $wpdb->prepare( "SELECT webhook_id, status, name, user_id, delivery_url, secret, topic, date_created, date_modified, api_version, failure_count, pending_delivery FROM {$wpdb->prefix}wc_webhooks WHERE webhook_id = %d LIMIT 1;", $webhook->get_id() ), ARRAY_A ); // WPCS: cache ok, DB call ok. |
| 81 | |
| 82 | wp_cache_add( $webhook->get_id(), $data, 'webhooks' ); |
| 83 | } |
| 84 | |
| 85 | if ( is_array( $data ) ) { |
| 86 | $webhook->set_props( |
| 87 | array( |
| 88 | 'id' => $data['webhook_id'], |
| 89 | 'status' => $data['status'], |
| 90 | 'name' => $data['name'], |
| 91 | 'user_id' => $data['user_id'], |
| 92 | 'delivery_url' => $data['delivery_url'], |
| 93 | 'secret' => $data['secret'], |
| 94 | 'topic' => $data['topic'], |
| 95 | 'date_created' => '0000-00-00 00:00:00' === $data['date_created'] ? null : $data['date_created'], |
| 96 | 'date_modified' => '0000-00-00 00:00:00' === $data['date_modified'] ? null : $data['date_modified'], |
| 97 | 'api_version' => $data['api_version'], |
| 98 | 'failure_count' => $data['failure_count'], |
| 99 | 'pending_delivery' => $data['pending_delivery'], |
| 100 | ) |
| 101 | ); |
| 102 | $webhook->set_object_read( true ); |
| 103 | |
| 104 | do_action( 'woocommerce_webhook_loaded', $webhook ); |
| 105 | } else { |
| 106 | throw new Exception( __( 'Invalid webhook.', 'woocommerce' ) ); |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Update a webhook. |
| 112 | * |
| 113 | * @since 3.3.0 |
| 114 | * @param WC_Webhook $webhook Webhook instance. |
| 115 | */ |
| 116 | public function update( &$webhook ) { |
| 117 | global $wpdb; |
| 118 | |
| 119 | $changes = $webhook->get_changes(); |
| 120 | $trigger = isset( $changes['delivery_url'] ); |
| 121 | |
| 122 | if ( isset( $changes['date_modified'] ) ) { |
| 123 | $date_modified = $webhook->get_date_modified()->date( 'Y-m-d H:i:s' ); |
| 124 | $date_modified_gmt = gmdate( 'Y-m-d H:i:s', $webhook->get_date_modified()->getTimestamp() ); |
| 125 | } else { |
| 126 | $date_modified = current_time( 'mysql' ); |
| 127 | $date_modified_gmt = current_time( 'mysql', 1 ); |
| 128 | $webhook->set_date_modified( $date_modified ); |
| 129 | } |
| 130 | |
| 131 | $data = array( |
| 132 | 'status' => $webhook->get_status( 'edit' ), |
| 133 | 'name' => $webhook->get_name( 'edit' ), |
| 134 | 'user_id' => $webhook->get_user_id( 'edit' ), |
| 135 | 'delivery_url' => $webhook->get_delivery_url( 'edit' ), |
| 136 | 'secret' => $webhook->get_secret( 'edit' ), |
| 137 | 'topic' => $webhook->get_topic( 'edit' ), |
| 138 | 'date_modified' => $date_modified, |
| 139 | 'date_modified_gmt' => $date_modified_gmt, |
| 140 | 'api_version' => $this->get_api_version_number( $webhook->get_api_version( 'edit' ) ), |
| 141 | 'failure_count' => $webhook->get_failure_count( 'edit' ), |
| 142 | 'pending_delivery' => $webhook->get_pending_delivery( 'edit' ), |
| 143 | ); |
| 144 | |
| 145 | $wpdb->update( |
| 146 | $wpdb->prefix . 'wc_webhooks', |
| 147 | $data, |
| 148 | array( |
| 149 | 'webhook_id' => $webhook->get_id(), |
| 150 | ) |
| 151 | ); // WPCS: DB call ok. |
| 152 | |
| 153 | $webhook->apply_changes(); |
| 154 | |
| 155 | if ( isset( $changes['status'] ) ) { |
| 156 | // We need to delete all transients, because we can't be sure of the old status. |
| 157 | $this->delete_transients( 'all' ); |
| 158 | } |
| 159 | wp_cache_delete( $webhook->get_id(), 'webhooks' ); |
| 160 | WC_Cache_Helper::invalidate_cache_group( 'webhooks' ); |
| 161 | |
| 162 | if ( 'active' === $webhook->get_status() && ( $trigger || $webhook->get_pending_delivery() ) ) { |
| 163 | $webhook->deliver_ping(); |
| 164 | } |
| 165 | |
| 166 | do_action( 'woocommerce_webhook_updated', $webhook->get_id() ); |
| 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Remove a webhook from the database. |
| 171 | * |
| 172 | * @since 3.3.0 |
| 173 | * @param WC_Webhook $webhook Webhook instance. |
| 174 | */ |
| 175 | public function delete( &$webhook ) { |
| 176 | global $wpdb; |
| 177 | |
| 178 | $wpdb->delete( |
| 179 | $wpdb->prefix . 'wc_webhooks', |
| 180 | array( |
| 181 | 'webhook_id' => $webhook->get_id(), |
| 182 | ), |
| 183 | array( '%d' ) |
| 184 | ); // WPCS: cache ok, DB call ok. |
| 185 | |
| 186 | $this->delete_transients( 'all' ); |
| 187 | wp_cache_delete( $webhook->get_id(), 'webhooks' ); |
| 188 | WC_Cache_Helper::invalidate_cache_group( 'webhooks' ); |
| 189 | do_action( 'woocommerce_webhook_deleted', $webhook->get_id(), $webhook ); |
| 190 | } |
| 191 | |
| 192 | /** |
| 193 | * Get API version number. |
| 194 | * |
| 195 | * @since 3.3.0 |
| 196 | * @param string $api_version REST API version. |
| 197 | * @return int |
| 198 | */ |
| 199 | public function get_api_version_number( $api_version ) { |
| 200 | return 'legacy_v3' === $api_version ? -1 : intval( substr( $api_version, -1 ) ); |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * Get webhooks IDs from the database. |
| 205 | * |
| 206 | * @since 3.3.0 |
| 207 | * @throws InvalidArgumentException If a $status value is passed in that is not in the known wc_get_webhook_statuses() keys. |
| 208 | * @param string $status Optional - status to filter results by. Must be a key in return value of @see wc_get_webhook_statuses(). @since 3.6.0. |
| 209 | * @return int[] |
| 210 | */ |
| 211 | public function get_webhooks_ids( $status = '' ) { |
| 212 | if ( ! empty( $status ) ) { |
| 213 | $this->validate_status( $status ); |
| 214 | } |
| 215 | |
| 216 | $ids = get_transient( $this->get_transient_key( $status ) ); |
| 217 | |
| 218 | if ( false === $ids ) { |
| 219 | $ids = $this->search_webhooks( |
| 220 | array( |
| 221 | 'limit' => -1, |
| 222 | 'status' => $status, |
| 223 | ) |
| 224 | ); |
| 225 | $ids = array_map( 'absint', $ids ); |
| 226 | set_transient( $this->get_transient_key( $status ), $ids ); |
| 227 | } |
| 228 | |
| 229 | return $ids; |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * Search webhooks. |
| 234 | * |
| 235 | * @param array $args Search arguments. |
| 236 | * @return array|object |
| 237 | */ |
| 238 | public function search_webhooks( $args ) { |
| 239 | global $wpdb; |
| 240 | |
| 241 | $args = wp_parse_args( |
| 242 | $args, |
| 243 | array( |
| 244 | 'limit' => 10, |
| 245 | 'offset' => 0, |
| 246 | 'order' => 'DESC', |
| 247 | 'orderby' => 'id', |
| 248 | 'paginate' => false, |
| 249 | ) |
| 250 | ); |
| 251 | |
| 252 | // Map post statuses. |
| 253 | $statuses = array( |
| 254 | 'publish' => 'active', |
| 255 | 'draft' => 'paused', |
| 256 | 'pending' => 'disabled', |
| 257 | ); |
| 258 | |
| 259 | // Map orderby to support a few post keys. |
| 260 | $orderby_mapping = array( |
| 261 | 'ID' => 'webhook_id', |
| 262 | 'id' => 'webhook_id', |
| 263 | 'name' => 'name', |
| 264 | 'title' => 'name', |
| 265 | 'post_title' => 'name', |
| 266 | 'post_name' => 'name', |
| 267 | 'date_created' => 'date_created_gmt', |
| 268 | 'date' => 'date_created_gmt', |
| 269 | 'post_date' => 'date_created_gmt', |
| 270 | 'date_modified' => 'date_modified_gmt', |
| 271 | 'modified' => 'date_modified_gmt', |
| 272 | 'post_modified' => 'date_modified_gmt', |
| 273 | ); |
| 274 | $orderby = isset( $orderby_mapping[ $args['orderby'] ] ) ? $orderby_mapping[ $args['orderby'] ] : 'webhook_id'; |
| 275 | $order = "ORDER BY {$orderby} " . esc_sql( strtoupper( $args['order'] ) ); |
| 276 | $limit = -1 < $args['limit'] ? $wpdb->prepare( 'LIMIT %d', $args['limit'] ) : ''; |
| 277 | $offset = 0 < $args['offset'] ? $wpdb->prepare( 'OFFSET %d', $args['offset'] ) : ''; |
| 278 | $status = ! empty( $args['status'] ) ? $wpdb->prepare( 'AND `status` = %s', isset( $statuses[ $args['status'] ] ) ? $statuses[ $args['status'] ] : $args['status'] ) : ''; |
| 279 | $search = ! empty( $args['search'] ) ? "AND `name` LIKE '%" . $wpdb->esc_like( sanitize_text_field( $args['search'] ) ) . "%'" : ''; |
| 280 | $include = ''; |
| 281 | $exclude = ''; |
| 282 | $date_created = ''; |
| 283 | $date_modified = ''; |
| 284 | |
| 285 | if ( ! empty( $args['include'] ) ) { |
| 286 | $args['include'] = implode( ',', wp_parse_id_list( $args['include'] ) ); |
| 287 | $include = 'AND webhook_id IN (' . $args['include'] . ')'; |
| 288 | } |
| 289 | |
| 290 | if ( ! empty( $args['exclude'] ) ) { |
| 291 | $args['exclude'] = implode( ',', wp_parse_id_list( $args['exclude'] ) ); |
| 292 | $exclude = 'AND webhook_id NOT IN (' . $args['exclude'] . ')'; |
| 293 | } |
| 294 | |
| 295 | if ( ! empty( $args['after'] ) || ! empty( $args['before'] ) ) { |
| 296 | $args['after'] = empty( $args['after'] ) ? '0000-00-00' : $args['after']; |
| 297 | $args['before'] = empty( $args['before'] ) ? current_time( 'mysql', 1 ) : $args['before']; |
| 298 | |
| 299 | $date_created = "AND `date_created_gmt` BETWEEN STR_TO_DATE('" . esc_sql( $args['after'] ) . "', '%Y-%m-%d %H:%i:%s') and STR_TO_DATE('" . esc_sql( $args['before'] ) . "', '%Y-%m-%d %H:%i:%s')"; |
| 300 | } |
| 301 | |
| 302 | if ( ! empty( $args['modified_after'] ) || ! empty( $args['modified_before'] ) ) { |
| 303 | $args['modified_after'] = empty( $args['modified_after'] ) ? '0000-00-00' : $args['modified_after']; |
| 304 | $args['modified_before'] = empty( $args['modified_before'] ) ? current_time( 'mysql', 1 ) : $args['modified_before']; |
| 305 | |
| 306 | $date_modified = "AND `date_modified_gmt` BETWEEN STR_TO_DATE('" . esc_sql( $args['modified_after'] ) . "', '%Y-%m-%d %H:%i:%s') and STR_TO_DATE('" . esc_sql( $args['modified_before'] ) . "', '%Y-%m-%d %H:%i:%s')"; |
| 307 | } |
| 308 | |
| 309 | // Check for cache. |
| 310 | $cache_key = WC_Cache_Helper::get_cache_prefix( 'webhooks' ) . 'search_webhooks' . md5( implode( ',', $args ) ); |
| 311 | $cache_value = wp_cache_get( $cache_key, 'webhook_search_results' ); |
| 312 | |
| 313 | if ( $cache_value ) { |
| 314 | return $cache_value; |
| 315 | } |
| 316 | |
| 317 | if ( $args['paginate'] ) { |
| 318 | $query = trim( |
| 319 | "SELECT SQL_CALC_FOUND_ROWS webhook_id |
| 320 | FROM {$wpdb->prefix}wc_webhooks |
| 321 | WHERE 1=1 |
| 322 | {$status} |
| 323 | {$search} |
| 324 | {$include} |
| 325 | {$exclude} |
| 326 | {$date_created} |
| 327 | {$date_modified} |
| 328 | {$order} |
| 329 | {$limit} |
| 330 | {$offset}" |
| 331 | ); |
| 332 | |
| 333 | $webhook_ids = wp_parse_id_list( $wpdb->get_col( $query ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
| 334 | $total = (int) $wpdb->get_var( 'SELECT FOUND_ROWS();' ); |
| 335 | $return_value = (object) array( |
| 336 | 'webhooks' => $webhook_ids, |
| 337 | 'total' => $total, |
| 338 | 'max_num_pages' => $args['limit'] > 1 ? ceil( $total / $args['limit'] ) : 1, |
| 339 | ); |
| 340 | } else { |
| 341 | $query = trim( |
| 342 | "SELECT webhook_id |
| 343 | FROM {$wpdb->prefix}wc_webhooks |
| 344 | WHERE 1=1 |
| 345 | {$status} |
| 346 | {$search} |
| 347 | {$include} |
| 348 | {$exclude} |
| 349 | {$date_created} |
| 350 | {$date_modified} |
| 351 | {$order} |
| 352 | {$limit} |
| 353 | {$offset}" |
| 354 | ); |
| 355 | |
| 356 | $webhook_ids = wp_parse_id_list( $wpdb->get_col( $query ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
| 357 | $return_value = $webhook_ids; |
| 358 | } |
| 359 | |
| 360 | wp_cache_set( $cache_key, $return_value, 'webhook_search_results' ); |
| 361 | |
| 362 | return $return_value; |
| 363 | } |
| 364 | |
| 365 | /** |
| 366 | * Count webhooks. |
| 367 | * |
| 368 | * @since 3.6.0 |
| 369 | * @param string $status Status to count. |
| 370 | * @return int |
| 371 | */ |
| 372 | protected function get_webhook_count( $status = 'active' ) { |
| 373 | global $wpdb; |
| 374 | $cache_key = WC_Cache_Helper::get_cache_prefix( 'webhooks' ) . $status . '_count'; |
| 375 | $count = wp_cache_get( $cache_key, 'webhooks' ); |
| 376 | |
| 377 | if ( false === $count ) { |
| 378 | $count = absint( $wpdb->get_var( $wpdb->prepare( "SELECT count( webhook_id ) FROM {$wpdb->prefix}wc_webhooks WHERE `status` = %s;", $status ) ) ); |
| 379 | |
| 380 | wp_cache_add( $cache_key, $count, 'webhooks' ); |
| 381 | } |
| 382 | |
| 383 | return $count; |
| 384 | } |
| 385 | |
| 386 | /** |
| 387 | * Get total webhook counts by status. |
| 388 | * |
| 389 | * @return array |
| 390 | */ |
| 391 | public function get_count_webhooks_by_status() { |
| 392 | $statuses = array_keys( wc_get_webhook_statuses() ); |
| 393 | $counts = array(); |
| 394 | |
| 395 | foreach ( $statuses as $status ) { |
| 396 | $counts[ $status ] = $this->get_webhook_count( $status ); |
| 397 | } |
| 398 | |
| 399 | return $counts; |
| 400 | } |
| 401 | |
| 402 | /** |
| 403 | * Check if a given string is in known statuses, based on return value of @see wc_get_webhook_statuses(). |
| 404 | * |
| 405 | * @since 3.6.0 |
| 406 | * @throws InvalidArgumentException If $status is not empty and not in the known wc_get_webhook_statuses() keys. |
| 407 | * @param string $status Status to check. |
| 408 | */ |
| 409 | private function validate_status( $status ) { |
| 410 | if ( ! array_key_exists( $status, wc_get_webhook_statuses() ) ) { |
| 411 | throw new InvalidArgumentException( sprintf( 'Invalid status given: %s. Status must be one of: %s.', $status, implode( ', ', array_keys( wc_get_webhook_statuses() ) ) ) ); |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | /** |
| 416 | * Get the transient key used to cache a set of webhook IDs, optionally filtered by status. |
| 417 | * |
| 418 | * @since 3.6.0 |
| 419 | * @param string $status Optional - status of cache key. |
| 420 | * @return string |
| 421 | */ |
| 422 | private function get_transient_key( $status = '' ) { |
| 423 | return empty( $status ) ? 'woocommerce_webhook_ids' : sprintf( 'woocommerce_webhook_ids_status_%s', $status ); |
| 424 | } |
| 425 | |
| 426 | /** |
| 427 | * Delete the transients used to cache a set of webhook IDs, optionally filtered by status. |
| 428 | * |
| 429 | * @since 3.6.0 |
| 430 | * @param string $status Optional - status of cache to delete, or 'all' to delete all caches. |
| 431 | */ |
| 432 | private function delete_transients( $status = '' ) { |
| 433 | |
| 434 | // Always delete the non-filtered cache. |
| 435 | delete_transient( $this->get_transient_key( '' ) ); |
| 436 | |
| 437 | if ( ! empty( $status ) ) { |
| 438 | if ( 'all' === $status ) { |
| 439 | foreach ( wc_get_webhook_statuses() as $status_key => $status_string ) { |
| 440 | delete_transient( $this->get_transient_key( $status_key ) ); |
| 441 | } |
| 442 | } else { |
| 443 | delete_transient( $this->get_transient_key( $status ) ); |
| 444 | } |
| 445 | } |
| 446 | } |
| 447 | } |
| 448 |