id, array( 'edit-' . Library::POST_TYPE, 'edit-product' ), true ) ) { wp_enqueue_style( 'prodtabs-admin', PRODTABS_URL . 'assets/css/admin.css', array(), Plugin::asset_version( 'assets/css/admin.css' ) ); } } /** * Columns of the list. * * @param string[] $columns Columns. */ public static function columns( $columns ) { $out = array(); foreach ( (array) $columns as $key => $label ) { if ( 'date' === $key ) { continue; } $out[ $key ] = $label; if ( 'title' === $key ) { $out['prodtabs_excerpt'] = __( 'Content', 'prodtabs' ); $out['prodtabs_uses'] = __( 'Used on', 'prodtabs' ); } } $out['prodtabs_modified'] = __( 'Last modified', 'prodtabs' ); return $out; } /** * Sortable columns. * * @param string[] $columns Columns. */ public static function sortable_columns( $columns ) { $columns['prodtabs_modified'] = 'modified'; return $columns; } /** * Column content. * * @param string $column Column. * @param int $post_id Library tab id. */ public static function column( $column, $post_id ): void { $post = get_post( $post_id ); if ( ! $post ) { return; } switch ( $column ) { case 'prodtabs_excerpt': $shop = Library::shop_title( $post ); if ( $shop !== $post->post_title ) { /* translators: %s: title shown in the shop. */ echo '' . esc_html( sprintf( __( 'Shop title: %s', 'prodtabs' ), $shop ) ) . '
'; } // A space before each tag, so that paragraphs and list items do not run into each other. echo esc_html( wp_trim_words( wp_strip_all_tags( str_replace( '<', ' <', strip_shortcodes( excerpt_remove_blocks( $post->post_content ) ) ) ), 18 ) ); break; case 'prodtabs_uses': $scope = (string) get_post_meta( $post_id, Library::META_SCOPE, true ); if ( '' !== $scope && isset( Library::scopes()[ $scope ] ) ) { echo '' . esc_html( Library::scopes()[ $scope ] ) . ''; break; } $count = self::count( (int) $post_id ); if ( ! $count ) { esc_html_e( 'No product', 'prodtabs' ); break; } printf( '%s', esc_url( add_query_arg( array( 'post_type' => 'product', 'prodtabs_library' => $post_id ), admin_url( 'edit.php' ) ) ), /* translators: %d: number of products. */ esc_html( sprintf( _n( '%d product', '%d products', $count, 'prodtabs' ), $count ) ) ); break; case 'prodtabs_modified': echo esc_html( get_the_modified_date( '', $post ) ); break; } } /** * Number of products that use a tab, counted for the whole page at once. * * @param int $post_id Library tab id. */ private static function count( int $post_id ): int { global $wp_query; if ( null === self::$counts ) { $ids = $wp_query instanceof \WP_Query ? wp_list_pluck( $wp_query->posts, 'ID' ) : array(); self::$counts = Library::usage_counts( $ids ); } if ( ! isset( self::$counts[ $post_id ] ) ) { self::$counts += Library::usage_counts( array( $post_id ) ); } return (int) self::$counts[ $post_id ]; } /** * Products list filtered by library tab (link of the "Used on" column). * * @param \WP_Query $query Query. */ public static function filter_products( $query ): void { if ( ! is_admin() || ! $query->is_main_query() || 'product' !== $query->get( 'post_type' ) ) { return; } $id = self::filtered_library_id(); if ( ! $id ) { return; } $meta = (array) $query->get( 'meta_query' ); $meta[] = Library::usage_meta_query( $id ); $query->set( 'meta_query', $meta ); $query->set( 'lang', '' ); // Every language: the products of a library tab may be in any of them. } /** Library tab used to filter the products list. */ private static function filtered_library_id(): int { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only list filter. return isset( $_GET['prodtabs_library'] ) ? absint( $_GET['prodtabs_library'] ) : 0; } /** Notices of the list screens: products filtered by tab, empty library. */ public static function notices(): void { $screen = get_current_screen(); if ( ! $screen ) { return; } if ( 'edit-product' === $screen->id && self::filtered_library_id() ) { $post = get_post( self::filtered_library_id() ); if ( $post && Library::POST_TYPE === $post->post_type ) { printf( '

%s %s

', /* translators: %s: library tab title. */ esc_html( sprintf( __( 'Products that use the shared tab ā€œ%sā€.', 'prodtabs' ), Library::shop_title( $post ) ) ), esc_url( admin_url( 'edit.php?post_type=product' ) ), esc_html__( 'Show all products', 'prodtabs' ) ); } return; } if ( 'edit-' . Library::POST_TYPE !== $screen->id ) { return; } $counts = (array) wp_count_posts( Library::POST_TYPE ); unset( $counts['auto-draft'] ); if ( array_sum( array_map( 'intval', $counts ) ) > 0 ) { return; } $yikes = Import_Yikes::detect(); ?>

'', 1 => __( 'Tab updated.', 'prodtabs' ), 4 => __( 'Tab updated.', 'prodtabs' ), 6 => __( 'Tab published.', 'prodtabs' ), 7 => __( 'Tab saved.', 'prodtabs' ), 8 => __( 'Tab submitted.', 'prodtabs' ), 9 => __( 'Tab scheduled.', 'prodtabs' ), 10 => __( 'Tab draft updated.', 'prodtabs' ), ); return $messages; } /** Panel "Tab" of the block editor: shop title and products that use the tab. */ public static function editor_assets(): void { $screen = get_current_screen(); if ( ! $screen || Library::POST_TYPE !== $screen->post_type ) { return; } $post_id = (int) get_the_ID(); wp_enqueue_script( 'prodtabs-library-editor', PRODTABS_URL . 'assets/js/library-editor.js', array( 'wp-plugins', 'wp-editor', 'wp-edit-post', 'wp-components', 'wp-data', 'wp-element', 'wp-core-data' ), Plugin::asset_version( 'assets/js/library-editor.js' ), true ); wp_localize_script( 'prodtabs-library-editor', 'prodtabsLibrary', self::editor_data( $post_id ) ); Display::copy_script(); } /** * Data of the editor panel. * * @param int $post_id Library tab id. */ private static function editor_data( int $post_id ): array { $count = $post_id ? (int) ( Library::usage_counts( array( $post_id ) )[ $post_id ] ?? 0 ) : 0; $products = array(); foreach ( $post_id ? Library::products_using( $post_id, 10 ) : array() as $id ) { $products[] = array( // Plain text: the editor panel escapes it, entities would show as such. 'title' => html_entity_decode( wp_strip_all_tags( get_the_title( $id ) ), ENT_QUOTES, 'UTF-8' ), 'url' => (string) get_edit_post_link( $id, 'raw' ), ); } return array( 'metaKey' => Library::META_SHOP_TITLE, 'scopeKey' => Library::META_SCOPE, 'scopes' => array_map( static function ( $value, $label ) { return array( 'value' => (string) $value, 'label' => $label, ); }, array_keys( Library::scopes() ), Library::scopes() ), 'count' => $count, 'products' => $products, 'allUrl' => add_query_arg( array( 'post_type' => 'product', 'prodtabs_library' => $post_id ), admin_url( 'edit.php' ) ), 'shortcode' => $post_id ? Display::shortcode_shared( $post_id ) : '', 'i18n' => array( 'panel' => __( 'Tab', 'prodtabs' ), 'shopTitle' => __( 'Title in the shop', 'prodtabs' ), 'shopHelp' => __( 'Shown to customers. Leave empty to use the title above, which then also serves as the internal name.', 'prodtabs' ), 'usedOn' => __( 'Used on', 'prodtabs' ), 'none' => __( 'No product uses this tab yet. Add it from the Tabs panel of a product.', 'prodtabs' ), /* translators: %d: number of products. */ 'count' => _n( '%d product', '%d products', $count, 'prodtabs' ), 'all' => __( 'See all the products', 'prodtabs' ), 'showOn' => __( 'Show on', 'prodtabs' ), 'showHelp' => __( 'Set to all products, the tab is added to every product after its own tabs. A product can hide it in its Tabs panel.', 'prodtabs' ), 'shortcode' => __( 'Shortcode', 'prodtabs' ), 'shortHelp' => __( 'Shows this tab in a page or a post, in the language of the page, once the tab is published.', 'prodtabs' ), 'copy' => __( 'Copy', 'prodtabs' ), ), ); } /** * Meta box for the classic editor, when the block editor is turned off for library tabs. * * @param \WP_Post $post Library tab. */ public static function classic_meta_box( $post ): void { if ( function_exists( 'use_block_editor_for_post' ) && use_block_editor_for_post( $post ) ) { return; } add_meta_box( 'prodtabs-tab', __( 'Tab', 'prodtabs' ), array( __CLASS__, 'render_classic_meta_box' ), Library::POST_TYPE, 'side' ); } /** * Classic meta box content. * * @param \WP_Post $post Library tab. */ public static function render_classic_meta_box( $post ): void { $data = self::editor_data( (int) $post->ID ); wp_nonce_field( 'prodtabs_library_meta', 'prodtabs_library_nonce' ); ?>


ID, Library::META_SCOPE, true ); ?>


' . esc_html( $data['i18n']['shortHelp'] ) . '

'; } } /** * Saves the classic meta box. * * @param int $post_id Library tab id. */ public static function save_classic_meta_box( $post_id ): void { if ( ! isset( $_POST['prodtabs_library_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['prodtabs_library_nonce'] ) ), 'prodtabs_library_meta' ) ) { return; } if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) ) { return; } $title = isset( $_POST['prodtabs_shop_title'] ) ? sanitize_text_field( wp_unslash( $_POST['prodtabs_shop_title'] ) ) : ''; update_post_meta( $post_id, Library::META_SHOP_TITLE, wp_slash( $title ) ); // No choice checked: the stored one belongs to an extension that is not running (Premium's "terms"), and stays. if ( isset( $_POST['prodtabs_scope'] ) ) { $scope = sanitize_key( wp_unslash( $_POST['prodtabs_scope'] ) ); update_post_meta( $post_id, Library::META_SCOPE, array_key_exists( $scope, Library::scopes() ) ? $scope : '' ); } } }