licenseManager = $licenseManager; } /** * Initialize analytics hooks */ public function init(): void { // Add submenu under WooCommerce Analytics add_action('admin_menu', [$this, 'addAnalyticsSubmenu'], 99); // Register REST API endpoints for analytics data add_action('rest_api_init', [$this, 'registerRestRoutes']); // Add license stats to WooCommerce Admin data registry add_action('admin_enqueue_scripts', [$this, 'enqueueAnalyticsData']); // Add analytics navigation item (WC Admin) add_filter('woocommerce_navigation_menu_items', [$this, 'addNavigationItem']); // Register WooCommerce Analytics report page add_filter('woocommerce_analytics_report_menu_items', [$this, 'addAnalyticsReportMenuItem']); } /** * Add submenu page under WooCommerce menu */ public function addAnalyticsSubmenu(): void { add_submenu_page( 'woocommerce', __('License Statistics', 'wc-licensed-product'), __('License Statistics', 'wc-licensed-product'), 'manage_woocommerce', 'wc-license-statistics', [$this, 'renderStatisticsPage'] ); } /** * Add navigation item for WC Admin navigation */ public function addNavigationItem(array $items): array { $items[] = [ 'id' => 'wc-license-statistics', 'title' => __('License Statistics', 'wc-licensed-product'), 'parent' => 'woocommerce-analytics', 'path' => '/analytics/license-statistics', ]; return $items; } /** * Add report menu item to WooCommerce Analytics */ public function addAnalyticsReportMenuItem(array $report_pages): array { $report_pages[] = [ 'id' => 'wc-license-statistics', 'title' => __('License Statistics', 'wc-licensed-product'), 'parent' => 'woocommerce-analytics', 'path' => '/analytics/license-statistics', ]; return $report_pages; } /** * Register REST API routes for analytics data */ public function registerRestRoutes(): void { register_rest_route('wc-licensed-product/v1', '/analytics/stats', [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [$this, 'getAnalyticsStats'], 'permission_callback' => function () { return current_user_can('manage_woocommerce'); }, 'args' => [ 'after' => [ 'description' => __('Limit response to stats after a given date.', 'wc-licensed-product'), 'type' => 'string', 'format' => 'date-time', ], 'before' => [ 'description' => __('Limit response to stats before a given date.', 'wc-licensed-product'), 'type' => 'string', 'format' => 'date-time', ], 'interval' => [ 'description' => __('Time interval to aggregate stats.', 'wc-licensed-product'), 'type' => 'string', 'enum' => ['day', 'week', 'month', 'quarter', 'year'], 'default' => 'month', ], ], ]); register_rest_route('wc-licensed-product/v1', '/analytics/products', [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [$this, 'getProductStats'], 'permission_callback' => function () { return current_user_can('manage_woocommerce'); }, 'args' => [ 'per_page' => [ 'description' => __('Maximum number of items to return.', 'wc-licensed-product'), 'type' => 'integer', 'default' => 10, ], 'orderby' => [ 'description' => __('Sort by this field.', 'wc-licensed-product'), 'type' => 'string', 'enum' => ['licenses_count', 'product_name'], 'default' => 'licenses_count', ], 'order' => [ 'description' => __('Order direction.', 'wc-licensed-product'), 'type' => 'string', 'enum' => ['asc', 'desc'], 'default' => 'desc', ], ], ]); } /** * Get analytics stats via REST API */ public function getAnalyticsStats(\WP_REST_Request $request): \WP_REST_Response { $stats = $this->licenseManager->getStatistics(); $interval = $request->get_param('interval') ?: 'month'; // Get time-series data based on interval $timeSeriesData = $this->getTimeSeriesData($interval, $request->get_param('after'), $request->get_param('before')); return new \WP_REST_Response([ 'totals' => [ 'total_licenses' => $stats['total'], 'active_licenses' => $stats['by_status']['active'] ?? 0, 'inactive_licenses' => $stats['by_status']['inactive'] ?? 0, 'expired_licenses' => $stats['by_status']['expired'] ?? 0, 'revoked_licenses' => $stats['by_status']['revoked'] ?? 0, 'lifetime_licenses' => $stats['lifetime'] ?? 0, 'expiring_soon' => $stats['expiring_soon'] ?? 0, ], 'intervals' => $timeSeriesData, ], 200); } /** * Get product statistics via REST API */ public function getProductStats(\WP_REST_Request $request): \WP_REST_Response { $stats = $this->licenseManager->getStatistics(); $perPage = $request->get_param('per_page') ?: 10; $productStats = array_slice($stats['by_product'] ?? [], 0, $perPage); return new \WP_REST_Response([ 'products' => $productStats, ], 200); } /** * Get time-series data for the specified interval */ private function getTimeSeriesData(string $interval, ?string $after = null, ?string $before = null): array { global $wpdb; $tableName = $wpdb->prefix . 'wc_licensed_product_licenses'; // Set default date range $endDate = $before ? new \DateTimeImmutable($before) : new \DateTimeImmutable(); $startDate = $after ? new \DateTimeImmutable($after) : $endDate->modify('-12 months'); // Build date format based on interval switch ($interval) { case 'day': $dateFormat = '%Y-%m-%d'; $phpFormat = 'Y-m-d'; break; case 'week': $dateFormat = '%Y-%u'; $phpFormat = 'Y-W'; break; case 'quarter': $dateFormat = "CONCAT(YEAR(created_at), '-Q', QUARTER(created_at))"; $phpFormat = 'Y-\QQ'; break; case 'year': $dateFormat = '%Y'; $phpFormat = 'Y'; break; case 'month': default: $dateFormat = '%Y-%m'; $phpFormat = 'Y-m'; break; } // Special handling for quarter since it's not a simple DATE_FORMAT if ($interval === 'quarter') { $sql = $wpdb->prepare( "SELECT {$dateFormat} as period, COUNT(*) as count FROM {$tableName} WHERE created_at >= %s AND created_at <= %s GROUP BY period ORDER BY period ASC", $startDate->format('Y-m-d 00:00:00'), $endDate->format('Y-m-d 23:59:59') ); } else { $sql = $wpdb->prepare( "SELECT DATE_FORMAT(created_at, %s) as period, COUNT(*) as count FROM {$tableName} WHERE created_at >= %s AND created_at <= %s GROUP BY period ORDER BY period ASC", $dateFormat, $startDate->format('Y-m-d 00:00:00'), $endDate->format('Y-m-d 23:59:59') ); } $results = $wpdb->get_results($sql, ARRAY_A); $data = []; foreach ($results as $row) { $data[] = [ 'interval' => $row['period'], 'subtotals' => [ 'licenses_count' => (int) $row['count'], ], ]; } return $data; } /** * Enqueue license analytics data for WC Admin */ public function enqueueAnalyticsData(): void { if (!function_exists('wc_admin_get_feature_config')) { return; } $screen = get_current_screen(); if (!$screen || strpos($screen->id, 'woocommerce') === false) { return; } $stats = $this->licenseManager->getStatistics(); wp_localize_script('wc-admin-app', 'wcLicenseStats', [ 'total' => $stats['total'], 'active' => $stats['by_status']['active'] ?? 0, 'inactive' => $stats['by_status']['inactive'] ?? 0, 'expired' => $stats['by_status']['expired'] ?? 0, 'revoked' => $stats['by_status']['revoked'] ?? 0, 'lifetime' => $stats['lifetime'] ?? 0, 'expiringSoon' => $stats['expiring_soon'] ?? 0, 'endpoints' => [ 'stats' => rest_url('wc-licensed-product/v1/analytics/stats'), 'products' => rest_url('wc-licensed-product/v1/analytics/products'), ], ]); } /** * Render the statistics page */ public function renderStatisticsPage(): void { $stats = $this->licenseManager->getStatistics(); // Render using Twig if available $plugin = \Jeremias\WcLicensedProduct\Plugin::getInstance(); $twig = $plugin->getTwig(); if ($twig) { try { echo $twig->render('admin/statistics.html.twig', [ 'stats' => $stats, 'admin_url' => admin_url('admin.php'), 'rest_url' => rest_url('wc-licensed-product/v1/analytics/'), ]); return; } catch (\Twig\Error\LoaderError $e) { // Template not found, use fallback } } // Fallback PHP rendering $this->renderStatisticsPageFallback($stats); } /** * Fallback rendering for statistics page */ private function renderStatisticsPageFallback(array $stats): void { ?>