providers = $providers; add_action( 'admin_menu', array( $this, 'register_menu' ) ); add_action( 'admin_init', array( $this, 'handle_connect_return' ) ); add_action( 'admin_init', array( $this, 'handle_post' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); } /** This plugin's page in wp-admin - also where 404 Emails sends the browser back after connecting. */ public static function page_url() { return admin_url( 'admin.php?page=' . self::MENU_SLUG ); } public function register_menu() { add_menu_page( __( '404 Emails', '404-emails' ), __( '404 Emails', '404-emails' ), self::CAPABILITY, self::MENU_SLUG, array( Forms_Page::class, 'render' ), 'dashicons-email-alt', 58 ); add_submenu_page( self::MENU_SLUG, __( 'Forms', '404-emails' ), __( 'Forms', '404-emails' ), self::CAPABILITY, self::MENU_SLUG, array( Forms_Page::class, 'render' ) ); add_submenu_page( self::MENU_SLUG, __( 'Other forms', '404-emails' ), __( 'Other forms', '404-emails' ), self::CAPABILITY, Other_Forms_Page::SLUG, array( Other_Forms_Page::class, 'render' ) ); add_submenu_page( self::MENU_SLUG, __( 'Settings', '404-emails' ), __( 'Settings', '404-emails' ), self::CAPABILITY, self::SETTINGS_SLUG, array( $this, 'render' ) ); } public function enqueue_assets( $hook_suffix ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- which screen this is. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; if ( ! in_array( $page, array( self::MENU_SLUG, self::SETTINGS_SLUG, Other_Forms_Page::SLUG ), true ) ) { return; } wp_enqueue_style( 'emails404-admin', EMAILS404_PLUGIN_URL . 'assets/admin.css', array(), EMAILS404_VERSION ); if ( self::MENU_SLUG === $page ) { wp_enqueue_script( 'emails404-forms-editor', EMAILS404_PLUGIN_URL . 'assets/forms-editor.js', array(), EMAILS404_VERSION, true ); } } /** * 404 Emails sends the browser back here with `state` and either a * one-time `code` or `error`. Finish connecting (Connect::complete checks * the state belongs to this admin), then reload the page without them. */ public function handle_connect_return() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- the PKCE state (checked in Connect::complete) is this redirect's CSRF protection. if ( ! isset( $_GET['page'], $_GET['state'] ) || self::MENU_SLUG !== $_GET['page'] ) { return; } if ( ! isset( $_GET['code'] ) && ! isset( $_GET['error'] ) ) { return; } if ( ! current_user_can( self::CAPABILITY ) ) { return; } $state = sanitize_text_field( wp_unslash( $_GET['state'] ) ); $code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : null; // phpcs:enable $result = Connect::complete( $state, $code ); delete_transient( self::FORMS_TRANSIENT ); wp_safe_redirect( add_query_arg( 'emails404_notice', $result, self::page_url() ) ); exit; } /** * Handles both the settings-save form and the Test-Connection button, * on admin_init so redirects can happen before any output starts. * Every branch re-checks capability and nonce independently - never * trusts that reaching this page at all implies authorization (Part U: * "Do not trust request parameters merely because they come from * wp-admin"). */ public function handle_post() { if ( ! isset( $_POST['emails404_action'] ) ) { return; } if ( ! is_admin() || ! current_user_can( self::CAPABILITY ) ) { return; } $action = sanitize_key( wp_unslash( $_POST['emails404_action'] ) ); if ( self::SAVE_ACTION === $action ) { $this->handle_save(); } elseif ( self::TEST_ACTION === $action ) { $this->handle_test_connection(); } elseif ( self::CONNECT_ACTION === $action ) { check_admin_referer( self::CONNECT_ACTION, 'emails404_nonce' ); // An outside address on purpose: 404 Emails' own sign-in and Allow page. wp_redirect( Connect::start_url( self::page_url() ) ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect exit; } elseif ( self::DISCONNECT_ACTION === $action ) { check_admin_referer( self::DISCONNECT_ACTION, 'emails404_nonce' ); Connect::disconnect(); delete_transient( self::FORMS_TRANSIENT ); wp_safe_redirect( add_query_arg( 'emails404_notice', 'disconnected', self::page_url() ) ); exit; } } private function handle_save() { check_admin_referer( self::SAVE_ACTION, 'emails404_nonce' ); $platform_url_raw = isset( $_POST['emails404_platform_url'] ) ? wp_unslash( $_POST['emails404_platform_url'] ) : ''; $sanitized_url = Settings::sanitize_platform_url( $platform_url_raw ); if ( '' !== trim( (string) $platform_url_raw ) && '' === $sanitized_url ) { $this->save_error = __( 'That platform URL was not accepted - it must be a valid HTTPS URL (a plain HTTP URL is only allowed for localhost/.test/.local development addresses).', '404-emails' ); return; } Settings::update_platform_url_raw( $sanitized_url ); if ( ! empty( $_POST['emails404_api_key'] ) ) { // A fresh key was pasted in - store it. An empty field is // deliberately treated as "leave the existing key alone," not // "clear it," since the field is never pre-filled with the // real secret (see Admin_Page::render_connection_section()) - // an admin re-saving the form without touching this field must // never accidentally wipe a working key. Settings::set_api_key( wp_unslash( $_POST['emails404_api_key'] ) ); } if ( ! empty( $_POST['emails404_has_site_display'] ) ) { $site = array(); foreach ( array( 'enabled', 'form', 'mode', 'trigger', 'delay', 'scroll', 'frequency', 'label', 'position', 'launcher', 'hide_paths', 'hide_shop' ) as $field ) { $key = 'emails404_site_' . $field; if ( isset( $_POST[ $key ] ) ) { $site[ $field ] = 'hide_paths' === $field ? sanitize_textarea_field( wp_unslash( $_POST[ $key ] ) ) : sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); } } Site_Display::save( $site ); } // The "Also add people from" settings are only on the page once connected - // never switch them off just because the form didn't include them. if ( ! empty( $_POST['emails404_has_sources'] ) ) { if ( isset( $_POST['emails404_selected_form_public_id'] ) ) { $form_id = sanitize_text_field( wp_unslash( $_POST['emails404_selected_form_public_id'] ) ); $form_name = isset( $_POST['emails404_selected_form_name'] ) ? sanitize_text_field( wp_unslash( $_POST['emails404_selected_form_name'] ) ) : ''; Settings::set_selected_form( $form_id, $form_name ); } Settings::set_event_tickets_enabled( ! empty( $_POST['emails404_et_enabled'] ) ); Settings::set_consent_checkbox_visible( ! empty( $_POST['emails404_et_consent_visible'] ) ); if ( isset( $_POST['emails404_et_consent_label'] ) ) { Settings::set_consent_label( wp_unslash( $_POST['emails404_et_consent_label'] ) ); } if ( isset( $_POST['emails404_et_consent_field_slug'] ) ) { Settings::set_consent_field_slug( wp_unslash( $_POST['emails404_et_consent_field_slug'] ) ); } Settings::set_woocommerce_enabled( ! empty( $_POST['emails404_woo_enabled'] ) ); if ( isset( $_POST['emails404_woo_consent_label'] ) ) { Settings::set_woo_consent_label( wp_unslash( $_POST['emails404_woo_consent_label'] ) ); } if ( isset( $_POST['emails404_woo_selected_form_public_id'] ) ) { $woo_form_id = sanitize_text_field( wp_unslash( $_POST['emails404_woo_selected_form_public_id'] ) ); $woo_form_name = isset( $_POST['emails404_woo_selected_form_name'] ) ? sanitize_text_field( wp_unslash( $_POST['emails404_woo_selected_form_name'] ) ) : ''; Settings::set_woo_selected_form( $woo_form_id, $woo_form_name ); } } delete_transient( self::FORMS_TRANSIENT ); $this->save_message = __( 'Settings saved.', '404-emails' ); } private function handle_test_connection() { check_admin_referer( self::TEST_ACTION, 'emails404_nonce' ); $result = Api_Client::from_settings()->list_forms(); if ( $result->success ) { Settings::set_connection_result( Settings::CONNECTION_CONNECTED ); $meta = Api_Client::from_settings()->get_meta(); if ( $meta->success && ! empty( $meta->data['meta']['workspaceName'] ) ) { Settings::set_workspace_name( (string) $meta->data['meta']['workspaceName'] ); } delete_transient( self::FORMS_TRANSIENT ); $this->save_message = __( 'Connection successful.', '404-emails' ); return; } $status = $this->map_error_to_connection_status( $result->error_category ); Settings::set_connection_result( $status, $result->error_category ); $this->save_error = $this->describe_connection_failure( $status ); } private function map_error_to_connection_status( $error_category ) { switch ( $error_category ) { case 'unauthorized': return Settings::CONNECTION_INVALID_KEY; case 'forbidden_scope': return Settings::CONNECTION_MISSING_SCOPE; case 'timeout': case 'unreachable': case 'not_configured': return Settings::CONNECTION_UNREACHABLE; default: return Settings::CONNECTION_UNEXPECTED; } } /** Beginner-friendly, never a stack trace or raw response body - Part D. */ private function describe_connection_failure( $status ) { switch ( $status ) { case Settings::CONNECTION_INVALID_KEY: return __( 'Invalid API key. Double-check the key you pasted, or create a new one in your 404 Emails workspace.', '404-emails' ); case Settings::CONNECTION_UNREACHABLE: return __( 'Could not reach 404 Emails. Check the Platform URL and your site\'s outbound internet access, then try again.', '404-emails' ); case Settings::CONNECTION_MISSING_SCOPE: return __( 'This API key is missing a required permission. Create a new key with the "View and submit signup forms" and "Create and update contacts" permissions.', '404-emails' ); default: return __( 'Unexpected response from 404 Emails. Please try again shortly.', '404-emails' ); } } public function render() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', '404-emails' ) ); } $et_provider = $this->find_event_tickets_provider(); $connected = Settings::is_connected(); ?>
; ?>)
save_message ); ?>
save_error ); ?>
' . esc_html( $workspace ) . '' ); } else { esc_html_e( 'Connected to 404 Emails', '404-emails' ); } ?>
|
|
|
%2$s
status_label( $status ) ); ?>
|
' . esc_html( $key_prefix ) . '' ); ?> |
|
|
| format_log_row( $last_success ) ) : esc_html__( 'None yet', '404-emails' ); ?> | |
| format_log_row( $last_failure ) ) : esc_html__( 'None yet', '404-emails' ); ?> | |