AI
1 year ago
RateLimits
2 months ago
Reports
2 weeks ago
Templates
3 years ago
views
2 months ago
ActivityPanelCounts.php
2 weeks ago
AnalyticsImports.php
1 month ago
Coupons.php
4 years ago
CustomAttributeTraits.php
4 years ago
Customers.php
4 years ago
Data.php
4 years ago
DataCountries.php
4 years ago
DataDownloadIPs.php
4 years ago
Experiments.php
4 years ago
Features.php
4 years ago
Init.php
2 weeks ago
LaunchYourStore.php
1 year ago
Leaderboards.php
1 month ago
Marketing.php
3 months ago
MarketingCampaignTypes.php
3 years ago
MarketingCampaigns.php
1 month ago
MarketingChannels.php
3 years ago
MarketingOverview.php
3 months ago
MarketingRecommendations.php
2 years ago
MobileAppMagicLink.php
3 years ago
MobileAppQRLogin.php
2 months ago
NoteActions.php
2 months ago
Notes.php
2 months ago
Notice.php
1 year ago
OnboardingFreeExtensions.php
2 years ago
OnboardingPlugins.php
2 months ago
OnboardingProductTypes.php
4 years ago
OnboardingProducts.php
2 weeks ago
OnboardingProfile.php
1 year ago
OnboardingTasks.php
2 weeks ago
OnboardingThemes.php
3 weeks ago
Options.php
1 month ago
Orders.php
2 years ago
PaymentGatewaySuggestions.php
3 months ago
Plugins.php
3 months ago
ProductAttributeTerms.php
4 years ago
ProductAttributes.php
4 years ago
ProductCategories.php
4 years ago
ProductForm.php
3 years ago
ProductReviews.php
4 years ago
ProductVariations.php
1 year ago
Products.php
1 year ago
ProductsLowInStock.php
6 months ago
SettingOptions.php
3 years ago
Settings.php
2 months ago
ShippingPartnerSuggestions.php
3 months ago
Taxes.php
4 years ago
Themes.php
2 years ago
ActivityPanelCounts.php
212 lines
| 1 | <?php |
| 2 | /** |
| 3 | * REST API ActivityPanelCounts Controller |
| 4 | * |
| 5 | * Handles requests to /activity-panel/counts. |
| 6 | */ |
| 7 | |
| 8 | declare( strict_types=1 ); |
| 9 | |
| 10 | namespace Automattic\WooCommerce\Admin\API; |
| 11 | |
| 12 | defined( 'ABSPATH' ) || exit; |
| 13 | |
| 14 | /** |
| 15 | * ActivityPanelCounts controller. |
| 16 | * |
| 17 | * @internal |
| 18 | */ |
| 19 | class ActivityPanelCounts extends \WC_REST_Data_Controller { |
| 20 | |
| 21 | /** |
| 22 | * Endpoint namespace. |
| 23 | * |
| 24 | * @var string |
| 25 | */ |
| 26 | protected $namespace = 'wc-analytics'; |
| 27 | |
| 28 | /** |
| 29 | * Route base. |
| 30 | * |
| 31 | * @var string |
| 32 | */ |
| 33 | protected $rest_base = 'activity-panel/counts'; |
| 34 | |
| 35 | /** |
| 36 | * Register routes. |
| 37 | */ |
| 38 | public function register_routes(): void { |
| 39 | register_rest_route( |
| 40 | $this->namespace, |
| 41 | '/' . $this->rest_base, |
| 42 | array( |
| 43 | array( |
| 44 | 'methods' => \WP_REST_Server::READABLE, |
| 45 | 'callback' => array( $this, 'get_counts' ), |
| 46 | 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
| 47 | 'args' => $this->get_counts_params(), |
| 48 | ), |
| 49 | 'schema' => array( $this, 'get_item_schema' ), |
| 50 | ) |
| 51 | ); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Return the orders/reviews/low-stock counts used by the Activity Panel in one response, |
| 56 | * instead of one request per count. |
| 57 | * |
| 58 | * @param \WP_REST_Request<array<string, mixed>> $request Request object. |
| 59 | * @return \WP_REST_Response |
| 60 | */ |
| 61 | public function get_counts( $request ) { |
| 62 | $order_statuses = (array) $request->get_param( 'order_statuses' ); |
| 63 | |
| 64 | // When a merchant has cleared every actionable order status there is nothing |
| 65 | // "to fulfill". Short-circuit to 0 rather than querying: an empty status list |
| 66 | // would otherwise count every order, and the previous client-side |
| 67 | // getUnreadOrders() returned 0 in this case. |
| 68 | $orders_to_fulfill_count = empty( $order_statuses ) |
| 69 | ? 0 |
| 70 | : $this->get_count_via( |
| 71 | '/wc-analytics/orders', |
| 72 | array( |
| 73 | 'page' => 1, |
| 74 | 'per_page' => 1, |
| 75 | 'status' => $order_statuses, |
| 76 | '_fields' => array( 'id' ), |
| 77 | ) |
| 78 | ); |
| 79 | |
| 80 | return rest_ensure_response( |
| 81 | array( |
| 82 | 'orders_to_fulfill_count' => $orders_to_fulfill_count, |
| 83 | 'reviews_to_moderate_count' => $this->get_count_via( |
| 84 | '/wc-analytics/products/reviews', |
| 85 | array( |
| 86 | 'page' => 1, |
| 87 | 'per_page' => 1, |
| 88 | 'status' => $request->get_param( 'review_status' ), |
| 89 | '_fields' => array( 'id' ), |
| 90 | ) |
| 91 | ), |
| 92 | 'products_low_in_stock_count' => $this->get_count_via( |
| 93 | '/wc-analytics/products/count-low-in-stock', |
| 94 | array( 'status' => $request->get_param( 'product_status' ) ) |
| 95 | ), |
| 96 | ) |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Run one of the existing count endpoints internally and read its total off the response, |
| 102 | * so the counting logic itself (query building, permission checks) isn't duplicated here. |
| 103 | * |
| 104 | * @param string $route REST route to call, e.g. '/wc-analytics/orders'. |
| 105 | * @param array $params Query params for the sub-request. |
| 106 | * @return int|null Null when the sub-request failed, so callers can tell "unknown" apart from a real zero count. |
| 107 | */ |
| 108 | private function get_count_via( $route, $params ) { |
| 109 | $sub_request = new \WP_REST_Request( 'GET', $route ); |
| 110 | foreach ( $params as $key => $value ) { |
| 111 | $sub_request->set_param( $key, $value ); |
| 112 | } |
| 113 | |
| 114 | $response = rest_do_request( $sub_request ); |
| 115 | |
| 116 | if ( $response->is_error() ) { |
| 117 | wc_get_logger()->warning( |
| 118 | sprintf( 'Activity Panel counts sub-request to %s failed.', $route ), |
| 119 | array( 'source' => 'activity-panel-counts' ) |
| 120 | ); |
| 121 | return null; |
| 122 | } |
| 123 | |
| 124 | $headers = $response->get_headers(); |
| 125 | if ( isset( $headers['X-WP-Total'] ) ) { |
| 126 | return (int) $headers['X-WP-Total']; |
| 127 | } |
| 128 | |
| 129 | $data = $response->get_data(); |
| 130 | return isset( $data['total'] ) ? (int) $data['total'] : null; |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Get the query params for the /activity-panel/counts endpoint. |
| 135 | * |
| 136 | * @return array |
| 137 | */ |
| 138 | public function get_counts_params() { |
| 139 | $params = array(); |
| 140 | $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); |
| 141 | $params['order_statuses'] = array( |
| 142 | 'description' => __( 'Order statuses counted as "to fulfill".', 'woocommerce' ), |
| 143 | 'type' => 'array', |
| 144 | 'items' => array( 'type' => 'string' ), |
| 145 | 'default' => $this->get_default_order_statuses(), |
| 146 | 'sanitize_callback' => 'wp_parse_list', |
| 147 | 'validate_callback' => 'rest_validate_request_arg', |
| 148 | ); |
| 149 | $params['review_status'] = array( |
| 150 | 'description' => __( 'Review status counted as "to moderate".', 'woocommerce' ), |
| 151 | 'type' => 'string', |
| 152 | 'default' => 'hold', |
| 153 | 'sanitize_callback' => 'sanitize_key', |
| 154 | 'validate_callback' => 'rest_validate_request_arg', |
| 155 | ); |
| 156 | $params['product_status'] = array( |
| 157 | 'description' => __( 'Product post status used for the low stock count.', 'woocommerce' ), |
| 158 | 'type' => 'string', |
| 159 | 'default' => 'publish', |
| 160 | 'sanitize_callback' => 'sanitize_key', |
| 161 | 'validate_callback' => 'rest_validate_request_arg', |
| 162 | ); |
| 163 | |
| 164 | return $params; |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * Get the default order statuses counted as "to fulfill", matching the store's own |
| 169 | * actionable order statuses setting. |
| 170 | * |
| 171 | * @return array |
| 172 | */ |
| 173 | private function get_default_order_statuses() { |
| 174 | $actionable = get_option( 'woocommerce_actionable_order_statuses', false ); |
| 175 | |
| 176 | // Any array is respected as-is, including an explicitly empty one: the merchant |
| 177 | // intentionally cleared all actionable statuses, so there is nothing to fulfill, |
| 178 | // matching the previous client-side behaviour. A missing (never configured) or |
| 179 | // malformed option falls back to the built-in defaults. |
| 180 | return is_array( $actionable ) ? $actionable : array( 'processing', 'on-hold' ); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Get the schema for the /activity-panel/counts response. |
| 185 | * |
| 186 | * @return array |
| 187 | */ |
| 188 | public function get_item_schema() { |
| 189 | $schema = array( |
| 190 | '$schema' => 'http://json-schema.org/draft-04/schema#', |
| 191 | 'title' => 'activity_panel_counts', |
| 192 | 'type' => 'object', |
| 193 | 'properties' => array( |
| 194 | 'orders_to_fulfill_count' => array( |
| 195 | 'description' => __( 'Number of orders to fulfill. Null if the underlying sub-request failed.', 'woocommerce' ), |
| 196 | 'type' => array( 'integer', 'null' ), |
| 197 | ), |
| 198 | 'reviews_to_moderate_count' => array( |
| 199 | 'description' => __( 'Number of reviews awaiting moderation. Null if the underlying sub-request failed.', 'woocommerce' ), |
| 200 | 'type' => array( 'integer', 'null' ), |
| 201 | ), |
| 202 | 'products_low_in_stock_count' => array( |
| 203 | 'description' => __( 'Number of products low in stock. Null if the underlying sub-request failed.', 'woocommerce' ), |
| 204 | 'type' => array( 'integer', 'null' ), |
| 205 | ), |
| 206 | ), |
| 207 | ); |
| 208 | |
| 209 | return $this->add_additional_fields_schema( $schema ); |
| 210 | } |
| 211 | } |
| 212 |