PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.16
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.16
1.4.16 1.4.15 1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 All 179 releases
disco / rest / Analytics / ProductsApi.php

ProductsApi.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.4.16, at rest/Analytics/ProductsApi.php

295 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore
2
3 /**
4 * Analytics REST API — Products Endpoint
5 *
6 * @package Disco
7 * @subpackage \Rest\Analytics
8 * @since 1.1.13
9 * @category Rest
10 */
11
12 namespace Disco\Rest\Analytics;
13
14 use Disco\App\Analytics\Services\ProductService;
15 use WP_REST_Server;
16
17 /**
18 * Class ProductsApi
19 *
20 * Registers and handles the following REST API routes:
21 *
22 * - GET /disco/v1/analytics/products — Paginated products sold via campaigns.
23 * - GET /disco/v1/analytics/products/{id} — Single product detail.
24 *
25 * @package Disco
26 * @subpackage \Rest\Analytics
27 * @author Ohidul Islam <wahid0003@gmail.com>
28 * @link https://webappick.com
29 * @license https://opensource.org/licenses/gpl-license.php GNU Public License
30 * @category Rest
31 */
32 class ProductsApi extends Base {
33
34 /**
35 * Registers the REST API routes for the products analytics endpoint.
36 */
37 public function register_routes(): void {
38 // GET /analytics/products
39 register_rest_route(
40 $this->namespace,
41 '/' . $this->rest_base . '/products',
42 array(
43 array(
44 'methods' => WP_REST_Server::READABLE,
45 'callback' => array( $this, 'get_products' ),
46 'permission_callback' => array( $this, 'permissions_check' ),
47 'args' => $this->get_products_table_params(),
48 ),
49 'schema' => array( $this, 'get_product_schema' ),
50 )
51 );
52
53 // GET /analytics/products/{id}
54 register_rest_route(
55 $this->namespace,
56 '/' . $this->rest_base . '/products/(?P<id>[\d]+)',
57 array(
58 'args' => array(
59 'id' => array(
60 'description' => __( 'WooCommerce product ID.', 'disco' ),
61 'type' => 'integer',
62 ),
63 ),
64 array(
65 'methods' => WP_REST_Server::READABLE,
66 'callback' => array( $this, 'get_product' ),
67 'permission_callback' => array( $this, 'permissions_check' ),
68 'args' => $this->get_date_params(),
69 ),
70 'schema' => array( $this, 'get_product_schema' ),
71 )
72 );
73 }
74
75 // =========================================================================
76 // Callbacks
77 // =========================================================================
78
79 /**
80 * GET /disco/v1/analytics/products
81 *
82 * Returns a paginated product performance table for Disco-driven orders.
83 * Only completed orders with disco_campaign meta are included.
84 * Includes per-product category list and campaigns (via GROUP_CONCAT).
85 *
86 * Query params:
87 * date_from (string Y-m-d) Range start. Default: 28 days ago.
88 * date_to (string Y-m-d) Range end. Default: today.
89 * search (string) Numeric = exact product ID; text = product name LIKE search.
90 * sort_by (string) Alias for orderby. One of: revenue (default), orders, customers.
91 * orderby (string) Sort field. One of: total_revenue (default), total_orders, total_customers.
92 * order (string) Sort direction: asc | desc (default).
93 * campaign_id (int) Filter to orders that used this campaign.
94 * customer_id (int) Filter to orders by this WP user.
95 * order_id (int) Filter to products from a single order.
96 * page (int ≥ 1) Page number. Default: 1.
97 * limit (int 1–100) Results per page. Default: 10.
98 *
99 * @param \WP_REST_Request $request Incoming REST request.
100 * @return \WP_REST_Response|\WP_Error
101 */
102 // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh, SlevomatCodingStandard.Functions.FunctionLength.FunctionLength
103 public function get_products( $request ) {
104 $error = $this->validate_date_params( $request );
105
106 if ( is_wp_error( $error ) ) {
107 return $error;
108 }
109
110 $page = 1;
111 $per_page = 10;
112
113 if ( isset( $request['page'] ) ) {
114 $page = absint( $request['page'] );
115 }
116
117 if ( isset( $request['limit'] ) ) {
118 $per_page = min( absint( $request['limit'] ), 100 );
119 }
120
121 $sort_by_map = array(
122 'revenue' => 'total_revenue',
123 'orders' => 'total_orders',
124 'customers' => 'total_customers',
125 'quantity' => 'total_quantity',
126 );
127 $sort_by = '';
128 $default_orderby = 'total_revenue';
129
130 if ( is_string( $request['sort_by'] ) ) {
131 $sort_by = sanitize_text_field( $request['sort_by'] );
132 }
133
134 if ( is_string( $request['orderby'] ) ) {
135 $default_orderby = sanitize_text_field( $request['orderby'] );
136 }
137
138 $orderby = $sort_by_map[ $sort_by ] ?? $default_orderby;
139
140 $date_to = gmdate( 'Y-m-d' );
141
142 if ( is_string( $request['date_to'] ) && $request['date_to'] ) {
143 $date_to = sanitize_text_field( $request['date_to'] );
144 }
145
146 $date_from = gmdate( 'Y-m-d', strtotime( '-27 days', (int) strtotime( $date_to ) ) );
147
148 if ( is_string( $request['date_from'] ) && $request['date_from'] ) {
149 $date_from = sanitize_text_field( $request['date_from'] );
150 }
151
152 $args = array(
153 'date_from' => $date_from,
154 'date_to' => $date_to,
155 'search' => is_string( $request['search'] ) ? sanitize_text_field( $request['search'] ) : '',
156 'campaign_id' => isset( $request['campaign_id'] ) ? absint( $request['campaign_id'] ) : 0,
157 'customer_id' => isset( $request['customer_id'] ) ? absint( $request['customer_id'] ) : 0,
158 'order_id' => isset( $request['order_id'] ) ? absint( $request['order_id'] ) : 0,
159 'orderby' => $orderby,
160 'order' => is_string( $request['order'] ) ? sanitize_text_field( $request['order'] ) : 'desc',
161 'page' => $page,
162 'limit' => $per_page,
163 );
164
165 $result = ProductService::get_products_table( $args );
166 $pagination = $result['pagination'];
167 $total = (int) ( $pagination['total'] ?? 0 );
168 $total_pages = (int) ( $pagination['pages'] ?? 0 );
169
170 $response_data = array(
171 'current_period' => $result['current_period'],
172 'compare_period' => $result['compare_period'],
173 'data' => $this->add_item_links( $result['data'], 'products' ),
174 'collection' => $this->build_collection_meta( $total, $per_page, $page ),
175 'links' => $this->build_top_links( $request, 'products', $total_pages, $page ),
176 );
177
178 $response = rest_ensure_response( $response_data );
179 $response->header( 'X-WP-Total', $total );
180 $response->header( 'X-WP-TotalPages', $total_pages );
181
182 return $response;
183 }
184
185 /**
186 * GET /disco/v1/analytics/products/{id}
187 *
188 * Returns detail for a single product including aggregated campaign metrics.
189 *
190 * Path param:
191 * id (int) WooCommerce product ID.
192 *
193 * Query params:
194 * date_from (string Y-m-d) Range start. Default: all-time.
195 * date_to (string Y-m-d) Range end. Default: all-time.
196 *
197 * @param \WP_REST_Request $request Incoming REST request.
198 * @return \WP_REST_Response|\WP_Error
199 */
200 public function get_product( $request ) {
201 $error = $this->validate_date_params( $request );
202
203 if ( is_wp_error( $error ) ) {
204 return $error;
205 }
206
207 $args = array(
208 'date_from' => is_string( $request['date_from'] ) ? sanitize_text_field( $request['date_from'] ) : '',
209 'date_to' => is_string( $request['date_to'] ) ? sanitize_text_field( $request['date_to'] ) : '',
210 );
211
212 return rest_ensure_response( ProductService::get_product( absint( $request['id'] ), $args ) );
213 }
214
215 // =========================================================================
216 // Schema
217 // =========================================================================
218
219 /**
220 * Retrieves the product analytics schema, conforming to JSON Schema.
221 *
222 * @return array
223 */
224 public function get_product_schema() { //phpcs:ignore
225 return array(
226 '$schema' => 'http://json-schema.org/draft-04/schema#',
227 'title' => 'analytics-product',
228 'type' => 'object',
229 'properties' => array(
230 'id' => array(
231 'description' => __( 'Product ID.', 'disco' ),
232 'type' => 'integer',
233 'context' => array( 'view' ),
234 'readonly' => true,
235 ),
236 'name' => array(
237 'description' => __( 'Product name.', 'disco' ),
238 'type' => 'string',
239 'context' => array( 'view' ),
240 'readonly' => true,
241 ),
242 'image' => array(
243 'description' => __( 'Product image URL.', 'disco' ),
244 'type' => 'string',
245 'context' => array( 'view' ),
246 'readonly' => true,
247 ),
248 'unit_price' => array(
249 'description' => __( 'Regular (list) price.', 'disco' ),
250 'type' => 'number',
251 'context' => array( 'view' ),
252 'readonly' => true,
253 ),
254 'categories' => array(
255 'description' => __( 'Product categories (id + name).', 'disco' ),
256 'type' => 'array',
257 'context' => array( 'view' ),
258 'readonly' => true,
259 ),
260 'campaigns' => array(
261 'description' => __( 'Campaigns (id + name) this product was sold through.', 'disco' ),
262 'type' => 'array',
263 'context' => array( 'view' ),
264 'readonly' => true,
265 ),
266 'total_orders' => array(
267 'description' => __( 'Orders containing this product via a campaign.', 'disco' ),
268 'type' => 'integer',
269 'context' => array( 'view' ),
270 'readonly' => true,
271 ),
272 'total_customers' => array(
273 'description' => __( 'Distinct customers who bought this product.', 'disco' ),
274 'type' => 'integer',
275 'context' => array( 'view' ),
276 'readonly' => true,
277 ),
278 'total_quantity' => array(
279 'description' => __( 'Total units sold across all campaign orders.', 'disco' ),
280 'type' => 'integer',
281 'context' => array( 'view' ),
282 'readonly' => true,
283 ),
284 'total_revenue' => array(
285 'description' => __( 'Revenue from line totals.', 'disco' ),
286 'type' => 'number',
287 'context' => array( 'view' ),
288 'readonly' => true,
289 ),
290 ),
291 );
292 }
293
294 }
295