$args Extra query arguments. * @return string */ public static function url( $tab = 'settings', array $args = array() ) { $query = array( 'page' => self::PAGE ); if ( 'settings' !== $tab && '' !== $tab ) { $query['tab'] = sanitize_key( $tab ); } return add_query_arg( array_merge( $query, $args ), admin_url( 'admin.php' ) ); } /** * Tabs of the screen: Settings first, then the tabs of add-ons. * * @return array Slug => label. */ public static function tabs() { $tabs = array( 'settings' => __( 'Settings', 'dialtidy' ) ); /** * Tabs an add-on shows after Settings. * * @internal For add-ons. Not a public API: may change without notice. * * @param array $extra Slug => label. */ $extra = apply_filters( 'dialtidy_admin_tabs', array() ); foreach ( is_array( $extra ) ? $extra : array() as $slug => $label ) { $slug = is_string( $slug ) ? sanitize_key( $slug ) : ''; if ( '' !== $slug && ! isset( $tabs[ $slug ] ) && is_string( $label ) && '' !== trim( $label ) ) { $tabs[ $slug ] = $label; } } return $tabs; } /** * Tab shown on this request. * * @return string */ public static function current_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only tab switch. $tab = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'settings'; return isset( self::tabs()[ $tab ] ) ? $tab : 'settings'; } /** * "Settings" first in the plugin row. * * @param mixed $links Links. * @return mixed */ public static function action_links( $links ) { if ( is_array( $links ) && current_user_can( self::CAP ) ) { array_unshift( $links, '' . esc_html__( 'Settings', 'dialtidy' ) . '' ); } return $links; } /** * Country pickers on the settings screen. * * @param string $hook Screen hook. */ public static function assets( $hook ) { if ( self::SCREEN !== $hook ) { return; } wp_enqueue_style( 'woocommerce_admin_styles' ); wp_enqueue_script( 'wc-enhanced-select' ); wp_enqueue_script( 'dialtidy-admin', DIALTIDY_URL . 'assets/js/admin.js', array( 'jquery', 'wc-enhanced-select' ), DIALTIDY_VERSION, true ); wp_enqueue_style( 'dialtidy-admin', DIALTIDY_URL . 'assets/css/admin.css', array(), DIALTIDY_VERSION ); } /** * Help tab. */ public static function help() { $screen = get_current_screen(); if ( ! $screen ) { return; } $faq = array( __( 'Where do I find Dialtidy after installing it?', 'dialtidy' ) => __( 'In the admin menu, go to WooCommerce → Dialtidy. You can also click “Settings” under the plugin’s name on the Plugins screen.', 'dialtidy' ), __( 'Which phone fields get a flag?', 'dialtidy' ) => __( 'The billing and shipping phone in the block checkout and the classic checkout, and the addresses in My account.', 'dialtidy' ), __( 'How are numbers saved?', 'dialtidy' ) => __( 'In international format with the country code, for example +12015550123, even when the customer typed (201) 555-0123. On the order screen it is shown with spaces, for example +1 201-555-0123.', 'dialtidy' ), __( 'Is the phone field required?', 'dialtidy' ) => __( 'Dialtidy follows the WooCommerce setting, and an empty optional phone is never blocked. To change the setting, edit the Checkout page and select the Checkout block: the phone field option is in the block settings. With the classic checkout, it is under Appearance → Customize → WooCommerce → Checkout.', 'dialtidy' ), ); /** * Questions and answers of the Help tab (question => answer, plain text). * * @internal For add-ons. Not a public API: may change without notice. * * @param array $faq Questions and answers. */ $faq = (array) apply_filters( 'dialtidy_help_faq', $faq ); $html = ''; foreach ( $faq as $question => $answer ) { $html .= '

' . esc_html( $question ) . '

' . esc_html( $answer ) . '

'; } $screen->add_help_tab( array( 'id' => 'dialtidy-faq', 'title' => __( 'FAQ', 'dialtidy' ), 'content' => $html, ) ); $screen->set_help_sidebar( '

' . esc_html__( 'More help', 'dialtidy' ) . '

' . esc_html__( 'Support forum', 'dialtidy' ) . '

' ); } /** * Saves the settings form. */ public static function save() { if ( ! isset( $_POST['_wpnonce'] ) || ! is_string( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ), self::NONCE ) ) { wp_die( esc_html__( 'The link has expired. Reload the page and try again.', 'dialtidy' ), '', array( 'response' => 403 ) ); } if ( ! current_user_can( self::CAP ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to change these settings.', 'dialtidy' ), '', array( 'response' => 403 ) ); } $values = array(); foreach ( Settings::GROUPS as $group ) { $values[ $group . '_source' ] = isset( $_POST[ $group . '_source' ] ) && is_string( $_POST[ $group . '_source' ] ) ? sanitize_key( wp_unslash( $_POST[ $group . '_source' ] ) ) : ''; foreach ( array( 'custom', 'exclude' ) as $list ) { $key = $group . '_' . $list; $values[ $key ] = isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ? map_deep( wp_unslash( $_POST[ $key ] ), 'sanitize_text_field' ) : array(); } } $values['popular'] = isset( $_POST['popular'] ) && is_array( $_POST['popular'] ) ? map_deep( wp_unslash( $_POST['popular'] ), 'sanitize_text_field' ) : array(); $values['mode'] = isset( $_POST['mode'] ) && is_string( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : ''; /** * Settings form fields of an add-on: name => type (flag or int). * * @internal For add-ons. Not a public API: may change without notice. * * @param array $fields Fields. */ $fields = apply_filters( 'dialtidy_form_fields', array() ); $addon = array(); foreach ( is_array( $fields ) ? $fields : array() as $name => $type ) { if ( ! is_string( $name ) || ! preg_match( '/^[a-z0-9_]{1,64}$/', $name ) ) { continue; } $raw = isset( $_POST[ $name ] ) && is_string( $_POST[ $name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $name ] ) ) : null; if ( 'flag' === $type ) { $addon[ $name ] = '1' === $raw; } elseif ( 'int' === $type ) { $addon[ $name ] = null !== $raw && preg_match( '/^\d{1,9}$/', $raw ) ? (int) $raw : null; } } Settings::update( Settings::sanitize( $values ) ); /** * Saves an add-on's settings fields, read after the nonce and capability checks. * * @internal For add-ons. Not a public API: may change without notice. * * @param array $addon Field values (null: not sent or not a number). */ do_action( 'dialtidy_save_settings', $addon ); wp_safe_redirect( add_query_arg( 'saved', '1', self::url() ) ); exit; } /** * The settings screen. */ public static function render() { if ( ! current_user_can( self::CAP ) ) { return; } $s = Settings::all(); $names = Countries::all_codes(); $sources = array( 'all' => __( 'All countries', 'dialtidy' ), 'selling' => __( 'Countries you sell to (WooCommerce → Settings → General)', 'dialtidy' ), 'shipping' => __( 'Countries you ship to (WooCommerce → Settings → General)', 'dialtidy' ), 'custom' => __( 'Selected countries', 'dialtidy' ), ); $groups = array( 'billing' => __( 'Billing phone', 'dialtidy' ), 'shipping' => __( 'Shipping phone', 'dialtidy' ), ); $modes = array( 'block' => array( __( 'Stop the order', 'dialtidy' ), __( 'The customer sees what is wrong under the field and fixes the number before ordering.', 'dialtidy' ) ), 'warn' => array( __( 'Only warn', 'dialtidy' ), __( 'The customer sees a hint but can still order.', 'dialtidy' ) ), 'off' => array( __( 'Off', 'dialtidy' ), __( 'No check. Valid numbers are still saved in international format.', 'dialtidy' ) ), ); $tab = self::current_tab(); echo '

' . esc_html__( 'Dialtidy', 'dialtidy' ) . '


'; $tabs = self::tabs(); if ( count( $tabs ) > 1 ) { echo ''; } if ( 'settings' !== $tab ) { /** * Prints an add-on tab. * * @internal For add-ons. Not a public API: may change without notice. * * @param string $tab Tab slug. */ do_action( 'dialtidy_admin_tab', $tab ); echo '
'; return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View only: success notice after saving. if ( isset( $_GET['saved'] ) ) { echo '

' . esc_html__( 'Settings saved.', 'dialtidy' ) . '

'; } echo '

' . esc_html__( 'Customers pick their country from a list with flags, type the number as they are used to, and the order gets it in international format.', 'dialtidy' ) . '

'; echo '
'; echo ''; wp_nonce_field( self::NONCE ); echo '

' . esc_html__( 'Countries in the phone fields', 'dialtidy' ) . '

'; echo '

' . esc_html__( 'Each phone field has its own list. For example, the shipping phone can accept a foreign number even if you only ship to one country.', 'dialtidy' ) . '

'; echo ''; foreach ( $groups as $group => $label ) { echo ''; } echo ''; echo '
'; echo '

' . esc_html__( 'Checking numbers', 'dialtidy' ) . '

'; echo '

' . esc_html__( 'What happens when a customer types a number that can’t be right.', 'dialtidy' ) . '

'; echo '
'; /** * Prints an add-on's settings cards after Dialtidy's cards and before Plugin data, inside the settings form. * * @internal For add-ons. Not a public API: may change without notice. */ do_action( 'dialtidy_settings_cards' ); echo '

' . esc_html__( 'Plugin data', 'dialtidy' ) . '

'; echo ''; /** * Prints an add-on's rows () in the "Plugin data" card, the last card of the settings form. * * @internal For add-ons. Not a public API: may change without notice. */ do_action( 'dialtidy_settings_data_rows' ); echo '
'; echo '

'; echo '
'; } /** * A multiple country picker. * * @param string $name Field name. * @param string $id Element id. * @param array $selected Selected codes. * @param array $names Code => name. */ private static function country_select( $name, $id, array $selected, array $names ) { echo ''; } }