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(
'
',
/* 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 : '' );
}
}
}