isset( $part['min'] ) ? (int) $part['min'] : 1, 'max' => isset( $part['max'] ) ? (int) $part['max'] : 1, ); }; foreach ( array_slice( array_values( $parts ), 0, self::MAX_TEMPLATE_PARTS ) as $part ) { if ( ! is_array( $part ) || ! isset( $part['kind'] ) || ! is_string( $part['kind'] ) ) { continue; } switch ( $part['kind'] ) { case 'specs': $out[] = array_merge( array( 'kind' => 'specs', 'style' => isset( $part['style'] ) && 'table' === $part['style'] ? 'table' : 'label_colon' ), $counts( $part ) ); break; case 'paragraphs': $out[] = array_merge( array( 'kind' => 'paragraphs' ), $counts( $part ) ); break; case 'bullets': $out[] = array_merge( array( 'kind' => 'bullets', 'ordered' => ! empty( $part['ordered'] ) ), $counts( $part ) ); break; case 'heading': $heading = array( 'kind' => 'heading', 'level' => isset( $part['level'] ) && 2 === (int) $part['level'] ? 2 : 3 ); $text = isset( $part['text'] ) && is_string( $part['text'] ) ? trim( sanitize_text_field( wp_strip_all_tags( $part['text'] ) ) ) : ''; if ( '' !== $text ) { $heading['text'] = function_exists( 'mb_substr' ) ? mb_substr( $text, 0, 80 ) : substr( $text, 0, 80 ); } $out[] = $heading; break; case 'qa': $style = isset( $part['style'] ) && in_array( $part['style'], array( 'bold_question', 'heading_question', 'q_a_lines' ), true ) ? $part['style'] : 'bold_question'; $out[] = array_merge( array( 'kind' => 'qa', 'style' => $style ), $counts( $part ) ); break; case 'kept': $out[] = array( 'kind' => 'kept' ); break; } } return $out; } /** * Every field and its format, as the platform lists them. */ public function list_formats() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->require_manage(); $this->require_connected(); $result = $this->plugin->api()->field_formats( $this->plugin->settings()->get_store_id() ); if ( is_wp_error( $result ) ) { wp_send_json_error( $this->error_payload( $result ) ); } wp_send_json_success( array( 'enabled' => ! empty( $result['enabled'] ), 'fields' => isset( $result['fields'] ) && is_array( $result['fields'] ) ? $result['fields'] : array(), ) ); } /** * One field in full: its labels per product type and the store's examples. */ public function detail() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->require_manage(); $this->require_connected(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the target is sanitized in target_from_request(). $target = self::target_from_request( wp_unslash( $_POST ) ); if ( '' === $target ) { wp_send_json_error( array( 'message' => __( 'That field was not recognised.', 'agentic-commerce-engine' ) ) ); } $result = $this->plugin->api()->field_formats( $this->plugin->settings()->get_store_id(), $target ); if ( is_wp_error( $result ) ) { wp_send_json_error( $this->error_payload( $result ) ); } wp_send_json_success( array( 'field' => isset( $result['field'] ) && is_array( $result['field'] ) ? $result['field'] : array() ) ); } /** * Save one field's choice. */ public function update_format() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->require_manage(); $this->require_connected(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the target is sanitized and the JSON patch rebuilt from known keys below. $post = wp_unslash( $_POST ); $target = self::target_from_request( $post ); $patch = self::patch_from_request( $post ); if ( '' === $target || empty( $patch ) ) { wp_send_json_error( array( 'message' => __( 'That change was not recognised.', 'agentic-commerce-engine' ) ) ); } $result = $this->plugin->api()->update_field_format( $this->plugin->settings()->get_store_id(), array_merge( array( 'target' => $target ), $patch ) ); if ( is_wp_error( $result ) ) { wp_send_json_error( $this->error_payload( $result ) ); } // Whether "Your custom fields" is offered in the enrichment picker // depends on having a field to fill: fetch the catalog fresh. if ( isset( $patch['fillEmpty'] ) ) { $this->plugin->articles()->types( true ); } wp_send_json_success( array( 'field' => isset( $result['field'] ) && is_array( $result['field'] ) ? $result['field'] : array() ) ); } /** * A product's current value laid out in a format. Free: nothing is written. */ public function preview() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->require_manage(); $this->require_connected(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the target is sanitized and the JSON patch rebuilt from known keys below. $post = wp_unslash( $_POST ); $target = self::target_from_request( $post ); if ( '' === $target ) { wp_send_json_error( array( 'message' => __( 'That field was not recognised.', 'agentic-commerce-engine' ) ) ); } $result = $this->plugin->api()->preview_field_format( $this->plugin->settings()->get_store_id(), array_merge( array( 'target' => $target ), self::patch_from_request( $post ) ) ); if ( is_wp_error( $result ) ) { wp_send_json_error( $this->error_payload( $result ) ); } // The layouts are drawn as HTML on the card: cleaned here, not trusted. $before = isset( $result['before']['html'] ) && is_string( $result['before']['html'] ) ? wp_kses_post( $result['before']['html'] ) : ''; $after = isset( $result['after'] ) && is_array( $result['after'] ) ? $result['after'] : null; wp_send_json_success( array( 'product' => array( 'title' => isset( $result['product']['title'] ) && is_string( $result['product']['title'] ) ? sanitize_text_field( $result['product']['title'] ) : '', ), 'parts' => isset( $result['parts'] ) && is_array( $result['parts'] ) ? $result['parts'] : array(), 'before' => $before, 'after' => null === $after ? null : array( 'html' => isset( $after['html'] ) && is_string( $after['html'] ) ? wp_kses_post( $after['html'] ) : '', 'leftOut' => isset( $after['leftOut'] ) && is_array( $after['leftOut'] ) ? array_map( 'sanitize_text_field', $after['leftOut'] ) : array(), 'writtenLater' => isset( $after['writtenLater'] ) && is_array( $after['writtenLater'] ) ? array_map( 'sanitize_text_field', $after['writtenLater'] ) : array(), ), ) ); } /** * Learn every format again now. Asynchronous; at most once a minute. */ public function relearn() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->require_manage(); $this->require_connected(); $result = $this->plugin->api()->relearn_field_formats( $this->plugin->settings()->get_store_id() ); if ( is_wp_error( $result ) ) { wp_send_json_error( $this->error_payload( $result ) ); } wp_send_json_success( array( 'requested' => true ) ); } }