scorer = $scorer; $this->scanner = $scanner; add_action( 'add_meta_boxes', array( $this, 'register' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); // Keep this box pinned to the top of the product sidebar. `high` priority // only wins for users with no saved meta-box order; once a merchant has // edited products, WordPress persists an order and appends newly-added boxes // to the BOTTOM, ignoring priority. This filter re-pins ours to the top of // the "side" column so the most valuable at-a-glance signal stays visible. add_filter( 'get_user_option_meta-box-order_product', array( $this, 'pin_to_top' ) ); // Keep the store-wide average fresh as products are edited, so the header + // admin-bar score reflect the change on the next load rather than lagging // behind the per-product score the merchant just watched update. add_action( 'woocommerce_update_product', array( $this, 'sync_store_score' ) ); } /** * Re-score a saved product into the stored scan so the store average stays * accurate. No-op when no audit has run yet (we never fabricate scan data). * * @param int $product_id Product ID. * @return void */ public function sync_store_score( $product_id ) { if ( null === $this->scanner->get_state() ) { return; } $this->scanner->update_product( (int) $product_id ); } /** * Pin our box to the top of the product sidebar, regardless of any saved * meta-box order, by moving its id to the front of the "side" column. * * A merchant can still collapse it; we only control ordering. Runs on the * cached user-option read WordPress uses to lay out meta boxes. * * @param mixed $order Saved order (array of context => comma-separated ids), or false. * @return mixed */ public function pin_to_top( $order ) { if ( ! is_array( $order ) ) { // No saved order: `high` priority already puts us at the top. return $order; } $id = self::BOX_ID; // Strip our id from every column so it can't appear twice. foreach ( $order as $context => $ids ) { $list = array_filter( explode( ',', (string) $ids ), static function ( $one ) use ( $id ) { return '' !== $one && $one !== $id; } ); $order[ $context ] = implode( ',', $list ); } // Prepend to the side column. $side = ( isset( $order['side'] ) && '' !== $order['side'] ) ? (string) $order['side'] : ''; $order['side'] = ( '' === $side ) ? $id : $id . ',' . $side; return $order; } /** * Register the meta box on the product edit screen. High priority so it sits * near the top of the sidebar (this is the most valuable at-a-glance signal), * not buried below other plugins' boxes. * * @return void */ public function register() { add_meta_box( self::BOX_ID, __( 'Agent Readiness', 'agentictrack-agent-readiness-for-woocommerce' ), array( $this, 'render' ), 'product', 'side', 'high' ); } /** * Enqueue the small self-contained meta-box stylesheet on product edit screens. * * The main admin.css is scoped to `.arwc-wrap` (and full-bleeds the page), so * the meta box — which lives outside that wrapper — gets its own tiny sheet. * * @param string $hook_suffix Current admin page hook. * @return void */ public function enqueue_assets( $hook_suffix ) { if ( 'post.php' !== $hook_suffix && 'post-new.php' !== $hook_suffix ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || 'product' !== $screen->post_type ) { return; } wp_enqueue_style( 'arwc-metabox', ARWC_PLUGIN_URL . 'admin/css/metabox.css', array(), ARWC_VERSION ); // Inline quick-fixes (featured image + identifiers) reuse the Fix AJAX // endpoints and the media picker, so merchants resolve the common issues // without leaving the product editor. wp_enqueue_media(); wp_enqueue_script( 'arwc-metabox', ARWC_PLUGIN_URL . 'admin/js/metabox.js', array( 'jquery' ), ARWC_VERSION, true ); wp_localize_script( 'arwc-metabox', 'ARWC_MB', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( ARWC_Admin::FIX_NONCE_ACTION ), 'i18n' => array( 'saving' => __( 'Saving…', 'agentictrack-agent-readiness-for-woocommerce' ), 'saved' => __( 'Saved ✓', 'agentictrack-agent-readiness-for-woocommerce' ), 'error' => __( 'Something went wrong. Please try again.', 'agentictrack-agent-readiness-for-woocommerce' ), 'enterValue' => __( 'Enter a value first.', 'agentictrack-agent-readiness-for-woocommerce' ), 'chooseImage' => __( 'Choose a featured image', 'agentictrack-agent-readiness-for-woocommerce' ), 'useImage' => __( 'Use this image', 'agentictrack-agent-readiness-for-woocommerce' ), 'imageSet' => __( 'Image set ✓', 'agentictrack-agent-readiness-for-woocommerce' ), ), ) ); } /** * Human label for a severity code (mirrors the audit's CRITICAL/WARNING/INFO * tags so the two surfaces read the same). Inlined here so the box needs no * view-helper include. * * @param string $severity Severity code. * @return string */ protected function severity_label( $severity ) { switch ( $severity ) { case 'critical': return __( 'Critical', 'agentictrack-agent-readiness-for-woocommerce' ); case 'info': return __( 'Info', 'agentictrack-agent-readiness-for-woocommerce' ); case 'warning': default: return __( 'Warning', 'agentictrack-agent-readiness-for-woocommerce' ); } } /** * Render the meta box contents. * * @param WP_Post $post The product post being edited. * @return void */ public function render( $post ) { $product = wc_get_product( $post->ID ); if ( ! $product instanceof WC_Product ) { echo '

' . esc_html__( 'Save this product to see its agent-readiness score.', 'agentictrack-agent-readiness-for-woocommerce' ) . '

'; return; } $scored = $this->scorer->score_product( $product ); $score = (int) $scored['score']; $grade = ARWC_Scorer::grade( $score ); $issues = isset( $scored['issues'] ) && is_array( $scored['issues'] ) ? $scored['issues'] : array(); switch ( $grade ) { case 'good': $grade_label = __( 'Good', 'agentictrack-agent-readiness-for-woocommerce' ); break; case 'fair': $grade_label = __( 'Fair', 'agentictrack-agent-readiness-for-woocommerce' ); break; default: $grade_label = __( 'Poor', 'agentictrack-agent-readiness-for-woocommerce' ); break; } // Arc geometry mirrors the dashboard ring (pathLength=100). $arc = 100 - max( 0, min( 100, $score ) ); // Which common issues can be quick-fixed right here in the editor. $sku = (string) $product->get_sku(); $gtin = is_callable( array( $product, 'get_global_unique_id' ) ) ? (string) $product->get_global_unique_id() : ''; if ( '' === $gtin ) { $gtin = (string) $product->get_meta( '_global_unique_id', true ); } $need_image = $product->get_image_id() <= 0; $need_sku = '' === $sku; $need_gtin = '' === $gtin; $has_quick = $need_image || $need_sku || $need_gtin; $fix_url = add_query_arg( array( 'page' => ARWC_Admin::FIX_PAGE_SLUG, 'arwc_focus' => (int) $product->get_id(), ), admin_url( 'admin.php' ) ); ?>

4 ) : ?> →