scorer = $scorer; } /** * Number of products processed per batch (filterable). * * @return int */ public function get_batch_size() { $size = (int) apply_filters( 'arwc_scan_batch_size', self::DEFAULT_BATCH ); return max( 1, min( 200, $size ) ); } /** * Count of published products to be scanned. * * @return int */ public function get_total_products() { $result = wc_get_products( array( 'status' => 'publish', 'type' => array_keys( wc_get_product_types() ), 'limit' => 1, 'paginate' => true, 'return' => 'ids', ) ); return isset( $result->total ) ? (int) $result->total : 0; } /** * Reset stored scan state and start a fresh run. * * @return array The freshly initialized state. */ public function reset() { $state = array( 'version' => ARWC_VERSION, 'total' => $this->get_total_products(), 'processed' => 0, 'sum_score' => 0, 'completed' => false, 'rows' => array(), 'issue_counts' => array(), 'distribution' => array( 'good' => 0, 'fair' => 0, 'poor' => 0, ), ); $this->save_state( $state ); return $state; } /** * Scan one batch (one page) of products and persist the partial results. * * @param int $page 1-based page number. * @return array { * @type int $processed Total products processed so far. * @type int $total Total products to process. * @type bool $done Whether the scan is complete. * @type int $batch Number of products processed in this batch. * } */ public function scan_batch( $page ) { $page = max( 1, (int) $page ); // Page 1 starts a clean run. $state = ( 1 === $page ) ? $this->reset() : $this->get_state(); $per_page = $this->get_batch_size(); $query = wc_get_products( array( 'status' => 'publish', 'type' => array_keys( wc_get_product_types() ), 'limit' => $per_page, 'page' => $page, 'paginate' => true, 'orderby' => 'ID', 'order' => 'ASC', 'return' => 'objects', ) ); $products = isset( $query->products ) ? $query->products : array(); $batch_count = 0; foreach ( $products as $product ) { if ( ! $product instanceof WC_Product ) { continue; } $scored = $this->scorer->score_product( $product ); $grade = ARWC_Scorer::grade( $scored['score'] ); $state['rows'][] = array( 'id' => $product->get_id(), 'name' => $product->get_name(), 'type' => $product->get_type(), 'score' => $scored['score'], 'grade' => $grade, 'issues' => $scored['issues'], ); $state['processed']++; $state['sum_score'] += $scored['score']; $state['distribution'][ $grade ]++; // Aggregate issue frequency for the "top issues" summary. foreach ( $scored['issues'] as $issue ) { $code = $issue['code']; if ( ! isset( $state['issue_counts'][ $code ] ) ) { $state['issue_counts'][ $code ] = array( 'message' => $issue['message'], 'severity' => $issue['severity'], 'count' => 0, ); } $state['issue_counts'][ $code ]['count']++; } $batch_count++; } $total = isset( $query->total ) ? (int) $query->total : $state['total']; $state['total'] = $total; $done = ( $state['processed'] >= $total ) || ( 0 === $batch_count ); $state['completed'] = $done; $this->save_state( $state ); return array( 'processed' => (int) $state['processed'], 'total' => (int) $total, 'done' => (bool) $done, 'batch' => (int) $batch_count, ); } /** * Get the persisted scan state. * * @return array|null Null if no scan has run. */ public function get_state() { $state = get_option( self::RESULTS_OPTION, null ); return is_array( $state ) ? $state : null; } /** * Persist the scan state (non-autoloaded to keep it off every page load). * * @param array $state State to save. * @return void */ protected function save_state( array $state ) { update_option( self::RESULTS_OPTION, $state, false ); } /** * Delete all stored scan data. * * @return void */ public function clear() { delete_option( self::RESULTS_OPTION ); } /** * Build a dashboard-ready summary from the stored state. * * @return array|null Null if there is no completed/partial scan. */ public function get_summary() { $state = $this->get_state(); if ( null === $state ) { return null; } $processed = (int) $state['processed']; $store_score = $processed > 0 ? (int) round( $state['sum_score'] / $processed ) : 0; // Top issues, most frequent first. $issues = $state['issue_counts']; uasort( $issues, static function ( $a, $b ) { return $b['count'] <=> $a['count']; } ); return array( 'store_score' => $store_score, 'grade' => ARWC_Scorer::grade( $store_score ), 'total' => (int) $state['total'], 'processed' => $processed, 'completed' => ! empty( $state['completed'] ), 'distribution' => $state['distribution'], 'top_issues' => array_slice( $issues, 0, 10, true ), 'rows' => $state['rows'], ); } }