*/ public static function abilities(): array { return [ new Ability( 'sarcio/woo-orders-recent', __('Look at recent orders', 'sarcio'), Ability::READ, 'Recent WooCommerce orders, newest first: id, number, status, total, date, the customer’s first name and what was bought. Filter by status.', [ 'properties' => [ 'limit' => ['maximum' => 50, 'minimum' => 1, 'type' => 'integer'], 'status' => ['enum' => self::STATUSES, 'type' => 'string'], ], 'required' => [], 'type' => 'object', ], static fn (array $args): bool => self::canSee(), static fn (array $args): array => self::recent($args), ), new Ability( 'sarcio/woo-sales-summary', __('Look at what sold', 'sarcio'), Ability::READ, 'Sales over the last days (7 by default, up to 90): orders, revenue, average order and the best-selling products, from paid orders.', ['properties' => ['days' => ['maximum' => 90, 'minimum' => 1, 'type' => 'integer']], 'required' => [], 'type' => 'object'], static fn (array $args): bool => self::canSee(), static fn (array $args): array => self::summary((int) ($args['days'] ?? 7)), ), new Ability( 'sarcio/woo-order-status', __('Move an order along', 'sarcio'), Ability::WRITE, 'Set an order’s status (processing, on-hold, completed, cancelled) with an optional note the customer is emailed. Made when the person confirms; undo puts the previous status back. Refunds are not done here.', [ 'properties' => [ 'id' => ['minimum' => 1, 'type' => 'integer'], 'note' => ['maxLength' => 1000, 'type' => 'string'], 'status' => ['enum' => ['processing', 'on-hold', 'completed', 'cancelled'], 'type' => 'string'], ], 'required' => ['id', 'status'], 'type' => 'object', ], static fn (array $args): bool => self::canSee(), static fn (array $args): array => self::setStatus($args), null, static fn (array $before, array $after): array => self::restore($before), ), ]; } private static function canSee(): bool { return function_exists('wc_get_orders') && (current_user_can('manage_woocommerce') || current_user_can('edit_shop_orders')); } public static function describe(object $order): array { $items = []; foreach ((array) $order->get_items() as $item) { $items[] = ['name' => (string) $item->get_name(), 'quantity' => (int) $item->get_quantity()]; } return [ 'customer' => (string) $order->get_billing_first_name(), 'date' => $order->get_date_created() ? $order->get_date_created()->date('Y-m-d H:i') : '', 'id' => (int) $order->get_id(), 'items' => $items, 'number' => (string) $order->get_order_number(), 'status' => (string) $order->get_status(), 'total' => (string) $order->get_total(), ]; } /** @param array $args */ private static function recent(array $args): array { if (!function_exists('wc_get_orders')) { return ['error' => __('WooCommerce is not on this site.', 'sarcio')]; } $query = ['limit' => (int) ($args['limit'] ?? 10), 'orderby' => 'date', 'order' => 'DESC']; if (($args['status'] ?? '') !== '') { $query['status'] = (string) $args['status']; } $orders = wc_get_orders($query); return ['orders' => array_map([self::class, 'describe'], is_array($orders) ? array_values(array_filter($orders, 'is_object')) : [])]; } private static function summary(int $days): array { if (!function_exists('wc_get_orders')) { return ['error' => __('WooCommerce is not on this site.', 'sarcio')]; } $orders = wc_get_orders(['date_created' => '>' . (time() - $days * 86400), 'limit' => 500, 'status' => ['processing', 'completed', 'on-hold']]); $count = 0; $revenue = 0.0; $products = []; foreach (is_array($orders) ? $orders : [] as $order) { $count++; $revenue += (float) $order->get_total(); foreach ((array) $order->get_items() as $item) { $name = (string) $item->get_name(); $products[$name] = ($products[$name] ?? 0) + (int) $item->get_quantity(); } } arsort($products); return [ 'averageOrder' => $count > 0 ? round($revenue / $count, 2) : 0, 'currency' => function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : '', 'days' => $days, 'orders' => $count, 'revenue' => round($revenue, 2), 'topProducts' => array_slice(array_map(static fn (string $name, int $quantity): array => ['name' => $name, 'quantity' => $quantity], array_keys($products), $products), 0, 10), ]; } /** @param array $args */ private static function setStatus(array $args): array { $order = function_exists('wc_get_order') ? wc_get_order((int) $args['id']) : null; if (!is_object($order)) { return ['error' => __('There is no order with that id.', 'sarcio')]; } $before = (string) $order->get_status(); $status = (string) $args['status']; if ($before === $status) { return ['error' => __('The order already has that status.', 'sarcio')]; } $note = sanitize_textarea_field((string) ($args['note'] ?? '')); $order->update_status($status, $note !== '' ? $note : '', true); if ($note !== '') { $order->add_order_note($note, true); } return [ 'journal' => [ 'after' => ['id' => (int) $order->get_id(), 'status' => $status], 'before' => ['id' => (int) $order->get_id(), 'status' => $before], 'summary' => sprintf( /* translators: 1: the order number, 2: the new status. */ __('Moved order #%1$s to %2$s', 'sarcio'), (string) $order->get_order_number(), $status, ), 'target' => 'order:' . $order->get_id(), ], 'order' => self::describe($order), ]; } /** @param array $before */ private static function restore(array $before): array { $order = function_exists('wc_get_order') ? wc_get_order((int) ($before['id'] ?? 0)) : null; if (!is_object($order)) { return ['error' => __('That order no longer exists.', 'sarcio')]; } $order->update_status((string) ($before['status'] ?? 'processing'), __('Put back by Sarcio undo.', 'sarcio'), true); return ['restored' => true]; } }