admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'preorder_pilot_search_products' ), 'loading' => __( 'Searching products…', 'preorder-pilot' ), 'empty' => __( 'No eligible products found.', 'preorder-pilot' ), 'error' => __( 'Search failed. Try again.', 'preorder-pilot' ), 'choose' => __( 'Choose a product', 'preorder-pilot' ), ) ); } /** * Url. * * @param string $tab Tab. * @param array $args Arguments. */ public static function url( $tab = 'overview', $args = array() ) { return add_query_arg( array_merge( array( 'page' => self::PAGE_SLUG, 'tab' => $tab, ), $args ), admin_url( 'admin.php' ) ); } /** * Request scalar. * * @param mixed $source Source. * @param mixed $key Key. * @param string $fallback Fallback value. */ private static function request_scalar( $source, $key, $fallback = '' ) { if ( ! is_array( $source ) || ! isset( $source[ $key ] ) ) { return $fallback; } $value = wp_unslash( $source[ $key ] ); return is_scalar( $value ) ? $value : $fallback; } /** * Request text. * * @param mixed $source Source. * @param mixed $key Key. * @param string $fallback Fallback value. */ private static function request_text( $source, $key, $fallback = '' ) { return sanitize_text_field( self::request_scalar( $source, $key, $fallback ) ); } /** * Request textarea. * * @param mixed $source Source. * @param mixed $key Key. * @param string $fallback Default. */ private static function request_textarea( $source, $key, $fallback = '' ) { return sanitize_textarea_field( self::request_scalar( $source, $key, $fallback ) ); } /** * Request key. * * @param mixed $source Source. * @param mixed $key Key. * @param string $fallback Default. */ private static function request_key( $source, $key, $fallback = '' ) { return sanitize_key( self::request_scalar( $source, $key, $fallback ) ); } /** * Request absint. * * @param mixed $source Source. * @param mixed $key Key. */ private static function request_absint( $source, $key ) { return absint( self::request_scalar( $source, $key, 0 ) ); } /** * Request id list. * * @param mixed $source Source. * @param mixed $key Key. */ private static function request_id_list( $source, $key ) { if ( ! is_array( $source ) || ! isset( $source[ $key ] ) ) { return array(); } $values = wp_unslash( $source[ $key ] ); if ( ! is_array( $values ) ) { $values = array( $values ); } $ids = array(); foreach ( $values as $value ) { if ( ! is_scalar( $value ) || ! preg_match( '/^[0-9]+$/D', (string) $value ) ) { continue; } $id = absint( $value ); if ( $id ) { $ids[] = $id; } } return array_values( array_unique( $ids ) ); } /** * Query product ids. * * @param array $args Arguments. */ private static function query_product_ids( $args = array() ) { global $wpdb; $args = wp_parse_args( $args, array( 'search' => '', 'configured' => false, 'statuses' => array( 'publish' ), 'limit' => 15, 'page' => 1, ) ); $statuses = array_values( array_intersect( (array) $args['statuses'], array( 'publish', 'private', 'draft', 'pending', 'future' ) ) ); if ( ! $statuses ) { $statuses = array( 'publish' ); } $limit = max( 1, min( 50, absint( $args['limit'] ) ) ); $page = max( 1, absint( $args['page'] ) ); $where = array( "p.post_type IN ('product','product_variation')" ); $params = array(); $where[] = 'p.post_status IN (' . implode( ',', array_fill( 0, count( $statuses ), '%s' ) ) . ')'; $params = array_merge( $params, $statuses ); if ( ! empty( $args['configured'] ) ) { $where[] = "EXISTS (SELECT 1 FROM {$wpdb->postmeta} pp_conf WHERE pp_conf.post_id = p.ID AND pp_conf.meta_key = '_preorder_pilot')"; } $search = trim( (string) $args['search'] ); if ( '' !== $search ) { $like = '%' . $wpdb->esc_like( $search ) . '%'; $search_where = array( 'p.post_title LIKE %s', 'parent.post_title LIKE %s', "EXISTS (SELECT 1 FROM {$wpdb->postmeta} sku WHERE sku.post_id = p.ID AND sku.meta_key = '_sku' AND sku.meta_value LIKE %s)" ); $params = array_merge( $params, array( $like, $like, $like ) ); if ( ctype_digit( $search ) ) { $search_where[] = 'p.ID = %d'; $params[] = absint( $search ); } $where[] = '(' . implode( ' OR ', $search_where ) . ')'; } $where_sql = implode( ' AND ', $where ); $count_sql = "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->posts} parent ON parent.ID = p.post_parent WHERE {$where_sql}"; $total = (int) $wpdb->get_var( $wpdb->prepare( $count_sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Built from fixed fragments; values use placeholders. $params_for_ids = array_merge( $params, array( $limit, ( $page - 1 ) * $limit ) ); $ids_sql = "SELECT DISTINCT p.ID FROM {$wpdb->posts} p LEFT JOIN {$wpdb->posts} parent ON parent.ID = p.post_parent WHERE {$where_sql} ORDER BY p.ID DESC LIMIT %d OFFSET %d"; return (object) array( 'ids' => array_map( 'absint', $wpdb->get_col( $wpdb->prepare( $ids_sql, $params_for_ids ) ) ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Built from fixed fragments; values use placeholders. 'total' => $total, ); } /** * Authorize. * * @param mixed $action Action. */ private function authorize( $action ) { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You cannot manage preorders.', 'preorder-pilot' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'preorder_pilot_' . $action ); } /** * Redirect with a notice; shared with other admin handlers. * * @param string $tab Tab. * @param string $message Message. * @param bool $error Error. */ public static function notice_redirect( $tab, $message, $error = false ) { $key = wp_generate_uuid4(); set_transient( 'preorder_pilot_notice_' . get_current_user_id() . '_' . $key, array( 'message' => $message, 'error' => $error, 'edit' => null, ), MINUTE_IN_SECONDS ); wp_safe_redirect( self::url( $tab, array( 'pilot_notice' => $key ) ) ); exit; } /** * Redirect. * * @param mixed $tab Tab. * @param mixed $message Message. * @param bool $error Error. * @param mixed|null $edit Edit. */ private function redirect( $tab, $message, $error = false, $edit = null ) { $key = wp_generate_uuid4(); set_transient( 'preorder_pilot_notice_' . get_current_user_id() . '_' . $key, array( 'message' => $message, 'error' => $error, 'edit' => $edit, ), MINUTE_IN_SECONDS ); wp_safe_redirect( self::url( $tab, array( 'pilot_notice' => $key ) ) ); exit; } // phpcs:disable WordPress.Security.NonceVerification.Missing -- Each handler below calls authorize() (check_admin_referer) or check_ajax_referer() before reading input. /** * Save product. */ public function save_product() { $this->authorize( 'save_product' ); $id = self::request_absint( $_POST, 'product_id' ); if ( ! current_user_can( 'edit_post', $id ) ) { $this->redirect( 'products', __( 'This product cannot be edited by your account.', 'preorder-pilot' ), true ); } $data = PreOrder_Pilot_Products::get_settings( $id ); foreach ( PreOrder_Pilot_Products::field_definitions( wc_get_product( $id ) ) as $field => $definition ) { if ( 'checkbox' === $definition['type'] ) { $data[ $field ] = isset( $_POST[ $field ] ); } elseif ( isset( $_POST[ $field ] ) && ! is_array( $_POST[ $field ] ) ) { $data[ $field ] = 'textarea' === $definition['type'] ? self::request_textarea( $_POST, $field ) : self::request_text( $_POST, $field ); } } $result = PreOrder_Pilot_Products::save_settings( $id, $data ); $product = wc_get_product( $id ); $edit = is_wp_error( $result ) && $product ? array_merge( $data, array( 'product_id' => $id, 'product_name' => wp_strip_all_tags( $product->get_formatted_name() ), ) ) : null; $this->redirect( 'products', is_wp_error( $result ) ? $result->get_error_message() : __( 'Preorder saved. Your storefront now uses these settings.', 'preorder-pilot' ), is_wp_error( $result ), $edit ); } /** * Bulk products. */ public function bulk_products() { $this->authorize( 'bulk_products' ); $ids = self::request_id_list( $_POST, 'product_ids' ); $mode = self::request_key( $_POST, 'bulk_action' ); if ( ! $ids || ! in_array( $mode, array( 'always', 'disabled', 'availability_date', 'release' ), true ) ) { $this->redirect( 'products', __( 'Select products and choose a bulk action.', 'preorder-pilot' ), true ); } foreach ( $ids as $id ) { if ( ! wc_get_product( $id ) || ! current_user_can( 'edit_post', $id ) ) { $this->redirect( 'products', __( 'A product is missing or cannot be edited. No products were changed.', 'preorder-pilot' ), true ); } } $bulk_date = self::request_text( $_POST, 'bulk_date' ); if ( 'availability_date' === $mode && ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $bulk_date ) || is_wp_error( PreOrder_Pilot_Products::sanitize_settings( array_merge( PreOrder_Pilot_Products::defaults(), array( 'availability_date' => $bulk_date ) ) ) ) ) ) { $this->redirect( 'products', __( 'Choose a valid expected shipping date before applying the bulk update.', 'preorder-pilot' ), true ); } $changed = 0; $errors = array(); foreach ( $ids as $id ) { if ( 'release' === $mode ) { $result = PreOrder_Pilot_Emails::release_product( $id ); if ( is_wp_error( $result ) ) { $errors[] = '#' . $id . ': ' . $result->get_error_message(); } else { ++$changed; } continue; } $data = PreOrder_Pilot_Products::get_settings( $id ); if ( 'availability_date' === $mode ) { $data['availability_date'] = $bulk_date; } else { $data['mode'] = $mode; } $result = PreOrder_Pilot_Products::save_settings( $id, $data ); if ( is_wp_error( $result ) ) { $errors[] = '#' . $id . ': ' . $result->get_error_message(); } else { ++$changed; } } $this->redirect( 'products', sprintf( /* translators: %d: Number of products updated. */ _n( '%d product updated.', '%d products updated.', $changed, 'preorder-pilot' ), $changed ) . ' ' . implode( ' ', $errors ), (bool) $errors ); } /** * Save settings. */ public function save_settings() { $this->authorize( 'save_settings' ); $settings = get_option( self::OPTION_NAME, array() ); if ( ! is_array( $settings ) ) { $settings = array(); } foreach ( array( 'default_message', 'default_button' ) as $field ) { $settings[ $field ] = self::request_text( $_POST, $field ); } $settings['availability_message'] = $settings['default_message']; $settings['preorder_status'] = isset( $_POST['preorder_status'] ); $settings['keep_data'] = ! isset( $_POST['delete_data'] ); /** * Lets add-ons save the settings fields they render. The request nonce is already verified. * * @param array $settings Settings. */ $settings = (array) apply_filters( 'preorder_pilot_save_store_settings', $settings ); update_option( self::OPTION_NAME, $settings ); $this->redirect( 'settings', __( 'Store defaults saved.', 'preorder-pilot' ) ); } /** * Search products. */ public function search_products() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array(), 403 ); } check_ajax_referer( 'preorder_pilot_search_products', 'nonce' ); $result = self::query_product_ids( array( 'search' => self::request_text( $_GET, 'search' ), 'configured' => false, 'statuses' => array( 'publish' ), 'limit' => 40, ) ); $rows = array(); foreach ( $result->ids as $id ) { $product = wc_get_product( $id ); if ( ! $product || ! $product->is_type( PreOrder_Pilot_Products::supported_types() ) || ! current_user_can( 'edit_post', $id ) ) { continue; } $rows[] = array( 'id' => $id, 'name' => wp_strip_all_tags( $product->get_formatted_name() ), ); } wp_send_json_success( $rows ); } /** * Toggle product. */ public function toggle_product() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'You cannot manage preorders.', 'preorder-pilot' ) ), 403 ); } check_ajax_referer( 'preorder_pilot_search_products', 'nonce' ); $id = self::request_absint( $_POST, 'product_id' ); if ( ! current_user_can( 'edit_post', $id ) ) { wp_send_json_error( array( 'message' => __( 'You cannot edit this product.', 'preorder-pilot' ) ), 403 ); } $data = PreOrder_Pilot_Products::get_settings( $id ); $data['mode'] = 'disabled' === $data['mode'] ? 'always' : 'disabled'; $result = PreOrder_Pilot_Products::save_settings( $id, $data ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ), 400 ); } $message = 'disabled' === $data['mode'] ? __( 'Preorder paused.', 'preorder-pilot' ) : __( 'Preorder enabled.', 'preorder-pilot' ); $key = wp_generate_uuid4(); set_transient( 'preorder_pilot_notice_' . get_current_user_id() . '_' . $key, array( 'message' => $message, 'error' => false, ), MINUTE_IN_SECONDS ); wp_send_json_success( array( 'message' => $message, 'redirect' => self::url( 'products', array( 'pilot_notice' => $key ) ), ) ); } // phpcs:enable WordPress.Security.NonceVerification.Missing /** * Render page. */ public function render_page() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $tabs = $this->tabs(); $tab = self::request_key( $_GET, 'tab', 'overview' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation/display parameter; no state change. if ( ! isset( $tabs[ $tab ] ) ) { $tab = 'overview'; } ?>
PreOrder Pilot
notice(); ?>
render_tab( $tab ); ?>
product_dialog(); ?>
__( 'Overview', 'preorder-pilot' ), 'products' => __( 'Products', 'preorder-pilot' ), 'orders' => __( 'Orders', 'preorder-pilot' ), 'settings' => __( 'Settings', 'preorder-pilot' ), 'diagnostics' => __( 'Diagnostics', 'preorder-pilot' ), ); $tabs = apply_filters( 'preorder_pilot_admin_tabs', $core ); return is_array( $tabs ) ? array_merge( $tabs, array_diff_key( $core, $tabs ) ) : $core; } /** * Render one tab. * * @param string $tab Tab key. */ private function render_tab( $tab ) { if ( in_array( $tab, array( 'overview', 'products', 'orders', 'settings', 'diagnostics' ), true ) ) { call_user_func( array( $this, 'render_' . $tab ) ); return; } do_action( 'preorder_pilot_render_tab_' . $tab, $this ); } /** * Notice. */ private function notice() { $external = self::request_key( $_GET, 'preorder_pilot_notice' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation/display parameter; no state change. $messages = array( 'notifications_processed' => __( 'Availability notifications processed. Check subscriber status below; mail acceptance does not confirm delivery.', 'preorder-pilot' ), 'notifications_unavailable' => __( 'An availability update cannot be sent yet. The product must be in stock and availability notifications must be enabled.', 'preorder-pilot' ), ); if ( isset( $messages[ $external ] ) ) { ?>

get_parent_id() ? $product->get_parent_id() : $id ] = true; } } $active = count( $active_products ); ?>


<?php esc_attr_e( 'PreOrder Pilot parcel-carrying robot mascot', 'preorder-pilot' ); ?>
kpi( __( 'Customer preorders', 'preorder-pilot' ), sprintf( /* translators: %s: distinct customer orders containing a preorder this calendar month. */ __( '%s preorders this month', 'preorder-pilot' ), number_format_i18n( $customer_orders['current'] ) ), sprintf( /* translators: %s: distinct customer preorder orders in the previous calendar month. */ __( '%s preorders last month', 'preorder-pilot' ), number_format_i18n( $customer_orders['previous'] ) ), '◉' ); $this->kpi( __( 'Active products', 'preorder-pilot' ), number_format_i18n( $active ), __( 'Currently accepting preorders', 'preorder-pilot' ), '◈' ); $this->kpi( __( 'Preorder orders', 'preorder-pilot' ), number_format_i18n( $summary['orders'] ), __( 'Booked orders · all time', 'preorder-pilot' ), '▤' ); $this->kpi( __( 'Units preordered', 'preorder-pilot' ), number_format_i18n( $summary['units'] ), __( 'After refunded quantities', 'preorder-pilot' ), '▦' ); $revenue = array(); foreach ( $summary['revenue'] as $currency => $amount ) { $revenue[] = wc_price( $amount, array( 'currency' => $currency ) ); } $this->kpi( __( 'Net preorder revenue', 'preorder-pilot' ), $revenue ? implode( '
', $revenue ) : wc_price( 0 ), __( 'Excludes tax, shipping and refunds', 'preorder-pilot' ), '↗', true ); ?>

product_table( true ); ?>
✦

heading( __( 'Products, ready before they arrive.', 'preorder-pilot' ), __( 'Manage pre-order dates and button labels in one place.', 'preorder-pilot' ) ); ?>

search_form( 'products', __( 'Search by product name, SKU or ID', 'preorder-pilot' ) ); $this->product_table(); ?>
$search, 'configured' => true, 'statuses' => array( 'publish', 'private', 'draft', 'pending', 'future' ), 'limit' => $per_page, 'page' => $page, ) ); foreach ( $result->ids as $id ) { $product = wc_get_product( $id ); if ( $product ) { $products[] = $product; } } $total = $result->total; if ( ! $products ) { $this->empty_state( __( 'Your next launch starts here', 'preorder-pilot' ), $search ? __( 'No configured preorders match this search.', 'preorder-pilot' ) : __( 'Add your first preorder product. Real orders and performance appear as customers buy.', 'preorder-pilot' ), true, 'greeting' ); return; } if ( ! $compact ) { ?>
get_id(); $settings = PreOrder_Pilot_Products::get_settings( $product ); $active = PreOrder_Pilot_Products::is_active( $product ); $edit_data = array_merge( $settings, array( 'product_id' => $id, 'product_name' => wp_strip_all_tags( $product->get_formatted_name() ), ) ); ?> ' . wp_kses_post( (string) apply_filters( 'preorder_pilot_capacity_cell', '', $product, $settings ) ) . ''; } ?>
get_image( array( 42, 42 ) ) ); ?>
get_name() ); ?>get_sku() ? $product->get_sku() : '#' . $id ); ?> is_type( 'variation' ) ) { echo ' · ' . esc_html( wc_get_formatted_variation( $product, true, false, true ) ); } ?>
pagination( $total, $per_page, $page, 'products', $search ); } } /** * Pagination. * * @param mixed $total Total. * @param mixed $size Size. * @param mixed $page Page. * @param mixed $tab Tab. * @param mixed $search Search. */ public function pagination( $total, $size, $page, $tab, $search ) { $pages = (int) ceil( $total / $size ); if ( $pages < 2 ) { return; } ?> heading( __( 'Every preorder, in one place.', 'preorder-pilot' ), __( 'Track native WooCommerce orders that contain pre-order items.', 'preorder-pilot' ) ); $page = max( 1, self::request_absint( $_GET, 'paged' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation/display parameter; no state change. $search = self::request_text( $_GET, 'search' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation/display parameter; no state change. $result = PreOrder_Pilot_Reports::orders( $page, $search ); ?>

search_form( 'orders', __( 'Order ID or exact customer email', 'preorder-pilot' ) ); if ( ! $result->orders ) { $this->empty_state( __( 'Ready when your customers are', 'preorder-pilot' ), $search ? __( 'No preorder orders match. Search by numeric order ID or full email.', 'preorder-pilot' ) : __( 'Orders containing preorder items appear here after checkout. No sample orders are counted.', 'preorder-pilot' ), false, 'thinking' ); } else { ?>
orders as $order ) { $totals = PreOrder_Pilot_Reports::line_totals( $order ); $date = $order->get_date_created(); ?>
#get_order_number() ); ?>get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_billing_email() ); ?>date_i18n( get_option( 'date_format' ) ) : '—' ); ?>get_status() ) ); ?> $order->get_currency() ) ) ); do_action( 'preorder_pilot_order_row_actions', $order ); ?>
pagination( $result->total, 20, $page, 'orders', $search ); } ?>

heading( __( 'Simple defaults. Your voice.', 'preorder-pilot' ), __( 'Choose the messages customers see unless a product uses custom text.', 'preorder-pilot' ) ); $options = get_option( self::OPTION_NAME, array() ); if ( ! is_array( $options ) ) { $options = array(); } ?>

field( 'default_message', __( 'Availability message', 'preorder-pilot' ), 'text', $options['default_message'] ?? __( 'Available for pre-order.', 'preorder-pilot' ) ); $this->field( 'default_button', __( 'Preorder button', 'preorder-pilot' ), 'text', $options['default_button'] ?? __( 'Pre-order now', 'preorder-pilot' ) ); ?>

Settings > Emails.', 'preorder-pilot' ); ?>


heading( __( 'Know what your store is running.', 'preorder-pilot' ), __( 'Live configuration details for troubleshooting and support.', 'preorder-pilot' ) ); $hpos = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); $checkout = get_post( wc_get_page_id( 'checkout' ) ); $cart = get_post( wc_get_page_id( 'cart' ) ); $rows = array( __( 'WordPress', 'preorder-pilot' ) => get_bloginfo( 'version' ), __( 'WooCommerce', 'preorder-pilot' ) => defined( 'WC_VERSION' ) ? WC_VERSION : '—', __( 'PHP', 'preorder-pilot' ) => PHP_VERSION, __( 'Theme', 'preorder-pilot' ) => wp_get_theme()->get( 'Name' ), __( 'Order storage', 'preorder-pilot' ) => $hpos ? __( 'High performance order storage (HPOS)', 'preorder-pilot' ) : __( 'WordPress order storage', 'preorder-pilot' ), __( 'Cart', 'preorder-pilot' ) => $cart && has_block( 'woocommerce/cart', $cart ) ? __( 'WooCommerce Cart block detected', 'preorder-pilot' ) : __( 'Classic cart or custom page', 'preorder-pilot' ), __( 'Checkout', 'preorder-pilot' ) => $checkout && has_block( 'woocommerce/checkout', $checkout ) ? __( 'WooCommerce Checkout block detected', 'preorder-pilot' ) : __( 'Classic checkout or custom page', 'preorder-pilot' ), __( 'Store email', 'preorder-pilot' ) => get_option( 'woocommerce_email_from_address', get_option( 'admin_email' ) ), __( 'Mail delivery', 'preorder-pilot' ) => __( 'Not verified. Check customer email delivery before launch.', 'preorder-pilot' ), __( 'Edition', 'preorder-pilot' ) => ucfirst( PreOrder_Pilot_Features::edition() ), ); $rows = (array) apply_filters( 'preorder_pilot_diagnostics_rows', $rows ); ?>

$value ) { ?>

$attribute_value ) { if ( in_array( $attribute, $allowed_attributes, true ) && is_scalar( $attribute_value ) ) { printf( ' %s="%s"', esc_attr( $attribute ), esc_attr( $attribute_value ) ); } } ?> >

field( 'availability_date', __( 'Expected availability', 'preorder-pilot' ), 'date' ); ?>
field( 'button_text', __( 'Button text · leave empty for default', 'preorder-pilot' ), 'text', '', array( 'maxlength' => 80 ) ); do_action( 'preorder_pilot_dialog_fields', $this ); ?>
id || ! current_user_can( 'manage_woocommerce' ) || PreOrder_Pilot_Features::extended() || get_user_meta( get_current_user_id(), 'preorder_pilot_upsell_dismissed', true ) ) { return; } $dismiss = wp_nonce_url( add_query_arg( 'action', 'preorder_pilot_dismiss_upsell', admin_url( 'admin-post.php' ) ), 'preorder_pilot_dismiss_upsell' ); printf( '

%1$s %3$s · %5$s

', esc_html__( 'Need deposits, automatic pre-orders when stock runs out, variation pre-orders or a launch calendar? The optional PreOrder Pilot Growth add-on adds them.', 'preorder-pilot' ), esc_url( 'https://licenses.weareplano.gr/buy' ), esc_html__( 'Learn about Growth', 'preorder-pilot' ), esc_url( $dismiss ), esc_html__( 'Dismiss', 'preorder-pilot' ) ); } /** * Remember the dismissal for this user. */ public function dismiss_upsell() { $this->authorize( 'dismiss_upsell' ); update_user_meta( get_current_user_id(), 'preorder_pilot_upsell_dismissed', 1 ); wp_safe_redirect( self::url() ); exit; } }