7f78669c5b
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q8t9GEyyLZbUQu26na1JQg
191 lines
11 KiB
PHP
191 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Batch 20 — WooCommerce deep (Pro-equivalent, net-new).
|
|
*
|
|
* Completes WooCommerce coverage beyond the baseline product/order/coupon/
|
|
* variation/customer set: product categories, payment gateways, shipping zones,
|
|
* tax rates, settings groups, order notes, refunds, a sales report, and product
|
|
* reviews. Guarded by the WooCommerce class — inert unless Woo is active.
|
|
* GPL-2.0-or-later. See NOTICE.md.
|
|
*/
|
|
|
|
namespace AskTamMCP\Extensions;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class Batch20 {
|
|
|
|
public static function register($registry): void {
|
|
if (!class_exists('WooCommerce')) return;
|
|
|
|
$def = function (string $name, string $desc, array $props, array $required, string $scope, string $method) {
|
|
$schema = ['type' => 'object', 'properties' => $props];
|
|
if ($required) $schema['required'] = $required;
|
|
return ['name' => $name, 'description' => $desc, 'scope' => $scope,
|
|
'handler' => 'AskTamMCP\\Extensions\\Batch20::' . $method, 'inputSchema' => $schema];
|
|
};
|
|
|
|
$registry->register($def('wc_list_product_categories', 'List WooCommerce product categories (id, name, slug, parent, count).',
|
|
['parent' => ['type' => 'integer'], 'search' => ['type' => 'string'], 'per_page' => ['type' => 'integer', 'default' => 100]], [], 'read', 'listCategories'));
|
|
|
|
$registry->register($def('wc_create_product_category', 'Create a WooCommerce product category.',
|
|
['name' => ['type' => 'string'], 'slug' => ['type' => 'string'], 'parent' => ['type' => 'integer'], 'description' => ['type' => 'string']], ['name'], 'write', 'createCategory'));
|
|
|
|
$registry->register($def('wc_list_payment_gateways', 'List WooCommerce payment gateways and whether each is enabled.',
|
|
[], [], 'read', 'paymentGateways'));
|
|
|
|
$registry->register($def('wc_list_shipping_zones', 'List WooCommerce shipping zones with their regions and methods.',
|
|
[], [], 'read', 'shippingZones'));
|
|
|
|
$registry->register($def('wc_list_tax_rates', 'List WooCommerce tax rates (country, state, rate, name, class).',
|
|
['class' => ['type' => 'string'], 'limit' => ['type' => 'integer', 'default' => 100]], [], 'read', 'taxRates'));
|
|
|
|
$registry->register($def('wc_get_settings', 'Read a group of WooCommerce settings. group = general | products | tax | account | email. Returns the relevant woocommerce_* options.',
|
|
['group' => ['type' => 'string', 'enum' => ['general', 'products', 'tax', 'account', 'email']]], ['group'], 'read', 'getSettings'));
|
|
|
|
$registry->register($def('wc_add_order_note', 'Add a note to a WooCommerce order. customer_note=true emails it to the customer.',
|
|
['order_id' => ['type' => 'integer'], 'note' => ['type' => 'string'], 'customer_note' => ['type' => 'boolean']], ['order_id', 'note'], 'write', 'addOrderNote'));
|
|
|
|
$registry->register($def('wc_create_refund', 'Create a refund against a WooCommerce order. amount optional (full refund if omitted). reason optional.',
|
|
['order_id' => ['type' => 'integer'], 'amount' => ['type' => 'number'], 'reason' => ['type' => 'string'], 'restock' => ['type' => 'boolean']], ['order_id'], 'write', 'createRefund'));
|
|
|
|
$registry->register($def('wc_sales_report', 'Sales report over a date range: order count, gross total, and item count. Dates are YYYY-MM-DD (default last 30 days).',
|
|
['date_from' => ['type' => 'string'], 'date_to' => ['type' => 'string'], 'statuses' => ['type' => 'array']], [], 'read', 'salesReport'));
|
|
|
|
$registry->register($def('wc_list_product_reviews', 'List WooCommerce product reviews (rating, author, excerpt, product). Optionally filter by product_id.',
|
|
['product_id' => ['type' => 'integer'], 'per_page' => ['type' => 'integer', 'default' => 50]], [], 'read', 'listReviews'));
|
|
}
|
|
|
|
public static function listCategories(array $args): array {
|
|
$terms = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false,
|
|
'parent' => isset($args['parent']) ? (int) $args['parent'] : '',
|
|
'search' => $args['search'] ?? '', 'number' => (int) ($args['per_page'] ?? 100)]);
|
|
$out = [];
|
|
foreach ((array) $terms as $t) {
|
|
if (!is_object($t)) continue;
|
|
$out[] = ['id' => $t->term_id, 'name' => $t->name, 'slug' => $t->slug, 'parent' => $t->parent, 'count' => $t->count];
|
|
}
|
|
return ['count' => count($out), 'categories' => $out];
|
|
}
|
|
|
|
public static function createCategory(array $args): array {
|
|
$name = trim((string) ($args['name'] ?? ''));
|
|
if ($name === '') throw new \RuntimeException('name required.');
|
|
$res = wp_insert_term($name, 'product_cat', [
|
|
'slug' => $args['slug'] ?? '', 'parent' => (int) ($args['parent'] ?? 0), 'description' => $args['description'] ?? '',
|
|
]);
|
|
if (is_wp_error($res)) throw new \RuntimeException('Create failed: ' . $res->get_error_message());
|
|
return ['id' => $res['term_id'], 'name' => $name];
|
|
}
|
|
|
|
public static function paymentGateways(array $args): array {
|
|
$gw = WC()->payment_gateways() ? WC()->payment_gateways()->payment_gateways() : [];
|
|
$out = [];
|
|
foreach ((array) $gw as $id => $g) {
|
|
$out[] = ['id' => $id, 'title' => $g->get_title(), 'enabled' => ($g->enabled === 'yes'), 'method_title' => $g->get_method_title()];
|
|
}
|
|
return ['count' => count($out), 'gateways' => $out];
|
|
}
|
|
|
|
public static function shippingZones(array $args): array {
|
|
if (!class_exists('WC_Shipping_Zones')) throw new \RuntimeException('Shipping zones API unavailable.');
|
|
$zones = \WC_Shipping_Zones::get_zones();
|
|
$out = [];
|
|
foreach ((array) $zones as $z) {
|
|
$methods = [];
|
|
foreach ((array) ($z['shipping_methods'] ?? []) as $m) {
|
|
$methods[] = ['id' => $m->id, 'title' => $m->get_title(), 'enabled' => ($m->is_enabled())];
|
|
}
|
|
$out[] = ['id' => $z['id'] ?? $z['zone_id'] ?? null, 'name' => $z['zone_name'] ?? '', 'regions' => $z['formatted_zone_location'] ?? '', 'methods' => $methods];
|
|
}
|
|
return ['count' => count($out), 'zones' => $out];
|
|
}
|
|
|
|
public static function taxRates(array $args): array {
|
|
global $wpdb;
|
|
$t = $wpdb->prefix . 'woocommerce_tax_rates';
|
|
$limit = max(1, (int) ($args['limit'] ?? 100));
|
|
$where = '';
|
|
if (!empty($args['class'])) $where = $wpdb->prepare('WHERE tax_rate_class = %s', (string) $args['class']);
|
|
$rows = $wpdb->get_results("SELECT tax_rate_country, tax_rate_state, tax_rate, tax_rate_name, tax_rate_class FROM {$t} {$where} ORDER BY tax_rate_order LIMIT {$limit}", ARRAY_A);
|
|
return ['count' => count((array) $rows), 'rates' => array_map(fn($r) => [
|
|
'country' => $r['tax_rate_country'], 'state' => $r['tax_rate_state'], 'rate' => $r['tax_rate'],
|
|
'name' => $r['tax_rate_name'], 'class' => $r['tax_rate_class'] ?: 'standard',
|
|
], (array) $rows)];
|
|
}
|
|
|
|
public static function getSettings(array $args): array {
|
|
$group = (string) ($args['group'] ?? 'general');
|
|
$keys = [
|
|
'general' => ['woocommerce_store_address', 'woocommerce_store_city', 'woocommerce_default_country', 'woocommerce_currency', 'woocommerce_currency_pos', 'woocommerce_price_decimal_sep', 'woocommerce_price_num_decimals'],
|
|
'products' => ['woocommerce_weight_unit', 'woocommerce_dimension_unit', 'woocommerce_manage_stock', 'woocommerce_notify_low_stock_amount', 'woocommerce_hide_out_of_stock_items', 'woocommerce_cart_redirect_after_add'],
|
|
'tax' => ['woocommerce_calc_taxes', 'woocommerce_prices_include_tax', 'woocommerce_tax_based_on', 'woocommerce_tax_display_shop', 'woocommerce_tax_display_cart'],
|
|
'account' => ['woocommerce_enable_guest_checkout', 'woocommerce_enable_checkout_login_reminder', 'woocommerce_enable_myaccount_registration'],
|
|
'email' => ['woocommerce_email_from_name', 'woocommerce_email_from_address', 'woocommerce_email_header_image'],
|
|
];
|
|
if (!isset($keys[$group])) throw new \RuntimeException('Unknown group.');
|
|
$out = [];
|
|
foreach ($keys[$group] as $k) $out[$k] = get_option($k);
|
|
return ['group' => $group, 'settings' => $out];
|
|
}
|
|
|
|
public static function addOrderNote(array $args): array {
|
|
$id = (int) ($args['order_id'] ?? 0);
|
|
$order = wc_get_order($id);
|
|
if (!$order) throw new \RuntimeException('Order not found: ' . $id);
|
|
$note = (string) ($args['note'] ?? '');
|
|
if ($note === '') throw new \RuntimeException('note required.');
|
|
$noteId = $order->add_order_note($note, !empty($args['customer_note']) ? 1 : 0, true);
|
|
return ['order_id' => $id, 'note_id' => $noteId, 'customer_note' => !empty($args['customer_note'])];
|
|
}
|
|
|
|
public static function createRefund(array $args): array {
|
|
$id = (int) ($args['order_id'] ?? 0);
|
|
$order = wc_get_order($id);
|
|
if (!$order) throw new \RuntimeException('Order not found: ' . $id);
|
|
$amount = isset($args['amount']) ? (float) $args['amount'] : (float) $order->get_total();
|
|
$refund = wc_create_refund([
|
|
'order_id' => $id,
|
|
'amount' => $amount,
|
|
'reason' => (string) ($args['reason'] ?? ''),
|
|
'restock_items' => !empty($args['restock']),
|
|
]);
|
|
if (is_wp_error($refund)) throw new \RuntimeException('Refund failed: ' . $refund->get_error_message());
|
|
return ['order_id' => $id, 'refund_id' => $refund->get_id(), 'amount' => $amount];
|
|
}
|
|
|
|
public static function salesReport(array $args): array {
|
|
$to = !empty($args['date_to']) ? (string) $args['date_to'] : date('Y-m-d');
|
|
$from = !empty($args['date_from']) ? (string) $args['date_from'] : date('Y-m-d', strtotime('-30 days'));
|
|
$statuses = !empty($args['statuses']) && is_array($args['statuses']) ? $args['statuses'] : ['wc-completed', 'wc-processing'];
|
|
$statuses = array_map(fn($s) => strpos($s, 'wc-') === 0 ? $s : 'wc-' . $s, $statuses);
|
|
$orders = wc_get_orders([
|
|
'limit' => -1, 'status' => $statuses,
|
|
'date_created' => $from . '...' . $to, 'return' => 'objects',
|
|
]);
|
|
$gross = 0.0; $items = 0; $n = 0;
|
|
foreach ((array) $orders as $o) { $gross += (float) $o->get_total(); $items += (int) $o->get_item_count(); $n++; }
|
|
return ['date_from' => $from, 'date_to' => $to, 'statuses' => $statuses,
|
|
'orders' => $n, 'gross_total' => round($gross, 2), 'currency' => get_woocommerce_currency(), 'items_sold' => $items];
|
|
}
|
|
|
|
public static function listReviews(array $args): array {
|
|
$cargs = ['post_type' => 'product', 'status' => 'approve', 'number' => (int) ($args['per_page'] ?? 50)];
|
|
if (!empty($args['product_id'])) $cargs['post_id'] = (int) $args['product_id'];
|
|
$comments = get_comments($cargs);
|
|
$out = [];
|
|
foreach ((array) $comments as $c) {
|
|
$out[] = [
|
|
'id' => (int) $c->comment_ID,
|
|
'product_id' => (int) $c->comment_post_ID,
|
|
'product' => get_the_title((int) $c->comment_post_ID),
|
|
'author' => $c->comment_author,
|
|
'rating' => (int) get_comment_meta($c->comment_ID, 'rating', true),
|
|
'excerpt' => mb_substr(wp_strip_all_tags($c->comment_content), 0, 200),
|
|
'date' => $c->comment_date,
|
|
];
|
|
}
|
|
return ['count' => count($out), 'reviews' => $out];
|
|
}
|
|
}
|