PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 All 36 releases
desktop-mode / apps / my-wordpress / parts / woocommerce.php

woocommerce.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.12, at apps/my-wordpress/parts/woocommerce.php

670 lines 21.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * My WordPress — the WooCommerce surface.
4 *
5 * Part of the `my-wordpress` app: required by `my-wordpress.os.php`,
6 * same namespace, plain `.php` on purpose — only `*.os.php` files are
7 * app entries to the framework loader. This part is the app's half of
8 * the WooCommerce integration: the Orders and Customers sections, the
9 * band-ordered Products / Coupons queries, and the per-row facts the
10 * shared `os.my-wordpress.*` JS seams read. Everything here is inert
11 * unless WooCommerce is active.
12 *
13 * It is deliberately thin: every rule — which band a product is in,
14 * how customers are ranked, what an order row says — lives in the
15 * existing `openstation_my_wordpress_woo_*` helpers WP Explorer
16 * already runs (`includes/my-wordpress/integrations/`), called here
17 * behind `function_exists` guards. One set of rules, two windows; the
18 * client half is the same `os-my-wordpress-woocommerce` bundle both
19 * windows load, subscribed to the same hook bus.
20 *
21 * @package OpenStation
22 */
23
24 namespace OpenStation\Apps\MyWordPress;
25
26 use OpenStation\App\Os;
27 use OpenStation\App\State;
28
29 // Direct access, unless a standalone host is booting on bare PHP.
30 if ( ! defined( 'ABSPATH' ) ) {
31 defined( 'OPENSTATION_STANDALONE' ) || exit;
32 }
33
34 /**
35 * Whether the WooCommerce integration helpers are loaded and active.
36 *
37 * @return bool
38 */
39 function woo_ready() {
40 return function_exists( 'openstation_my_wordpress_woo_active' )
41 && openstation_my_wordpress_woo_active();
42 }
43
44 /**
45 * Whether a section descriptor is one of ours.
46 *
47 * @param array<string,mixed> $section Section descriptor.
48 * @param string $id Section id to test for.
49 * @return bool
50 */
51 function woo_section_is( array $section, $id ) {
52 return isset( $section['id'] ) && $id === $section['id'];
53 }
54
55 /**
56 * The Woo folder's group fields, through the same
57 * `openstation_my_wordpress_woo_group()` relabelling WP Explorer uses.
58 *
59 * @return array{id:string,label:string,icon:string,order:int}
60 */
61 function woo_group_fields() {
62 $group = array(
63 'id' => 'plugin:woocommerce',
64 'label' => 'WooCommerce',
65 'icon' => 'dashicons-admin-plugins',
66 'order' => 20,
67 );
68 if ( function_exists( 'openstation_my_wordpress_woo_group' ) ) {
69 $group = (array) openstation_my_wordpress_woo_group( $group, 'shop_order' );
70 }
71 return $group;
72 }
73
74 /**
75 * The sections this part adds to the root: Orders and Customers —
76 * the two shop surfaces a plain post-type folder cannot serve.
77 * Orders live outside `wp_posts` under High-Performance Order
78 * Storage, and Customers are a ranking over users, not a post type.
79 *
80 * Same labels, icons, groups and permission gates as WP Explorer's
81 * `wc-orders` / `wc-customers` entities. `flat => true` marks a
82 * section with no detail folder behind its tiles — the client keeps
83 * Navigate into and the quick-edit modal off it, the actions refuse
84 * post mutations on it.
85 *
86 * @param Os $os Host handle.
87 * @return array<int,array<string,mixed>>
88 */
89 function woo_sections( Os $os ) {
90 unset( $os );
91 if ( ! woo_ready() ) {
92 return array();
93 }
94
95 $group = woo_group_fields();
96 $sections = array();
97
98 $orders = get_post_type_object( 'shop_order' );
99 if ( $orders instanceof \WP_Post_Type && ! empty( $orders->cap->edit_posts ) ) {
100 $sections[] = array(
101 'id' => 'wc-orders',
102 'label' => __( 'Orders', 'desktop-mode' ),
103 'icon' => 'dashicons-cart',
104 'kind' => 'post',
105 // Claims `shop_order`, so the generic post-type pass skips
106 // it — the WP_Query path returns an empty folder on any
107 // HPOS store.
108 'post_type' => 'shop_order',
109 'capability' => (string) $orders->cap->edit_posts,
110 'thumbnails' => false,
111 'flat' => true,
112 'group' => $group['id'],
113 'groupLabel' => $group['label'],
114 'groupIcon' => $group['icon'],
115 'groupOrder' => (int) $group['order'],
116 );
117 }
118
119 if ( function_exists( 'openstation_my_wordpress_woo_customers_permission' )
120 && true === openstation_my_wordpress_woo_customers_permission() ) {
121 $sections[] = array(
122 'id' => 'wc-customers',
123 'label' => __( 'Customers', 'desktop-mode' ),
124 'icon' => 'dashicons-groups',
125 // Renders through the built-in user kind: avatar tiles, the
126 // dossier pane, the drag-out seam. A customer is a person
127 // before they are a row of money.
128 'kind' => 'user',
129 'post_type' => '',
130 // The two-gate permission was checked above; no single
131 // capability string expresses it.
132 'capability' => '',
133 'thumbnails' => true,
134 'group' => $group['id'],
135 'groupLabel' => $group['label'],
136 'groupIcon' => $group['icon'],
137 'groupOrder' => (int) $group['order'],
138 );
139 }
140
141 return $sections;
142 }
143
144 /**
145 * Give a discovered CPT section WooCommerce's icons and thumbnail
146 * rules, through the same `openstation_my_wordpress_woo_entity_icon()`
147 * (and its `openstation_my_wordpress_woo_section_icons` filter) that
148 * decorates WP Explorer's entities. Non-Woo types pass through
149 * untouched.
150 *
151 * @param array<string,mixed> $section Section descriptor.
152 * @param \WP_Post_Type $post_type Post type object.
153 * @return array<string,mixed>
154 */
155 function woo_decorate_section( array $section, \WP_Post_Type $post_type ) {
156 if ( ! function_exists( 'openstation_my_wordpress_woo_entity_icon' ) ) {
157 return $section;
158 }
159 $section = (array) openstation_my_wordpress_woo_entity_icon( $section, $post_type );
160 // Entity-descriptor keys the app has no reader for — `listFields`
161 // is the REST `_fields` allowlist, `listQuery` extra request
162 // params, `tileSize` the old grid's hint. The app's rows carry
163 // their facts directly and its queries are ordered server-side.
164 unset( $section['listFields'], $section['listQuery'], $section['tileSize'] );
165 return $section;
166 }
167
168 /**
169 * Sort options for the Woo-managed sections — a single honest entry,
170 * because their order is decided server-side: orders and coupons walk
171 * their bands, customers rank by band then spend, products shelve
172 * empty stock first. Null leaves a section to the generic options.
173 *
174 * @param array<string,mixed> $section Section descriptor.
175 * @return array<string,array{0:string,1:string,2:string}>|null
176 */
177 function woo_sort_options( array $section ) {
178 if ( ! woo_ready() ) {
179 return null;
180 }
181 if ( woo_section_is( $section, 'wc-orders' ) ) {
182 return array( 'default' => array( __( 'Needs attention first', 'desktop-mode' ), 'date', 'DESC' ) );
183 }
184 if ( woo_section_is( $section, 'wc-customers' ) ) {
185 return array( 'default' => array( __( 'Top spenders first', 'desktop-mode' ), '', '' ) );
186 }
187 if ( woo_section_is( $section, 'cpt-product' ) || woo_section_is( $section, 'cpt-shop_coupon' ) ) {
188 return array( 'default' => array( __( 'Store order', 'desktop-mode' ), 'date', 'DESC' ) );
189 }
190 return null;
191 }
192
193 /**
194 * Root-tile counts for the Woo sections, or null for anyone else's.
195 *
196 * @param array<string,mixed> $section Section descriptor.
197 * @return int|null
198 */
199 function woo_count( array $section ) {
200 if ( ! woo_ready() ) {
201 return null;
202 }
203 if ( woo_section_is( $section, 'wc-orders' ) ) {
204 $counted = wc_get_orders(
205 array(
206 'limit' => 1,
207 'paginate' => true,
208 'return' => 'ids',
209 )
210 );
211 return is_object( $counted ) && isset( $counted->total ) ? (int) $counted->total : 0;
212 }
213 if ( woo_section_is( $section, 'wc-customers' )
214 && function_exists( 'openstation_my_wordpress_woo_customer_plan' ) ) {
215 $plan = openstation_my_wordpress_woo_customer_plan();
216 return (int) ( $plan['customers'] ?? 0 );
217 }
218 return null;
219 }
220
221 /**
222 * One order as an app list row. The title is what a merchant scans
223 * for — number and total — and the REAL status rides `wcStatus` for
224 * the band assigner and the list view, never `status`: the tile
225 * ribbon only speaks draft/pending/private/future, and `wc-processing`
226 * would paint a meaningless ribbon on every order.
227 *
228 * @param \WC_Abstract_Order $order Order.
229 * @return array<string,mixed>
230 */
231 function woo_order_item( $order ) {
232 $total = openstation_my_wordpress_woo_price( $order->get_total(), $order->get_currency() );
233 $name = method_exists( $order, 'get_formatted_billing_full_name' )
234 ? trim( (string) $order->get_formatted_billing_full_name() )
235 : '';
236 $date = $order->get_date_created()
237 ? (string) date_i18n( (string) get_option( 'date_format' ), $order->get_date_created()->getTimestamp() )
238 : '';
239
240 return array(
241 'id' => (int) $order->get_id(),
242 'title' => sprintf(
243 /* translators: 1: order number, 2: formatted order total. */
244 __( '#%1$s · %2$s', 'desktop-mode' ),
245 $order->get_order_number(),
246 $total
247 ),
248 'subtitle' => sprintf(
249 /* translators: 1: customer name, 2: date. */
250 __( '%1$s — %2$s', 'desktop-mode' ),
251 '' !== $name ? $name : __( 'Guest', 'desktop-mode' ),
252 $date
253 ),
254 'status' => 'publish',
255 'excerpt' => '',
256 'thumb' => '',
257 // Refunds and custom order types don't carry an edit URL.
258 'link' => method_exists( $order, 'get_edit_order_url' )
259 ? esc_url_raw( (string) $order->get_edit_order_url() )
260 : '',
261 'mime' => '',
262 'lockedBy' => '',
263 'canEdit' => method_exists( $order, 'get_edit_order_url' ),
264 'canDelete' => false,
265 'wcStatus' => (string) $order->get_status(),
266 // The list view's facts. `format()`, as `WC_DateTime::date()`
267 // labels site time +00:00 and the client reads the offset.
268 'customer' => '' !== $name ? $name : __( 'Guest', 'desktop-mode' ),
269 'date' => $order->get_date_created() ? $order->get_date_created()->format( 'c' ) : '',
270 'modified' => $order->get_date_modified() ? $order->get_date_modified()->format( 'c' ) : '',
271 );
272 }
273
274 /**
275 * One page of the Orders section: the same band walker WP Explorer's
276 * `/woocommerce/orders` route runs — statuses sliced in display order
277 * so a band's rows arrive together and the grouping never reshuffles
278 * under the reader — reshaped as app rows.
279 *
280 * @param State $state State (`query`, `page`).
281 * @return array{items:array[],total:int,pages:int,page:int,perPage:int}
282 */
283 function woo_orders_page( State $state ) {
284 $per_page = PER_PAGE;
285 $page = max( 1, (int) $state->get( 'page' ) );
286 $query = (string) $state->get( 'query' );
287
288 $base = array(
289 'orderby' => 'date',
290 'order' => 'DESC',
291 );
292 if ( '' !== $query ) {
293 $base['s'] = $query;
294 }
295
296 $slices = openstation_my_wordpress_woo_order_band_slices( $base );
297
298 $total = 0;
299 foreach ( $slices as $slice ) {
300 $total += (int) $slice['count'];
301 }
302 $offset = ( $page - 1 ) * $per_page;
303
304 $orders = array();
305 $remaining = $per_page;
306 $cursor = 0;
307 foreach ( $slices as $slice ) {
308 if ( $remaining <= 0 ) {
309 break;
310 }
311 $count = (int) $slice['count'];
312 if ( 0 === $count ) {
313 continue;
314 }
315 if ( $offset >= $cursor + $count ) {
316 $cursor += $count;
317 continue;
318 }
319 $within = max( 0, $offset - $cursor );
320 $take = min( $remaining, $count - $within );
321
322 $batch = wc_get_orders(
323 array_merge(
324 $base,
325 array(
326 'status' => $slice['statuses'],
327 'limit' => $take,
328 'offset' => $within,
329 'paginate' => false,
330 )
331 )
332 );
333 $batch = is_object( $batch ) && isset( $batch->orders ) ? (array) $batch->orders : (array) $batch;
334
335 $orders = array_merge( $orders, $batch );
336 $remaining -= count( $batch );
337 $cursor += $count;
338 $offset = $cursor;
339 }
340
341 $items = array();
342 foreach ( $orders as $maybe_order ) {
343 // A data store may hand back ids rather than objects; and
344 // `WC_Abstract_Order` — not `WC_Order` — is what every order
345 // class actually extends, HPOS overrides and custom order
346 // types included.
347 $order = is_scalar( $maybe_order ) ? wc_get_order( (int) $maybe_order ) : $maybe_order;
348 if ( ! $order instanceof \WC_Abstract_Order ) {
349 continue;
350 }
351 try {
352 $items[] = woo_order_item( $order );
353 } catch ( \Throwable $e ) {
354 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
355 error_log(
356 sprintf(
357 '[openstation] Skipped order %d in the My WordPress app: %s',
358 is_object( $order ) ? (int) $order->get_id() : 0,
359 $e->getMessage()
360 )
361 );
362 }
363 }
364
365 return Os::page( $items, $total, $page, $per_page );
366 }
367
368 /**
369 * One page of the Customers section: the same plan-ordered roster WP
370 * Explorer's `/woocommerce/customers` route serves — band first,
371 * spend inside the band — as app user rows carrying the
372 * `openstation_woo_customer` facts the shared seams read.
373 *
374 * @param Os $os Host handle.
375 * @param State $state State (`query`, `page`).
376 * @return array{items:array[],total:int,pages:int,page:int,perPage:int}
377 */
378 function woo_customers_page( Os $os, State $state ) {
379 $per_page = PER_PAGE;
380 $page = max( 1, (int) $state->get( 'page' ) );
381 $query = trim( (string) $state->get( 'query' ) );
382
383 $plan = openstation_my_wordpress_woo_customer_plan();
384 $capped = ! empty( $plan['capped'] );
385
386 $args = array(
387 'number' => $per_page,
388 'paged' => $page,
389 'fields' => 'all',
390 );
391
392 if ( $capped ) {
393 // Past the cap the plan holds no ids, so hand the ordering
394 // back to the database: newest accounts first, the only
395 // useful order left once bands are off.
396 $args['role'] = 'customer';
397 $args['orderby'] = 'registered';
398 $args['order'] = 'DESC';
399 } else {
400 $ids = array_map( 'intval', (array) ( $plan['ids'] ?? array() ) );
401 if ( array() === $ids ) {
402 return Os::page( array(), 0, $page, $per_page );
403 }
404 $args['include'] = $ids;
405 // `include` + `orderby => include` replays the plan's order
406 // verbatim — the user-query spelling of `post__in`.
407 $args['orderby'] = 'include';
408 }
409
410 if ( '' !== $query ) {
411 $args['search'] = '*' . $query . '*';
412 $args['search_columns'] = array( 'user_login', 'user_email', 'user_nicename', 'display_name' );
413 // A search is a different question from "show me the roster",
414 // and the plan's order would hide matches below the fold.
415 if ( ! $capped ) {
416 $args['orderby'] = 'display_name';
417 $args['order'] = 'ASC';
418 }
419 }
420
421 $users = new \WP_User_Query( $args );
422 $total = (int) $users->get_total();
423
424 $items = array();
425 foreach ( (array) $users->get_results() as $user ) {
426 if ( ! $user instanceof \WP_User ) {
427 continue;
428 }
429 $row = user_row(
430 $user,
431 static function ( $user_id ) use ( $os ) {
432 return $os->can( 'edit_user', $user_id );
433 }
434 );
435 $row += woo_user_extras( (int) $user->ID );
436 $items[] = $row;
437 }
438
439 return Os::page( $items, $total, $page, $per_page );
440 }
441
442 /**
443 * The whole list page for a Woo section, or null for anyone else's.
444 *
445 * @param Os $os Host handle.
446 * @param array<string,mixed> $section Section descriptor.
447 * @param State $state State.
448 * @return array<string,mixed>|null
449 */
450 function woo_list( Os $os, array $section, State $state ) {
451 if ( ! woo_ready() ) {
452 return null;
453 }
454 if ( woo_section_is( $section, 'wc-orders' ) ) {
455 return woo_orders_page( $state );
456 }
457 if ( woo_section_is( $section, 'wc-customers' ) ) {
458 return woo_customers_page( $os, $state );
459 }
460 return null;
461 }
462
463 /**
464 * Band-order the Products and Coupons queries, exactly as the REST
465 * collections are ordered for WP Explorer: the cached plan replayed
466 * as `post__in`, so each band's rows arrive together and the client's
467 * grouping can never disagree with the order they arrive in. A capped
468 * catalogue falls back to stock status, which at least floats empty
469 * shelves to the top; a search is the user asking for relevance and
470 * is left alone.
471 *
472 * @param array<string,mixed> $args `WP_Query` args.
473 * @param array<string,mixed> $section Section descriptor.
474 * @param State $state State.
475 * @return array<string,mixed>
476 */
477 function woo_query_args( array $args, array $section, State $state ) {
478 if ( ! woo_ready() || '' !== (string) $state->get( 'query' ) ) {
479 return $args;
480 }
481
482 if ( woo_section_is( $section, 'cpt-product' )
483 && function_exists( 'openstation_my_wordpress_woo_product_plan' ) ) {
484 $plan = openstation_my_wordpress_woo_product_plan();
485 if ( ! empty( $plan['ids'] ) ) {
486 $args['post__in'] = array_map( 'intval', (array) $plan['ids'] );
487 $args['orderby'] = 'post__in';
488 unset( $args['order'] );
489 } else {
490 $args['meta_key'] = '_stock_status'; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
491 $args['orderby'] = array(
492 'meta_value' => 'DESC',
493 'date' => 'DESC',
494 );
495 }
496 return $args;
497 }
498
499 if ( woo_section_is( $section, 'cpt-shop_coupon' )
500 && function_exists( 'openstation_my_wordpress_woo_coupon_plan' ) ) {
501 $plan = openstation_my_wordpress_woo_coupon_plan();
502 if ( ! empty( $plan['ids'] ) ) {
503 $args['post__in'] = array_map( 'intval', (array) $plan['ids'] );
504 $args['orderby'] = 'post__in';
505 unset( $args['order'] );
506 }
507 }
508
509 return $args;
510 }
511
512 /**
513 * The `openstation_woo` facts for a product or coupon row — the same
514 * payload the REST fields ship on `wp/v2` rows, which is what the
515 * shared band assigner and the stock-ribbon decorator read. Empty for
516 * every other row.
517 *
518 * @param \WP_Post $post Post.
519 * @return array<string,mixed>
520 */
521 function woo_extras( \WP_Post $post ) {
522 if ( ! woo_ready() ) {
523 return array();
524 }
525
526 if ( 'product' === $post->post_type
527 && function_exists( 'openstation_my_wordpress_woo_product_band_id' ) ) {
528 $product = wc_get_product( $post->ID );
529 if ( ! $product ) {
530 return array();
531 }
532 $slugs = wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'slugs' ) );
533 return array(
534 'openstation_woo' => array(
535 // The band this row belongs to, decided by the same
536 // rules that ordered the collection, so the two can't
537 // disagree.
538 'band' => openstation_my_wordpress_woo_product_band_id( $product ),
539 'stockStatus' => $product->get_stock_status(),
540 'stockLevel' => $product->managing_stock() ? (int) $product->get_stock_quantity() : null,
541 'onSale' => $product->is_on_sale(),
542 'categories' => is_wp_error( $slugs ) ? array() : array_values( $slugs ),
543 ),
544 );
545 }
546
547 if ( 'shop_coupon' === $post->post_type
548 && function_exists( 'openstation_my_wordpress_woo_coupon_band_id' ) ) {
549 $coupon = new \WC_Coupon( $post->ID );
550 if ( ! $coupon->get_id() ) {
551 return array();
552 }
553 return array(
554 'openstation_woo' => array(
555 'band' => openstation_my_wordpress_woo_coupon_band_id( $coupon ),
556 ),
557 );
558 }
559
560 return array();
561 }
562
563 /**
564 * The `openstation_woo_customer` facts for a user row — carried on
565 * the Customers section's rows ONLY. Deliberately narrower than WP
566 * Explorer (whose `/wp/v2/users` field puts spend on its Users list
567 * too): in this app the built-in Users folder is about people who
568 * write, and stays money-free — the shared bundle keys its badges
569 * and panel off these facts being present, so leaving them off a row
570 * is how a surface opts out. Gated exactly as the REST field is: a
571 * viewer who can't see orders sees no money.
572 *
573 * @param int $user_id User id.
574 * @return array<string,mixed>
575 */
576 function woo_user_extras( $user_id ) {
577 if ( ! woo_ready()
578 || ! function_exists( 'openstation_my_wordpress_woo_customer_facts' )
579 || ! function_exists( 'openstation_my_wordpress_woo_customers_permission' )
580 || true !== openstation_my_wordpress_woo_customers_permission() ) {
581 return array();
582 }
583 return array(
584 'openstation_woo_customer' => openstation_my_wordpress_woo_customer_facts( (int) $user_id ),
585 );
586 }
587
588 /**
589 * Whether the acting user may act on a Woo row — null passes the
590 * question back to the generic capability checks. Orders need it
591 * because `current_user_can( 'edit_post', $order_id )` means nothing
592 * once HPOS moves orders out of `wp_posts`.
593 *
594 * @param array<string,mixed> $section Section descriptor.
595 * @param int $id Item id.
596 * @param string $verb `edit` | `delete`.
597 * @return bool|null
598 */
599 function woo_allowed( array $section, $id, $verb ) {
600 unset( $id );
601 if ( ! woo_ready() || ! woo_section_is( $section, 'wc-orders' ) ) {
602 return null;
603 }
604 if ( 'edit' !== $verb ) {
605 // Orders are never mutated from here — WP Explorer's Orders
606 // section is read-and-open too.
607 return false;
608 }
609 $orders = get_post_type_object( 'shop_order' );
610 return $orders instanceof \WP_Post_Type
611 && ! empty( $orders->cap->edit_posts )
612 && current_user_can( $orders->cap->edit_posts );
613 }
614
615 /**
616 * The admin URL that edits a Woo row, '' for anyone else's. HPOS and
617 * legacy storage put the order screen in different places, and only
618 * WooCommerce knows which.
619 *
620 * @param array<string,mixed> $section Section descriptor.
621 * @param int $id Item id.
622 * @return string
623 */
624 function woo_edit_url( array $section, $id ) {
625 if ( ! woo_ready() || ! woo_section_is( $section, 'wc-orders' ) ) {
626 return '';
627 }
628 $order = wc_get_order( (int) $id );
629 if ( $order instanceof \WC_Abstract_Order && method_exists( $order, 'get_edit_order_url' ) ) {
630 return (string) $order->get_edit_order_url();
631 }
632 return '';
633 }
634
635 /**
636 * The dossier payload for one order. Deliberately spare: the pane's
637 * substance is the shared bundle's merchant panel, painted into the
638 * `header` slot with the full status, totals, line items and
639 * customer — repeating them as facts would say everything twice.
640 *
641 * @param array<string,mixed> $section Section descriptor.
642 * @param int $id Order id.
643 * @return array<string,mixed>|null Null when the order vanished.
644 */
645 function woo_detail( array $section, $id ) {
646 $order = wc_get_order( (int) $id );
647 if ( ! $order instanceof \WC_Abstract_Order ) {
648 return null;
649 }
650 $item = woo_order_item( $order );
651
652 return array(
653 'kind' => 'post',
654 'id' => (int) $order->get_id(),
655 'title' => (string) $item['title'],
656 'facts' => Os::facts(
657 array(
658 array(
659 __( 'Placed', 'desktop-mode' ),
660 $order->get_date_created()
661 ? (string) date_i18n( (string) get_option( 'date_format' ), $order->get_date_created()->getTimestamp() )
662 : '',
663 ),
664 )
665 ),
666 'canEdit' => ! empty( $item['canEdit'] ) && true === woo_allowed( $section, (int) $id, 'edit' ),
667 'canDelete' => false,
668 );
669 }
670