self::CAPABILITY );
}
/**
* Submenu under Products.
*/
public function menu(): void {
add_submenu_page(
'edit.php?post_type=product',
__( 'Product tab settings', 'pnscripts-tabcrest' ),
__( 'Tab settings', 'pnscripts-tabcrest' ),
self::CAPABILITY,
self::SLUG,
array( $this, 'render' )
);
}
/**
* Bookmarks of the 1.0.x page (page=pnscripts-product-tabs) open the renamed page.
*/
public function redirect_legacy_slug(): void {
$url = self::legacy_redirect_url();
if ( null !== $url ) {
wp_safe_redirect( $url );
exit;
}
}
/**
* Where a request for the 1.0.x page slug should go, or null when it is not one.
*/
public static function legacy_redirect_url(): ?string {
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only redirect between two admin pages.
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( self::LEGACY_SLUG !== $page || ! current_user_can( self::CAPABILITY ) ) {
return null;
}
return self::url( $tab );
}
/**
* Register the option with its sanitiser.
*/
public function register_setting(): void {
register_setting(
self::GROUP,
Settings::OPTION,
array(
'type' => 'array',
'sanitize_callback' => array( Settings::class, 'sanitize' ),
'show_in_rest' => false,
'default' => Settings::defaults(),
)
);
}
/**
* "Settings" link on the Plugins screen.
*
* @param mixed $links Links.
* @return array
*/
public function action_links( mixed $links ): array {
$links = is_array( $links ) ? $links : array();
array_unshift(
$links,
sprintf( '%s', esc_url( self::url() ), esc_html__( 'Settings', 'pnscripts-tabcrest' ) ),
sprintf( '%s', esc_url( admin_url( 'edit.php?post_type=' . GlobalTabs::POST_TYPE ) ), esc_html__( 'Global tabs', 'pnscripts-tabcrest' ) )
);
return $links;
}
/**
* Page URL.
*
* @param string $tab Page tab.
*/
public static function url( string $tab = '' ): string {
$args = array(
'post_type' => 'product',
'page' => self::SLUG,
);
if ( '' !== $tab ) {
$args['tab'] = $tab;
}
return add_query_arg( $args, admin_url( 'edit.php' ) );
}
/**
* Assets on our page.
*
* @param string $hook_suffix Page hook.
*/
public function enqueue( string $hook_suffix ): void {
if ( 'product_page_' . self::SLUG !== $hook_suffix ) {
return;
}
wp_enqueue_style( 'pnscripts-product-tabs-admin', PNSCRIPTS_PRODUCT_TABS_URL . 'assets/css/admin.css', array(), PNSCRIPTS_PRODUCT_TABS_VERSION );
wp_enqueue_script( 'pnscripts-product-tabs-import', PNSCRIPTS_PRODUCT_TABS_URL . 'assets/js/admin-import.js', array(), PNSCRIPTS_PRODUCT_TABS_VERSION, true );
wp_localize_script(
'pnscripts-product-tabs-import',
'pnscriptsProductTabsImport',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'action' => self::AJAX_ACTION,
'nonce' => wp_create_nonce( self::AJAX_ACTION ),
'confirmImport' => __( 'Import the YIKES tabs now? YIKES data is not changed and you can undo the import.', 'pnscripts-tabcrest' ),
'confirmUndo' => __( 'Remove all tabs created by the import (tabs you added yourself stay)?', 'pnscripts-tabcrest' ),
/* translators: %d: number of products handled so far */
'running' => __( 'Working… %d products handled', 'pnscripts-tabcrest' ),
'failed' => __( 'The request failed. Reload the page and try again.', 'pnscripts-tabcrest' ),
'doneDry' => __( 'Dry run finished: nothing was changed. These numbers are what an import will do.', 'pnscripts-tabcrest' ),
'doneImport' => __( 'Import finished. Check a few product pages, then deactivate YIKES Custom Product Tabs.', 'pnscripts-tabcrest' ),
'doneUndo' => __( 'Import removed.', 'pnscripts-tabcrest' ),
/* translators: %d: number of further notes */
'moreNotes' => __( '… and %d more notes like these.', 'pnscripts-tabcrest' ),
'labels' => self::report_labels(),
)
);
}
/**
* Labels of the report counters.
*
* @return array
*/
public static function report_labels(): array {
return array(
'products_total' => __( 'Products with YIKES tabs', 'pnscripts-tabcrest' ),
'saved_found' => __( 'YIKES saved tabs', 'pnscripts-tabcrest' ),
'saved_created' => __( 'Global tabs to create / created', 'pnscripts-tabcrest' ),
'saved_existing' => __( 'Saved tabs imported before (left as they are)', 'pnscripts-tabcrest' ),
'products_imported' => __( 'Products to import / imported', 'pnscripts-tabcrest' ),
'products_unchanged' => __( 'Products unchanged since the last import (skipped)', 'pnscripts-tabcrest' ),
'tabs_custom' => __( 'Product tabs copied', 'pnscripts-tabcrest' ),
'tabs_linked' => __( 'Product tabs linked to a global tab', 'pnscripts-tabcrest' ),
'tabs_disabled_empty' => __( 'Tabs without a title (imported switched off)', 'pnscripts-tabcrest' ),
'tabs_disabled_duplicate' => __( 'Tabs hidden by a duplicate title in YIKES (imported switched off)', 'pnscripts-tabcrest' ),
'replaced_defaults' => __( 'Default tabs YIKES replaced (hidden on those products)', 'pnscripts-tabcrest' ),
'removed_globals' => __( 'Imported global tabs removed', 'pnscripts-tabcrest' ),
'removed_products' => __( 'Products cleaned', 'pnscripts-tabcrest' ),
);
}
/**
* AJAX: one import batch.
*/
public function ajax_import(): void {
check_ajax_referer( self::AJAX_ACTION, 'nonce' );
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_send_json_error( array( 'message' => __( 'You are not allowed to import tabs.', 'pnscripts-tabcrest' ) ), 403 );
}
$mode = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : YikesImporter::MODE_DRY;
$cursor = isset( $_POST['cursor'] ) ? absint( wp_unslash( $_POST['cursor'] ) ) : 0;
if ( ! in_array( $mode, array( YikesImporter::MODE_DRY, YikesImporter::MODE_IMPORT, YikesImporter::MODE_UNDO ), true ) ) {
wp_send_json_error( array( 'message' => 'mode' ), 400 );
}
wp_send_json_success( $this->importer->run_batch( $mode, $cursor ) );
}
/**
* Page.
*/
public function render(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation.
$tab = isset( $_GET['tab'] ) && 'import' === sanitize_key( wp_unslash( $_GET['tab'] ) ) ? 'import' : 'settings';
?>
render_import();
} else {
$this->render_settings();
}
?>
settings->all();
$name = Settings::OPTION;
$labels = ProductPanel::default_labels();
?>
',
esc_attr( Settings::OPTION . '[' . $key . ']' ),
checked( $checked, true, false ),
esc_html( $label )
);
}
/**
* Import screen.
*/
private function render_import(): void {
$products = $this->importer->count_products();
$active = class_exists( 'YIKES_Custom_Product_Tabs_Display', false ) || defined( 'YIKES_Custom_Product_Tabs_Version' );
$last = (int) get_option( YikesImporter::LAST_RUN, 0 );
?>
0 ? wp_date( get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' ), $last ) : false;
if ( is_string( $when ) ) {
echo ' · ';
printf(
/* translators: %s: date and time */
esc_html__( 'last import: %s', 'pnscripts-tabcrest' ),
esc_html( $when )
);
}
?>
id ) {
return;
}
if ( get_user_meta( get_current_user_id(), self::DISMISS_KEY, true ) || (int) get_option( YikesImporter::LAST_RUN, 0 ) > 0 ) {
return;
}
if ( 0 === $this->importer->count_products() ) {
return;
}
$dismiss = wp_nonce_url( add_query_arg( self::DISMISS_KEY, '1' ), self::DISMISS_KEY );
wp_enqueue_script( 'pnscripts-product-tabs-notice', PNSCRIPTS_PRODUCT_TABS_URL . 'assets/js/admin-notice.js', array(), PNSCRIPTS_PRODUCT_TABS_VERSION, true );
printf(
'',
esc_html__( 'PN Scripts Tabcrest found tabs from Custom Product Tabs for WooCommerce (YIKES). Import them in one click; YIKES data stays untouched.', 'pnscripts-tabcrest' ),
esc_url( self::url( 'import' ) ),
esc_html__( 'Review the import', 'pnscripts-tabcrest' ),
esc_url( $dismiss ),
esc_html__( 'Dismiss', 'pnscripts-tabcrest' )
);
}
/**
* Store the dismissal.
*/
public function maybe_dismiss(): void {
if ( ! isset( $_GET[ self::DISMISS_KEY ], $_GET['_wpnonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), self::DISMISS_KEY ) ) {
return;
}
update_user_meta( get_current_user_id(), self::DISMISS_KEY, 1 );
wp_safe_redirect( remove_query_arg( array( self::DISMISS_KEY, '_wpnonce' ) ) );
exit;
}
}