'', 'status' => '', 'field' => '', 'paged' => 1, ); /** * @var int|null */ private $total = null; /** * Products already read this request, by id; false for an id with no product. * * @var array */ private $products = array(); /** * @param string $screen The page's hook suffix. */ public function __construct( string $screen = '' ) { parent::__construct( array( 'singular' => 'enriched_product', 'plural' => 'enriched_products', 'ajax' => false, 'screen' => '' === $screen ? null : $screen, ) ); } public function unavailable(): bool { return $this->unavailable; } public function prepare_items(): void { $this->filters = self::read_filters(); $this->_column_headers = array( $this->get_columns(), array(), array(), 'product' ); $query = array( 'q' => $this->filters['s'], 'status' => $this->filters['status'], 'field' => $this->filters['field'], 'page' => $this->filters['paged'], 'per_page' => self::PER_PAGE, ); $list = EnrichedApi::fetch( $query ); if ( null === $list ) { $this->unavailable = true; $this->items = array(); return; } $this->items = $list['products']; $this->total = $list['total']; self::prime_posts( $this->items ); $this->set_pagination_args( array( 'total_items' => $list['total'], 'per_page' => self::PER_PAGE, ) ); } /** * One query for all the posts of the page instead of one per row. * * @param array> $items Rows of the list. */ private static function prime_posts( array $items ): void { $ids = array(); foreach ( $items as $item ) { if ( ctype_digit( (string) $item['product_id'] ) ) { $ids[] = (int) $item['product_id']; } } if ( array() !== $ids ) { _prime_post_caches( $ids ); } } /** * Matches of the current filters, or null when the list is not available. */ public function total(): ?int { return $this->total; } /** * Only values the page offers get through; anything else is as if it had not been sent. * * @return array{s: string, status: string, field: string, paged: int} */ private static function read_filters(): array { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- filters of a read-only list; nothing changes. $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; $status = isset( $_GET['cortex_status'] ) ? sanitize_key( wp_unslash( $_GET['cortex_status'] ) ) : ''; $field = isset( $_GET['cortex_field'] ) ? sanitize_key( wp_unslash( $_GET['cortex_field'] ) ) : ''; $paged = isset( $_GET['paged'] ) ? absint( wp_unslash( $_GET['paged'] ) ) : 1; // phpcs:enable WordPress.Security.NonceVerification.Recommended return array( 's' => mb_substr( $search, 0, 200 ), 'status' => in_array( $status, EnrichedApi::STATUSES, true ) ? $status : '', 'field' => in_array( $field, EnrichedApi::FIELDS, true ) ? $field : '', 'paged' => max( 1, $paged ), ); } /** * @return array */ public function get_columns(): array { return array( 'picture' => __( 'Picture', 'cortexcommerce' ), 'product' => __( 'Product', 'cortexcommerce' ), 'status' => __( 'Status', 'cortexcommerce' ), 'updated' => __( 'Updated', 'cortexcommerce' ), 'store_links' => __( 'Links', 'cortexcommerce' ), ); } protected function get_default_primary_column_name(): string { return 'product'; } /** * @return array */ protected function get_table_classes(): array { return array( 'widefat', 'fixed', 'striped', 'cortexcommerce-enriched__table' ); } /** * Labels of the parts CortexCommerce can update. * * @return array */ public static function field_labels(): array { return array( 'title' => __( 'Title', 'cortexcommerce' ), 'description' => __( 'Description', 'cortexcommerce' ), 'short_description' => __( 'Short description', 'cortexcommerce' ), 'tags' => __( 'Tags', 'cortexcommerce' ), 'seo_title' => __( 'SEO title', 'cortexcommerce' ), 'meta_description' => __( 'Meta description', 'cortexcommerce' ), 'image_alts' => __( 'Image alt text', 'cortexcommerce' ), 'new_image' => __( 'New image', 'cortexcommerce' ), ); } /** * @return array */ public static function status_labels(): array { return array( 'live' => __( 'Live', 'cortexcommerce' ), 'approved' => __( 'Approved', 'cortexcommerce' ), 'waiting' => __( 'Waiting for review', 'cortexcommerce' ), ); } public function no_items(): void { esc_html_e( 'No products match these filters.', 'cortexcommerce' ); } /** * The filter row above the table and the pagination below it; no bulk actions and no nonce, as this is a GET form * that changes nothing. The page-number box is left out (the "bottom" pagination has none), because it would ride * along when the merchant presses Filter and keep them on page 3 of a new search. * * @param string $which top or bottom. */ protected function display_tablenav( $which ): void { echo '
'; if ( 'top' === $which ) { $this->extra_tablenav( 'top' ); } else { $this->pagination( 'bottom' ); } echo '
'; } /** * @param string $which top or bottom. */ protected function extra_tablenav( $which ): void { if ( 'top' !== $which ) { return; } echo '
'; echo ''; printf( '', esc_attr( $this->filters['s'] ), esc_attr__( 'Search by product name', 'cortexcommerce' ) ); self::select( 'cortexcommerce-enriched-status', 'cortex_status', __( 'Status', 'cortexcommerce' ), __( 'All statuses', 'cortexcommerce' ), self::status_labels(), $this->filters['status'] ); self::select( 'cortexcommerce-enriched-field', 'cortex_field', __( 'Part updated', 'cortexcommerce' ), __( 'All parts updated', 'cortexcommerce' ), self::field_labels(), $this->filters['field'] ); printf( '', esc_html__( 'Filter', 'cortexcommerce' ) ); echo '
'; } /** * @param string $id The element id. * @param string $name The query arg. * @param string $label Screen-reader label. * @param string $all The option for no filter. * @param array $options Value => label. * @param string $selected The value in use. */ private static function select( string $id, string $name, string $label, string $all, array $options, string $selected ): void { echo ''; echo ''; } /** * @param array $item A row of the list. * @return \WC_Product|null The store's product, or null when the store no longer has it. */ private function product( array $item ): ?\WC_Product { $id = (string) $item['product_id']; if ( ! isset( $this->products[ $id ] ) ) { $product = ctype_digit( $id ) ? wc_get_product( (int) $id ) : false; $this->products[ $id ] = $product instanceof \WC_Product ? $product : false; } return false === $this->products[ $id ] ? null : $this->products[ $id ]; } /** * @param array $item A row of the list. * @param string $column_name The column. */ protected function column_default( $item, $column_name ): void { unset( $item, $column_name ); } /** * @param array $item A row of the list. */ protected function column_picture( $item ): void { $product = $this->product( $item ); if ( null === $product ) { return; } $image_id = (int) $product->get_image_id(); if ( $image_id > 0 ) { echo wp_get_attachment_image( $image_id, array( 40, 40 ), false, array( 'alt' => '' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- core builds and escapes the tag. } else { $src = '' !== $item['preview_image_url'] ? $item['preview_image_url'] : wc_placeholder_img_src(); printf( '', esc_url( $src ) ); } } /** * @param array $item A row of the list. */ protected function column_product( $item ): void { $product = $this->product( $item ); echo '' . esc_html( null === $product ? $item['title'] : $product->get_name() ) . ''; } /** * @param array $item A row of the list. */ protected function column_status( $item ): void { $labels = self::status_labels(); printf( '%2$s', esc_attr( $item['status'] ), esc_html( $labels[ $item['status'] ] ?? $item['status'] ) ); } /** * @param array $item A row of the list. */ protected function column_updated( $item ): void { if ( array() === $item['fields'] ) { echo ''; return; } $labels = self::field_labels(); echo ''; foreach ( $labels as $key => $label ) { if ( in_array( $key, $item['fields'], true ) ) { echo '' . esc_html( $label ) . ''; } } echo ''; } /** * @param array $item A row of the list. */ protected function column_store_links( $item ): void { $product = $this->product( $item ); if ( null === $product ) { return; } echo ''; $edit = 'trash' === $product->get_status() ? null : get_edit_post_link( $product->get_id(), 'raw' ); if ( is_string( $edit ) && '' !== $edit ) { printf( '%2$s', esc_url( $edit ), esc_html__( 'Edit in admin', 'cortexcommerce' ) ); } // Anyone but the owner and the team would get a 404 on an unpublished product's address. if ( 'publish' === $product->get_status() ) { $view = get_permalink( $product->get_id() ); if ( is_string( $view ) && '' !== $view ) { ConnectPage::external_link( $view, __( 'View on store', 'cortexcommerce' ) ); } } echo ''; } }