'checkoutline', 'screen_id' => self::screen_id(), 'title' => __( 'Checkout Fields', 'checkoutline' ), ) ); } } /** @param array $ids */ public function screen_ids( $ids ): array { $ids = (array) $ids; $ids[] = self::screen_id(); return $ids; } /** @param array $links */ public function action_links( $links ): array { return array_merge( array( '' . esc_html__( 'Checkout Fields', 'checkoutline' ) . '' ), (array) $links ); } public function enqueue( $hook = '' ): void { if ( self::screen_id() !== $hook ) { return; } wp_enqueue_script( 'checkoutline-admin', CHECKOUTLINE_URL . 'assets/js/admin.js', array( 'jquery', 'jquery-ui-sortable', 'wc-enhanced-select' ), CHECKOUTLINE_VERSION, true ); wp_localize_script( 'checkoutline-admin', 'checkoutlineAdmin', array( 'confirmDelete' => __( 'Delete this field? Orders keep the values already saved.', 'checkoutline' ) ) ); wp_enqueue_style( 'checkoutline-admin', CHECKOUTLINE_URL . 'assets/css/admin.css', array( 'woocommerce_admin_styles' ), CHECKOUTLINE_VERSION ); } /** * Lines under a field's label in the list: why it isn't shown at checkout, if it can't be, and * what add-ons say about it (e.g. its conditions). * * @return string[] */ public static function notes( array $field ): array { $notes = array(); if ( ! Fields::type( $field['type'] ) ) { $notes[] = __( 'Not shown at checkout: the plugin that provides this type is inactive. Its settings and saved answers are kept.', 'checkoutline' ); } elseif ( ! Fields::available( $field ) ) { $notes[] = __( 'Not shown at checkout: the plugin that checks when it shows is inactive. Its settings and saved answers are kept.', 'checkoutline' ); } /** * Lines shown under a field's label in the list of fields, as plain text. * * @param string[] $notes Lines so far. * @param array $field The field. */ return array_values( array_filter( array_map( 'strval', (array) apply_filters( 'checkoutline_field_summary', $notes, $field ) ) ) ); } public function render(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- which screen to show. $edit = isset( $_GET['edit'] ) ? sanitize_key( wp_unslash( (string) $_GET['edit'] ) ) : ''; if ( '' !== $edit ) { $this->render_edit( $edit ); return; } $fields = Fields::all(); $types = Fields::types(); ?>


notices(); ?>

Checkoutline Pro adds them.', 'checkoutline' ), array( 'a' => array( 'href' => true, 'target' => true, 'rel' => true ) ) ), esc_url( 'https://extendiment.com/plugins/checkoutline' ) ); ?>



| |
' . esc_html__( 'Yes', 'checkoutline' ) . '' : '' . esc_html__( 'No', 'checkoutline' ) . ''; ?> >

$row ) : ?>
[ type, text ]. */ public static function messages(): array { $messages = array( 'saved' => array( 'success', __( 'Checkout fields saved.', 'checkoutline' ) ), 'added' => array( 'success', __( 'Field added. It shows at checkout now.', 'checkoutline' ) ), 'updated' => array( 'success', __( 'Field updated.', 'checkoutline' ) ), 'deleted' => array( 'success', __( 'Field deleted. Orders keep the values already saved.', 'checkoutline' ) ), 'label' => array( 'error', __( 'Give the field a label.', 'checkoutline' ) ), 'key' => array( 'error', __( 'That key is used by another field. Choose another.', 'checkoutline' ) ), 'options' => array( 'error', __( 'This type of field needs at least one option.', 'checkoutline' ) ), ); /** * Notices on the Checkout Fields screen, by the code in its `message` query argument: code => * [ 'success' or 'error', text ]. An add-on adds the texts for its own error codes here * (see `checkoutline_field_error`). * * @param array $messages */ $filtered = apply_filters( 'checkoutline_admin_messages', $messages ); return is_array( $filtered ) ? $filtered + $messages : $messages; } private function notices(): void { $messages = self::messages(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- a message after a redirect. $message = isset( $_GET['message'] ) ? sanitize_key( wp_unslash( (string) $_GET['message'] ) ) : ''; if ( isset( $messages[ $message ] ) && is_array( $messages[ $message ] ) ) { $type = 'error' === ( $messages[ $message ][0] ?? '' ) ? 'error' : 'success'; printf( '

%s

', esc_attr( $type ), esc_html( (string) ( $messages[ $message ][1] ?? '' ) ) ); } } /** Types (space-separated) whose definition matches, for a row's data-types; $extra is a stored type no plugin registers. */ private static function types_where( callable $test, string $extra = '' ): string { $types = array_keys( array_filter( Fields::registry(), $test ) ); if ( '' !== $extra ) { $types[] = $extra; } return implode( ' ', $types ); } private function render_edit( string $key ): void { $field = 'new' === $key ? null : Fields::get( $key ); $draft = get_transient( 'checkoutline_draft_' . get_current_user_id() ); if ( is_array( $draft ) ) { delete_transient( 'checkoutline_draft_' . get_current_user_id() ); } $value = is_array( $draft ) ? $draft : ( $field ? $field : array( 'key' => '', 'label' => '', 'location' => 'order', 'type' => 'text', 'required' => false, 'options' => array(), 'enabled' => true, ) ); $types = Fields::types(); $unknown = isset( $types[ $value['type'] ] ) ? '' : (string) $value['type']; $box = 'checkbox' === Fields::block_type( $value['type'] ) && '' === $unknown; ?>


notices(); ?>

←

$field ) { $sorted[ $key ]['enabled'] = in_array( $key, $enabled, true ); } Fields::save( $sorted ); if ( $core ) { CoreFields::save( $core ); } wp_safe_redirect( self::url( array( 'message' => 'saved' ) ) ); exit; } /** Adds or updates one field. */ public function save_field(): void { self::check( 'checkoutline_field' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- checked above. $original = isset( $_POST['original'] ) ? sanitize_key( wp_unslash( (string) $_POST['original'] ) ) : ''; $label = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['label'] ) ) : ''; $key = isset( $_POST['key'] ) ? Fields::key_from( sanitize_text_field( wp_unslash( (string) $_POST['key'] ) ) ) : ''; $type = isset( $_POST['type'] ) ? sanitize_key( wp_unslash( (string) $_POST['type'] ) ) : 'text'; $text = static fn( string $name ) => isset( $_POST[ $name ] ) ? sanitize_text_field( wp_unslash( (string) $_POST[ $name ] ) ) : ''; $fields = Fields::all(); $stored = '' !== $original && isset( $fields[ $original ] ) ? $fields[ $original ] : array(); if ( ! Fields::type( $type ) && $type !== ( $stored['type'] ?? '' ) ) { $type = 'text'; // Only a registered type, or the one the field already has. } $field = array( 'key' => '' !== $original ? $original : ( '' !== $key ? $key : Fields::key_from( $label ) ), 'label' => $label, 'location' => isset( $_POST['location'] ) ? sanitize_key( wp_unslash( (string) $_POST['location'] ) ) : 'order', 'type' => $type, 'required' => ! empty( $_POST['required'] ), 'enabled' => ! empty( $_POST['enabled'] ), 'options' => Fields::parse_options( isset( $_POST['options'] ) ? sanitize_textarea_field( wp_unslash( (string) $_POST['options'] ) ) : '' ), 'placeholder' => $text( 'placeholder' ), 'default' => 'checkbox' === Fields::block_type( $type ) ? ( empty( $_POST['default_checked'] ) ? '' : '1' ) : $text( 'default' ), ); // phpcs:enable if ( '' === $original && 'new' === $field['key'] ) { $field['key'] = 'new_field'; // "new" is the Add screen's address (?edit=new). } // Settings this screen doesn't show (an inactive add-on's) stay as they are. $field += array_diff_key( $stored, array_flip( Fields::OWN ) ); /** * A field posted from its settings screen, before it's checked and saved: an add-on adds * its own settings from $_POST (the nonce and capability are already checked). * * @param array $field The field from the posted form. */ $field = (array) apply_filters( 'checkoutline_posted_field', $field ); $error = ''; if ( '' === $label ) { $error = 'label'; } elseif ( '' === $original && ( '' === $field['key'] || isset( $fields[ $field['key'] ] ) ) ) { $error = 'key'; } elseif ( Fields::has_options( $field['type'] ) && ! $field['options'] ) { $error = 'options'; } else { /** * Why a posted field can't be saved, as a message code ('' when it can). The field * goes back to its screen with the notice for that code (`checkoutline_admin_messages`). * * @param string $error Default ''. * @param array $field The posted field. */ $error = sanitize_key( (string) apply_filters( 'checkoutline_field_error', '', $field ) ); } if ( $error ) { set_transient( 'checkoutline_draft_' . get_current_user_id(), $field, MINUTE_IN_SECONDS * 10 ); wp_safe_redirect( self::url( array( 'edit' => '' !== $original ? $original : 'new', 'message' => $error, ) ) ); exit; } $fields[ $field['key'] ] = $field; // An edit keeps its place; a new field goes last. Fields::save( $fields ); wp_safe_redirect( self::url( array( 'message' => '' !== $original ? 'updated' : 'added' ) ) ); exit; } public function delete_field(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- checked next. $key = isset( $_GET['key'] ) ? sanitize_key( wp_unslash( (string) $_GET['key'] ) ) : ''; self::check( 'checkoutline_delete_' . $key ); $fields = Fields::all(); unset( $fields[ $key ] ); Fields::save( $fields ); wp_safe_redirect( self::url( array( 'message' => 'deleted' ) ) ); exit; } }