label */ private function steps() { $steps = array( 'style' => __( 'Look & feel', 'holmesportfolio-forms' ), 'privacy' => __( 'Data & privacy', 'holmesportfolio-forms' ), 'email' => __( 'Sending email', 'holmesportfolio-forms' ), 'spam' => __( 'Spam', 'holmesportfolio-forms' ), 'forms' => __( 'Starter forms', 'holmesportfolio-forms' ), ); if ( apply_filters( 'holmesforms_lead_capture_enabled', false ) ) { $steps['updates'] = __( 'Sign up', 'holmesportfolio-forms' ); } return (array) apply_filters( 'holmesforms_setup_steps', $steps ); } public function register_page() { $hook = add_submenu_page( '', __( 'Set up HolmesPortfolio Forms', 'holmesportfolio-forms' ), __( 'Setup', 'holmesportfolio-forms' ), 'holmesforms_admin', self::PAGE, array( $this, 'render' ) ); add_action( 'load-' . $hook, array( $this, 'set_page_title' ) ); } public function set_page_title() { $GLOBALS['title'] = __( 'Set up HolmesPortfolio Forms', 'holmesportfolio-forms' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- sets the admin page title } /* Render */ public function render() { if ( ! current_user_can( 'holmesforms_admin' ) ) { wp_die( esc_html__( 'Not allowed.', 'holmesportfolio-forms' ), '', array( 'response' => 403 ) ); } wp_enqueue_style( 'holmesforms-admin', HOLMESFORMS_URL . 'assets/css/admin.css', array(), holmesforms_asset_version( 'assets/css/admin.css' ) ); wp_enqueue_script( 'holmesforms-restore', HOLMESFORMS_URL . 'assets/js/holmesforms-restore.js', array(), holmesforms_asset_version( 'assets/js/holmesforms-restore.js' ), true ); HP_Forms_Admin::enqueue_screen_scripts(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read only, which step shows $step = isset( $_GET['step'] ) ? sanitize_key( wp_unslash( $_GET['step'] ) ) : 'welcome'; $steps = $this->steps(); if ( 'welcome' !== $step && 'done' !== $step && ! isset( $steps[ $step ] ) ) { $step = 'welcome'; } echo '
'; echo wp_kses( HP_Forms_Admin::brand_header_html(), HP_Forms_Kses::admin() ); if ( isset( $steps[ $step ] ) ) { $this->print_progress_rail( array_keys( $steps ), $step ); } $method = 'step_' . $step; if ( method_exists( $this, $method ) ) { $this->$method(); } elseif ( isset( $steps[ $step ] ) ) { do_action( 'holmesforms_setup_render_step', $step, $this ); } else { $this->step_welcome(); } echo '
'; } private function print_progress_rail( $slugs, $current ) { $labels = $this->steps(); $is_done = true; echo '
    '; foreach ( $slugs as $i => $slug ) { if ( $slug === $current ) { $is_done = false; $step_class = ' is-current'; } else { $step_class = $is_done ? ' is-done' : ''; } echo '
  1. ' . ( (int) $i + 1 ) . ' ' . esc_html( wp_strip_all_tags( $labels[ $slug ] ) ) . '
  2. '; } echo '
'; } public function open_form( $step ) { echo '
'; wp_nonce_field( 'holmesforms_setup_' . $step ); echo ''; echo ''; } /** @param string $back_step Slug to go back to, or '' for none. */ public function close_form( $back_step, $next_label = '' ) { $next_label = $next_label ? $next_label : __( 'Continue', 'holmesportfolio-forms' ); echo '

'; if ( $back_step ) { echo '← ' . esc_html__( 'Back', 'holmesportfolio-forms' ) . ''; } echo ''; echo '

'; echo wp_kses( '
', HP_Forms_Kses::admin() ); wp_nonce_field( 'holmesforms_setup_skip' ); echo ''; echo ''; echo '
'; } private static function later_prompt_attributes() { return ' data-hpf-confirm="' . esc_attr__( 'You can only come back to the setup wizard from Settings, on the Help & support tab. Finish it later?', 'holmesportfolio-forms' ) . '"' . ' data-hpf-confirm-title="' . esc_attr__( 'Finish the setup wizard later?', 'holmesportfolio-forms' ) . '"' . ' data-hpf-confirm-label="' . esc_attr__( 'Yes, finish later', 'holmesportfolio-forms' ) . '"'; } public function saved_setting( $key, $fallback = '' ) { $settings = get_option( 'holmesforms_settings', array() ); return ( is_array( $settings ) && isset( $settings[ $key ] ) ) ? $settings[ $key ] : $fallback; } public function previous_step( $slug ) { $order = array_keys( $this->steps() ); $position = array_search( $slug, $order, true ); return ( false !== $position && $position > 0 ) ? $order[ $position - 1 ] : 'welcome'; } /* Steps */ private function step_welcome() { $rows = array( array( 'brush', __( 'How your forms look', 'holmesportfolio-forms' ), __( 'Pick a house style: GOV.UK, Bootstrap, or a clean built-in look. It applies to every form on the site. What each form asks is still up to you.', 'holmesportfolio-forms' ) ), array( 'shield', __( 'What you keep, and for how long', 'holmesportfolio-forms' ), __( 'Whether answers are stored, how long they stay, and whether to log the sender’s IP address.', 'holmesportfolio-forms' ) ), array( 'mail', __( 'Where system emails come from', 'holmesportfolio-forms' ), __( 'The “from” address on notifications, and the reference prefix for this site.', 'holmesportfolio-forms' ) ), array( 'shield', __( 'Spam', 'holmesportfolio-forms' ), __( 'Three checks run on every form without any setup. You can add a visible challenge on top.', 'holmesportfolio-forms' ) ), array( 'stack', __( 'Starter forms', 'holmesportfolio-forms' ), __( 'Ready-made forms to open, copy from, or drop on a page. Keep them, change them or remove them.', 'holmesportfolio-forms' ) ), ); echo '
'; echo '
'; echo wp_kses( '' . self::icon( 'spark' ) . '', HP_Forms_Kses::admin() ); echo '
'; echo '

' . esc_html__( 'Get your forms working the way you want', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'A few quick choices, about a minute in all. You can change any of them later in Settings. You can also skip the wizard now and come back to it whenever you like.', 'holmesportfolio-forms' ) . '

'; echo '
'; echo ''; $first_step = array_key_first( $this->steps() ); echo '

'; echo '' . esc_html__( 'Get started', 'holmesportfolio-forms' ) . ' →'; echo '

'; echo wp_kses( '
', HP_Forms_Kses::admin() ); wp_nonce_field( 'holmesforms_setup_skip' ); echo ''; echo ''; echo '
'; echo '
'; } /** @param string $name One of: spark, brush, shield, mail, stack. */ private static function icon( $name ) { $paths = array( 'spark' => '', 'brush' => '', 'shield' => '', 'mail' => '', 'stack' => '', ); $path = isset( $paths[ $name ] ) ? $paths[ $name ] : $paths['spark']; return ''; } private function step_style() { $current = (string) $this->saved_setting( 'style_mode', 'gds' ); $styles = array( 'gds' => array( __( 'GOV.UK Design System', 'holmesportfolio-forms' ), __( 'Built on the design system used across GOV.UK, so forms look and behave like other UK government services.', 'holmesportfolio-forms' ) ), 'bootstrap' => array( __( 'Bootstrap 5', 'holmesportfolio-forms' ), __( 'Use this if your site already runs on Bootstrap.', 'holmesportfolio-forms' ) ), 'plain' => array( __( 'Plain', 'holmesportfolio-forms' ), __( 'A tidy built-in look with rounded fields and a solid button. It does not depend on your theme.', 'holmesportfolio-forms' ) ), 'none' => array( __( 'Inherit my theme', 'holmesportfolio-forms' ), __( 'No form styles at all. Your theme decides how forms look.', 'holmesportfolio-forms' ) ), ); $this->open_form( 'style' ); echo '

' . esc_html__( 'How should your forms look?', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'One style applies to every form on the site. There is no per-form override. Set it here and change it any time in Settings.', 'holmesportfolio-forms' ) . '

'; echo '
    '; foreach ( $styles as $value => $pair ) { echo '
  • '; } echo '
'; $this->close_form( $this->previous_step( 'style' ) ); } private function step_privacy() { $store_mode = (string) $this->saved_setting( 'store_mode', 'all' ); $days = (int) $this->saved_setting( 'retention_days', 0 ); $bounds = HP_Forms_Storage::retention_bounds(); $days = $days >= 1 ? $days : $bounds['default']; $store_ip = (bool) $this->saved_setting( 'store_ip', false ); $this->open_form( 'privacy' ); echo '

' . esc_html__( 'What the site keeps', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'These choices decide how much personal data your site holds and for how long. Check them against how your organisation works and the rules that apply to it. This is guidance, not legal advice.', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'Store submissions', 'holmesportfolio-forms' ) . '

'; echo '
    '; $store_modes = array( 'all' => __( 'Keep a copy of every form’s answers', 'holmesportfolio-forms' ), 'per_form' => __( 'Choose form by form (you pick the forms in Settings, under Data & privacy)', 'holmesportfolio-forms' ), 'never' => __( 'Keep no answers on this site. Emails and connections still work.', 'holmesportfolio-forms' ), ); foreach ( $store_modes as $value => $label ) { echo '
  • '; } echo '
'; echo '

' . esc_html__( 'Any email you add to a form is sent whichever you pick. This only decides whether a copy of the answers stays in your site’s database. If you keep none, the delete-after setting below has nothing to do.', 'holmesportfolio-forms' ) . '

'; echo '
'; echo '

' . esc_html__( 'Delete entries after', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'days', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html( sprintf( /* translators: 1: minimum days, 2: maximum days */ __( 'A daily job removes stored entries older than this. The allowed range is %1$d–%2$d days. Retention always applies, so there is no “keep forever”. UK data protection law says personal data must not be kept longer than you need it. Guidance on storage limitation from the Information Commission’s Office (ICO) explains how to decide. This is guidance, not legal advice.', 'holmesportfolio-forms' ), $bounds['min'], $bounds['max'] ) ) . '

'; echo '
'; echo '
'; echo '

' . esc_html__( 'IP addresses', 'holmesportfolio-forms' ) . '

'; echo '

'; echo '

' . esc_html__( 'Off by default, and only kept when submissions are stored at all. An IP address is personal data under UK data protection law. Turn this on only if you have a specific reason, for example fraud investigation, and your privacy notice covers it. This is guidance, not legal advice.', 'holmesportfolio-forms' ) . '

'; echo '
'; echo '
'; echo '

' . esc_html__( 'Encryption at rest', 'holmesportfolio-forms' ) . '

'; if ( HP_Forms_Crypt::is_active() ) { /* translators: %s: where the key comes from: constant, env or filter */ echo '

' . esc_html( sprintf( __( 'Encryption is on. The key comes from: %s.', 'holmesportfolio-forms' ), HP_Forms_Crypt::source_name() ) ) . '

'; if ( ! extension_loaded( 'sodium' ) ) { echo '

' . esc_html__( 'Encryption still works, but this server\'s PHP is missing the sodium extension, so opening, listing, searching and exporting entries can be much slower. Ask your host to turn on the PHP sodium extension, which comes as standard with PHP 7.2 and later.', 'holmesportfolio-forms' ) . '

'; } } else { echo '

' . esc_html__( 'Encryption is off.', 'holmesportfolio-forms' ) . '

'; } echo '

' . esc_html__( 'Encryption seals stored answers, so a copy of the database, or a backup of it alone, can’t be read without the key. It won’t stop someone who can read your site’s files or run code on the server, because they can read the key too. Staff with access to entries still see the answers. It adds to a secure host, updates and two-step sign-in. It doesn’t replace them.', 'holmesportfolio-forms' ) . '

'; if ( ! HP_Forms_Crypt::is_active() ) { echo wp_kses( HP_Forms_Admin::encryption_key_box_html(), HP_Forms_Kses::admin() ); echo wp_kses( '

' . esc_html__( 'A server environment variable keeps the key off the site’s own files entirely, which is stronger than the line above.', 'holmesportfolio-forms' ) . ( class_exists( 'HP_Forms_Admin' ) ? HP_Forms_Admin::guide_link_html( 'encryption-key-source' ) : '' ) . '

', HP_Forms_Kses::admin() ); } echo '
'; $this->close_form( $this->previous_step( 'privacy' ) ); } private function step_email() { $from_email = (string) $this->saved_setting( 'from_email', '' ); $prefix = (string) $this->saved_setting( 'ref_prefix', 'ENQ' ); $this->open_form( 'email' ); echo '

' . esc_html__( 'Where system emails come from', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'These two apply to every form. Each form can change them for itself.', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'Default “from” address', 'holmesportfolio-forms' ) . '

'; echo '

'; /* translators: %s: an example email address */ echo '

' . esc_html( sprintf( __( 'For example, %s', 'holmesportfolio-forms' ), 'noreply@' . wp_parse_url( home_url(), PHP_URL_HOST ) ) ) . '

'; echo '

' . esc_html__( 'The “from” address on every notification, so a private inbox does not show as the sender. Use an address your site is allowed to send as. Your domain’s Sender Policy Framework (SPF) and DomainKeys Identified Mail (DKIM) records decide this. A “no-reply” address on your own domain is a common choice. Leave it blank to use the WordPress admin address.', 'holmesportfolio-forms' ) . '

'; $test_notice = get_transient( 'holmesforms_test_mail_' . get_current_user_id() ); if ( $test_notice ) { delete_transient( 'holmesforms_test_mail_' . get_current_user_id() ); echo '

' . esc_html( $test_notice ) . '

'; } /* translators: %s: the signed-in user's email address */ echo '

'; echo '

' . esc_html__( 'Reference prefix', 'holmesportfolio-forms' ) . '

'; echo '

'; echo '

' . esc_html( sprintf( /* translators: %s: example reference */ __( 'Every submission gets a reference like %s. It appears in the entry, the spreadsheet export and any email that uses the [reference] token. The number only counts up, so a reference is never reused. Each form can set its own prefix and keep its own count.', 'holmesportfolio-forms' ), ( $prefix ? $prefix : 'ENQ' ) . '-000042' ) ) . '

'; $this->close_form( $this->previous_step( 'email' ) ); } private function step_spam() { $current = (string) $this->saved_setting( 'spam_provider', 'none' ); $providers = array( 'none' => __( 'None (use the built-in checks only)', 'holmesportfolio-forms' ), 'turnstile' => __( 'Cloudflare Turnstile', 'holmesportfolio-forms' ), 'recaptcha' => __( 'Google reCAPTCHA', 'holmesportfolio-forms' ), ); $this->open_form( 'spam' ); echo '

' . esc_html__( 'Spam', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'A honeypot, a timing check and a rate limit run on every form. Visitors do not see them. You can add a challenge on top of that.', 'holmesportfolio-forms' ) . '

'; echo '
    '; foreach ( $providers as $value => $label ) { echo '
  • '; } echo '
'; echo '

' . esc_html__( 'The GOV.UK Service Manual advises against CAPTCHA tests. They can turn real people away, disabled and less confident users most of all. Choose None unless you have a specific reason. If you pick one, add its keys in Settings → Spam.', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'Both use cookies or similar technology.', 'holmesportfolio-forms' ) . ' ' . esc_html__( 'Turnstile and reCAPTCHA load from Cloudflare or Google, read information from your visitors\' browsers and can set cookies. HolmesPortfolio Forms sets none of its own.', 'holmesportfolio-forms' ) . '

'; $this->close_form( $this->previous_step( 'spam' ) ); } /** The Starter forms step. Every group is optional. */ private function step_forms() { require_once HOLMESFORMS_DIR . 'includes/class-hp-forms-plugin.php'; $this->open_form( 'forms' ); echo '

' . esc_html__( 'Starter forms', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'Ready-made forms, grouped by what they are for. Councils have their own set. Each form says who it suits. Tick the forms you want, or tick a group heading to pick every form in it. You can leave them all unticked. Their emails go to your site admin address until you change it on the form. Open a form afterwards to see how it goes together, copy the parts you want, or clear it out.', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'Each form is an example, written in good faith. It is not legal advice. Check it and set your own before you publish.', 'holmesportfolio-forms' ) . '

'; HP_Forms_Admin::render_starter_picker( false ); echo '

' . esc_html__( 'Add or reset these any time from Settings → General → Restore starter content.', 'holmesportfolio-forms' ) . '

'; $order = array_keys( $this->steps() ); $forms_label = ( 'forms' === end( $order ) ) ? __( 'Finish', 'holmesportfolio-forms' ) : __( 'Continue', 'holmesportfolio-forms' ); $this->close_form( $this->previous_step( 'forms' ), $forms_label ); } private function apply_forms_step( array $input ) { $posted = ( isset( $input['pick'] ) && is_array( $input['pick'] ) ) ? array_map( 'sanitize_text_field', $input['pick'] ) : array(); if ( ! $posted ) { return; } require_once HOLMESFORMS_DIR . 'includes/class-hp-forms-plugin.php'; HP_Forms_Plugin::restore_picked( $posted, ! empty( $input['ack'] ), true ); } private function step_updates() { require_once HOLMESFORMS_DIR . 'includes/class-hp-forms-leads.php'; $this->open_form( 'updates' ); echo '

' . esc_html__( 'Sign up with HolmesPortfolio?', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'This is optional. Leave the boxes unticked and press Finish to skip it.', 'holmesportfolio-forms' ) . '

'; $user = wp_get_current_user(); HP_Forms_Leads::render_fields( $user instanceof WP_User ? $user->user_email : '' ); $this->close_form( $this->previous_step( 'updates' ), __( 'Finish', 'holmesportfolio-forms' ) ); } private function apply_updates_step( array $input ) { require_once HOLMESFORMS_DIR . 'includes/class-hp-forms-leads.php'; HP_Forms_Leads::handle_submit( $input ); } private function step_done() { update_option( self::DONE, 1, false ); echo '
'; echo '

' . esc_html__( 'That’s the groundwork done', 'holmesportfolio-forms' ) . '

'; echo '

' . esc_html__( 'Your choices are saved. You can change any of them in Settings whenever you like. Check them against how your organisation works before you publish a form.', 'holmesportfolio-forms' ) . '

'; echo '

'; echo '' . esc_html__( 'Build a form', 'holmesportfolio-forms' ) . ' '; echo '' . esc_html__( 'Go to Forms', 'holmesportfolio-forms' ) . ''; echo '

'; echo '

' . esc_html__( 'New to the builder? The Form tutorial button on the Forms screen walks you through one topic at a time. The full', 'holmesportfolio-forms' ) . ' ' . esc_html__( 'guide is here', 'holmesportfolio-forms' ) . ' ' . esc_html__( '(opens in new tab)', 'holmesportfolio-forms' ) . '.

'; echo '
'; } /* Save */ public function handle_save() { $step = isset( $_POST['step'] ) ? sanitize_key( wp_unslash( $_POST['step'] ) ) : ''; $steps = $this->steps(); if ( ! isset( $steps[ $step ] ) ) { wp_safe_redirect( self::url() ); exit; } check_admin_referer( 'holmesforms_setup_' . $step ); if ( ! current_user_can( 'holmesforms_admin' ) ) { wp_die( esc_html__( 'Not allowed.', 'holmesportfolio-forms' ), '', array( 'response' => 403 ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- checked above. $next = $this->save_step( $step, map_deep( wp_unslash( $_POST ), 'sanitize_text_field' ) ); if ( 'email' === $step && ! empty( $_POST['send_test'] ) ) { require_once HOLMESFORMS_DIR . 'includes/class-hp-forms-mailer.php'; $to = wp_get_current_user()->user_email; $result = HP_Forms_Mailer::send_test( $to ); $message = is_wp_error( $result ) /* translators: %s: the reason the email failed */ ? sprintf( __( 'The test email could not be sent: %s', 'holmesportfolio-forms' ), $result->get_error_message() ) /* translators: %s: email address */ : sprintf( __( 'Test email sent to %s. It can take a few minutes to arrive, so check the spam folder too.', 'holmesportfolio-forms' ), $to ); set_transient( 'holmesforms_test_mail_' . get_current_user_id(), $message, self::TEST_NOTICE_SECONDS ); wp_safe_redirect( add_query_arg( 'step', 'email', self::url() ) . '#hpf-setup-test' ); exit; } wp_safe_redirect( add_query_arg( 'step', $next, self::url() ) ); exit; } public function save_step( $step, array $input ) { $steps = $this->steps(); if ( ! isset( $steps[ $step ] ) ) { return 'welcome'; } $saved = get_option( 'holmesforms_settings', array() ); $saved = is_array( $saved ) ? $saved : array(); $settings = $this->apply_step( $step, $input, $saved ); if ( $settings !== $saved ) { update_option( 'holmesforms_settings', $settings ); } $order = array_keys( $steps ); $position = array_search( $step, $order, true ); return ( false !== $position && isset( $order[ $position + 1 ] ) ) ? $order[ $position + 1 ] : 'done'; } /** @return array The settings with this step's answers applied. */ private function apply_step( $step, array $input, array $settings ) { if ( 'style' === $step ) { return self::apply_style_step( $input, $settings ); } if ( 'privacy' === $step ) { return self::apply_privacy_step( $input, $settings ); } if ( 'email' === $step ) { return self::apply_email_step( $input, $settings ); } if ( 'spam' === $step ) { return self::apply_spam_step( $input, $settings ); } if ( 'forms' === $step ) { $this->apply_forms_step( $input ); } elseif ( 'updates' === $step ) { $this->apply_updates_step( $input ); } else { do_action( 'holmesforms_setup_save_step', $step, $input ); } return $settings; } private static function apply_style_step( array $input, array $settings ) { $mode = isset( $input['style_mode'] ) ? sanitize_key( $input['style_mode'] ) : 'gds'; $settings['style_mode'] = in_array( $mode, array( 'gds', 'bootstrap', 'plain', 'none' ), true ) ? $mode : 'gds'; return $settings; } private static function apply_privacy_step( array $input, array $settings ) { $mode = isset( $input['store_mode'] ) ? sanitize_key( $input['store_mode'] ) : 'all'; $settings['store_mode'] = in_array( $mode, array( 'all', 'per_form', 'never' ), true ) ? $mode : 'all'; $bounds = HP_Forms_Storage::retention_bounds(); $days = isset( $input['retention_days'] ) ? absint( $input['retention_days'] ) : 0; $settings['retention_days'] = ( $days >= 1 ) ? (int) min( $bounds['max'], max( $bounds['min'], $days ) ) : $bounds['default']; $settings['store_ip'] = empty( $input['store_ip'] ) ? 0 : 1; return $settings; } private static function apply_email_step( array $input, array $settings ) { $from_email = isset( $input['from_email'] ) ? sanitize_email( (string) $input['from_email'] ) : ''; $settings['from_email'] = is_email( $from_email ) ? $from_email : ''; $prefix = isset( $input['ref_prefix'] ) ? strtoupper( preg_replace( '/[^A-Za-z0-9]/', '', sanitize_text_field( (string) $input['ref_prefix'] ) ) ) : ''; $settings['ref_prefix'] = '' !== $prefix ? substr( $prefix, 0, self::MAX_PREFIX_LENGTH ) : 'ENQ'; return $settings; } private static function apply_spam_step( array $input, array $settings ) { $provider = isset( $input['spam_provider'] ) ? sanitize_key( $input['spam_provider'] ) : 'none'; $settings['spam_provider'] = in_array( $provider, array( 'none', 'turnstile', 'recaptcha' ), true ) ? $provider : 'none'; return $settings; } public function handle_skip() { check_admin_referer( 'holmesforms_setup_skip' ); if ( ! current_user_can( 'holmesforms_admin' ) ) { wp_die( esc_html__( 'Not allowed.', 'holmesportfolio-forms' ), '', array( 'response' => 403 ) ); } update_option( self::DONE, 1, false ); wp_safe_redirect( admin_url( 'admin.php?page=' . HP_Forms_Admin::MENU_SLUG ) ); exit; } }