settings_repository = $settings_repository; $this->integration_status_provider = $integration_status_provider; $this->dashboard_view_model = $dashboard_view_model; $this->diagnostics_provider = $diagnostics_provider; } /** * Render the admin page. */ public function render(): void { if ( ! Capability::can_manage_settings() ) { wp_die( esc_html__( 'You do not have permission to access 2o2code Spamless Forms settings.', '2o2code-spamless-forms' ) ); } $notice = $this->maybe_handle_save(); $tab = $this->get_current_tab(); echo '
'; echo '

' . esc_html__( '2o2code Spamless Forms', '2o2code-spamless-forms' ) . '

'; if ( '' !== $notice ) { echo '

' . esc_html( $notice ) . '

'; } $this->render_tabs( $tab ); switch ( $tab ) { case self::TAB_PROTECTION: $this->render_protection(); break; case self::TAB_INTEGRATIONS: $this->render_integrations(); break; case self::TAB_ACTIVITY: $this->render_activity(); break; case self::TAB_TOOLS: $this->render_tools(); break; case self::TAB_ABOUT: $this->render_about(); break; default: $this->render_dashboard(); break; } echo '
'; } /** * Render tab navigation. * * @param string $current_tab Current tab. */ private function render_tabs( string $current_tab ): void { echo ''; } /** * Render dashboard screen. */ private function render_dashboard(): void { $data = $this->dashboard_view_model->get_data(); $status = isset( $data['protection_status'] ) && is_array( $data['protection_status'] ) ? $data['protection_status'] : $this->get_default_status(); $summary = isset( $data['summary'] ) && is_array( $data['summary'] ) ? $data['summary'] : $this->get_default_summary(); $event_summary = isset( $data['event_summary'] ) && is_array( $data['event_summary'] ) ? $data['event_summary'] : $this->get_default_event_summary(); $integrations = isset( $data['integrations'] ) && is_array( $data['integrations'] ) ? $data['integrations'] : array(); $events = isset( $data['recent_events'] ) && is_array( $data['recent_events'] ) ? $data['recent_events'] : array(); $settings = $this->settings_repository->get_settings(); $activity_text = $this->get_activity_summary_label( $event_summary, ! empty( $settings['log_allowed_events'] ) ); echo '
'; $this->render_page_header( 'twootwocode-zsf-dashboard-title', __( 'Dashboard', '2o2code-spamless-forms' ), __( 'A quick read on protection, setup health, and what to check next.', '2o2code-spamless-forms' ) ); $this->render_status_band( $status, (string) $data['strictness'] ); echo '
'; $integration_metric = $this->get_integration_metric_copy( (string) $settings['protection_mode'], $this->count_protected_integrations( $integrations ), (int) $summary['total'] ); $this->render_metric_card( $integration_metric['label'], $integration_metric['value'], $integration_metric['description'] ); $this->render_metric_card( __( 'Event logging', '2o2code-spamless-forms' ), ! empty( $settings['event_logging_enabled'] ) ? __( 'Enabled', '2o2code-spamless-forms' ) : __( 'Disabled', '2o2code-spamless-forms' ), ! empty( $settings['event_logging_enabled'] ) ? __( 'Minimal metadata can appear in Activity.', '2o2code-spamless-forms' ) : __( 'Activity will be limited while logging is off.', '2o2code-spamless-forms' ) ); $this->render_metric_card( __( 'Privacy-safe logging', '2o2code-spamless-forms' ), __( 'Minimal metadata', '2o2code-spamless-forms' ), __( 'No full submissions, raw IPs, raw emails, or raw user agents are stored.', '2o2code-spamless-forms' ) ); $this->render_metric_card( __( 'Recent activity', '2o2code-spamless-forms' ), $activity_text, __( 'Counts come from stored events only.', '2o2code-spamless-forms' ) ); echo '
'; $this->render_next_action( $status, $summary, $events, $settings ); $this->render_onboarding_checklist(); echo '
'; echo '
'; echo '

' . esc_html__( 'Integration status', '2o2code-spamless-forms' ) . '

'; $this->render_dashboard_integration_summary( $integrations, (string) $settings['protection_mode'] ); echo '
'; echo '
'; echo '

' . esc_html__( 'Recent Activity preview', '2o2code-spamless-forms' ) . '

'; $this->render_recent_events( $events ); echo '

' . esc_html__( 'View activity', '2o2code-spamless-forms' ) . '

'; echo '
'; echo '
'; $this->render_privacy_note( (string) $data['privacy_note'] ); $this->render_privacy_note( (string) $data['event_privacy_note'] ); echo '
'; } /** * Render protection settings screen. */ private function render_protection(): void { $settings = $this->settings_repository->get_settings(); echo '
'; $this->render_page_header( 'twootwocode-zsf-protection-title', __( 'Protection', '2o2code-spamless-forms' ), __( 'Choose how 2o2code Spamless Forms should handle suspicious submissions.', '2o2code-spamless-forms' ) ); echo '
'; $this->render_form_security_fields( self::SECTION_PROTECTION ); $this->render_protection_mode_settings( $settings ); $this->render_strictness_settings( $settings ); $this->render_logging_privacy_settings( $settings ); $this->render_advanced_settings( $settings ); echo '

'; echo '
'; echo '
'; } /** * Render integrations screen. */ private function render_integrations(): void { $settings = $this->settings_repository->get_settings(); $integrations = $this->integration_status_provider->get_statuses(); $data = $this->dashboard_view_model->get_data(); $events = isset( $data['activity_events'] ) && is_array( $data['activity_events'] ) ? $data['activity_events'] : array(); echo '
'; $this->render_page_header( 'twootwocode-zsf-integrations-title', __( 'Integrations', '2o2code-spamless-forms' ), __( 'Review what is detected and choose which Free integrations should be protected.', '2o2code-spamless-forms' ) ); if ( ! $this->has_detected_external_integration( $integrations ) ) { $this->render_empty_state( __( 'No supported form plugin has been detected yet.', '2o2code-spamless-forms' ), __( 'Native WordPress comments, login, and registration can still be protected when they are available and enabled.', '2o2code-spamless-forms' ) ); } echo '
'; $this->render_form_security_fields( self::SECTION_INTEGRATIONS ); $this->render_integration_management_cards( $integrations, $settings, $events ); echo '

'; echo '
'; echo '
'; } /** * Render activity screen. */ private function render_activity(): void { $data = $this->dashboard_view_model->get_data(); $events = isset( $data['activity_events'] ) && is_array( $data['activity_events'] ) ? $data['activity_events'] : array(); $event_summary = isset( $data['event_summary'] ) && is_array( $data['event_summary'] ) ? $data['event_summary'] : $this->get_default_event_summary(); $settings = $this->settings_repository->get_settings(); $show_allowed = ! empty( $settings['log_allowed_events'] ); echo '
'; $this->render_page_header( 'twootwocode-zsf-activity-title', __( 'Activity', '2o2code-spamless-forms' ), $show_allowed ? __( 'Recent allowed, blocked, and suspicious decisions from privacy-safe event logging.', '2o2code-spamless-forms' ) : __( 'Recent blocked and suspicious decisions from privacy-safe event logging.', '2o2code-spamless-forms' ) ); echo '
'; $this->render_metric_card( __( 'Blocked', '2o2code-spamless-forms' ), (string) (int) $event_summary['blocked'], __( 'High-confidence spam that was blocked.', '2o2code-spamless-forms' ) ); $this->render_metric_card( __( 'Suspicious', '2o2code-spamless-forms' ), (string) (int) $event_summary['suspicious'], __( 'Submissions flagged without being blocked.', '2o2code-spamless-forms' ) ); if ( $show_allowed ) { $this->render_metric_card( __( 'Allowed', '2o2code-spamless-forms' ), (string) (int) $event_summary['allowed'], __( 'Successful submissions recorded for temporary troubleshooting.', '2o2code-spamless-forms' ) ); } $this->render_metric_card( __( 'Retention period', '2o2code-spamless-forms' ), sprintf( '%d', (int) $settings['event_retention_days'] ), __( 'Days that minimal Activity metadata is kept.', '2o2code-spamless-forms' ) ); echo '
'; if ( empty( $settings['event_logging_enabled'] ) ) { $this->render_empty_state( __( 'Event logging is disabled.', '2o2code-spamless-forms' ), __( 'Activity will be limited until minimal event logging is enabled on the Protection page.', '2o2code-spamless-forms' ) ); } $this->render_recent_events( $events ); $this->render_privacy_note( __( 'Activity never shows full submissions, raw IP addresses, raw emails, raw user agents, tokens, secrets, or stored hashes.', '2o2code-spamless-forms' ) ); echo '
'; } /** * Render tools screen. */ private function render_tools(): void { $data = $this->diagnostics_provider->get_data(); echo '
'; $this->render_page_header( 'twootwocode-zsf-tools-title', __( 'Tools', '2o2code-spamless-forms' ), __( 'Support-safe diagnostics for the Free build.', '2o2code-spamless-forms' ) ); $this->render_privacy_note( __( 'Diagnostics show safe configuration details only. They do not include secrets, raw submissions, raw IP addresses, raw emails, raw user agents, tokens, or stored hashes.', '2o2code-spamless-forms' ) ); echo '
'; echo '
'; echo '

' . esc_html__( 'System information', '2o2code-spamless-forms' ) . '

'; echo ''; $this->render_diagnostic_row( __( 'Plugin version', '2o2code-spamless-forms' ), (string) $data['plugin_version'] ); $this->render_diagnostic_row( __( 'WordPress version', '2o2code-spamless-forms' ), '' === $data['wordpress_version'] ? __( 'Unavailable in this context', '2o2code-spamless-forms' ) : (string) $data['wordpress_version'] ); $this->render_diagnostic_row( __( 'PHP version', '2o2code-spamless-forms' ), (string) $data['php_version'] ); $this->render_diagnostic_row( __( 'Build profile', '2o2code-spamless-forms' ), (string) $data['build_profile'] ); $this->render_diagnostic_row( __( 'Composer autoload', '2o2code-spamless-forms' ), ! empty( $data['vendor_autoload_available'] ) ? __( 'Available', '2o2code-spamless-forms' ) : __( 'Not available', '2o2code-spamless-forms' ) ); echo '
'; echo '
'; echo '
'; echo '

' . esc_html__( 'Runtime state', '2o2code-spamless-forms' ) . '

'; echo ''; $this->render_diagnostic_row( __( 'Protection mode', '2o2code-spamless-forms' ), $this->get_mode_label( (string) $data['protection_mode'] ) ); $this->render_diagnostic_row( __( 'Strictness', '2o2code-spamless-forms' ), $this->get_strictness_label( (string) $data['strictness'] ) ); $this->render_diagnostic_row( __( 'Event logging', '2o2code-spamless-forms' ), ! empty( $data['event_logging_enabled'] ) ? __( 'Enabled', '2o2code-spamless-forms' ) : __( 'Disabled', '2o2code-spamless-forms' ) ); $this->render_diagnostic_row( __( 'Allowed-submission logging', '2o2code-spamless-forms' ), ! empty( $data['log_allowed_events'] ) ? __( 'Enabled', '2o2code-spamless-forms' ) : __( 'Disabled', '2o2code-spamless-forms' ) ); $this->render_diagnostic_row( __( 'Stored event count', '2o2code-spamless-forms' ), (string) (int) $data['stored_event_count'] ); $this->render_diagnostic_row( __( 'Runtime PRO modules', '2o2code-spamless-forms' ), (string) (int) $data['pro_module_count_loaded'] ); echo '
'; echo '
'; echo '
'; echo '

' . esc_html__( 'Integration availability', '2o2code-spamless-forms' ) . '

'; $this->render_integration_table( isset( $data['integrations'] ) && is_array( $data['integrations'] ) ? $data['integrations'] : array(), (string) $data['protection_mode'] ); echo '

' . esc_html__( 'Package and runtime safety', '2o2code-spamless-forms' ) . '

'; echo ''; echo '

' . esc_html__( 'Required PHP extensions', '2o2code-spamless-forms' ) . '

'; echo ''; $extensions = isset( $data['php_extensions'] ) && is_array( $data['php_extensions'] ) ? $data['php_extensions'] : array(); foreach ( $extensions as $extension => $available ) { $this->render_diagnostic_row( (string) $extension, ! empty( $available ) ? __( 'Available', '2o2code-spamless-forms' ) : __( 'Missing', '2o2code-spamless-forms' ) ); } echo '
'; echo '
'; } /** * Render About screen. */ private function render_about(): void { echo '
'; $this->render_page_header( 'twootwocode-zsf-about-title', __( 'About 2o2code Spamless Forms', '2o2code-spamless-forms' ), __( 'Local, no-API, no-CAPTCHA anti-spam protection for WordPress forms.', '2o2code-spamless-forms' ) ); echo '
'; echo '
'; echo '

' . esc_html__( 'What it does', '2o2code-spamless-forms' ) . '

'; echo '

' . esc_html__( '2o2code Spamless Forms checks submissions on this site. It does not require an external anti-spam account, CAPTCHA challenge, tracking pixel, or remote scoring API.', '2o2code-spamless-forms' ) . '

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

' . esc_html__( 'Privacy first', '2o2code-spamless-forms' ) . '

'; echo '

' . esc_html__( 'When Activity logging is enabled, 2o2code Spamless Forms stores minimal event metadata. Full submissions, raw IP addresses, raw emails, and raw user agents are not stored.', '2o2code-spamless-forms' ) . '

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

' . esc_html__( 'Included Free features', '2o2code-spamless-forms' ) . '

'; echo ''; echo '

' . esc_html__( 'Supported Free integrations', '2o2code-spamless-forms' ) . '

'; echo ''; echo '

' . esc_html__( 'Ongoing maintenance', '2o2code-spamless-forms' ) . '

'; echo '

' . esc_html__( '2o2code Spamless Forms is actively maintained for supported WordPress versions and form integrations. Compatibility, security, privacy, and usability improvements are prioritized as WordPress and supported plugins evolve.', '2o2code-spamless-forms' ) . '

'; echo '
'; } /** * Render protection mode settings. * * @param array $settings Settings. */ private function render_protection_mode_settings( array $settings ): void { echo '
'; echo '' . esc_html__( 'Mode', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html__( 'Choose whether to block spam, observe decisions without blocking, or temporarily disable protection.', '2o2code-spamless-forms' ) . '

'; $this->render_radio_cards( 'protection_mode', (string) $settings['protection_mode'], array( SettingsSnapshot::MODE_PROTECT => array( 'label' => __( 'Protect', '2o2code-spamless-forms' ), 'description' => __( 'Blocks high-confidence spam. Activity is recorded only when event logging is enabled.', '2o2code-spamless-forms' ), ), SettingsSnapshot::MODE_MONITOR => array( 'label' => __( 'Monitor', '2o2code-spamless-forms' ), 'description' => __( 'Observes spam-related decisions without blocking submissions. Event logging must be enabled for Activity.', '2o2code-spamless-forms' ), ), SettingsSnapshot::MODE_DISABLED => array( 'label' => __( 'Disabled', '2o2code-spamless-forms' ), 'description' => __( 'Allows all submissions and creates no new Activity events. Use only while troubleshooting.', '2o2code-spamless-forms' ), ), ) ); if ( SettingsSnapshot::MODE_DISABLED === (string) $settings['protection_mode'] ) { $this->render_warning_note( __( 'Disabled mode turns protection and new event logging off. Strictness and threshold settings remain saved but do not affect submissions until protection is enabled again.', '2o2code-spamless-forms' ) ); } echo '
'; } /** * Render strictness settings. * * @param array $settings Settings. */ private function render_strictness_settings( array $settings ): void { echo '
'; echo '' . esc_html__( 'Strictness', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html__( 'Strictness controls timing-only signals. Hard spam signals are blocked in Protect, recorded in Monitor, and ignored in Disabled mode.', '2o2code-spamless-forms' ) . '

'; $this->render_radio_cards( 'strictness', (string) $settings['strictness'], array( SettingsSnapshot::STRICTNESS_SAFE => array( 'label' => __( 'Safe', '2o2code-spamless-forms' ), 'description' => __( 'Ignores timing-only signals for the lowest false-positive risk.', '2o2code-spamless-forms' ), ), SettingsSnapshot::STRICTNESS_BALANCED => array( 'label' => __( 'Balanced', '2o2code-spamless-forms' ), 'description' => __( 'Records timing-only signals as Suspicious without blocking. Recommended for most sites.', '2o2code-spamless-forms' ), ), SettingsSnapshot::STRICTNESS_STRICT => array( 'label' => __( 'Strict', '2o2code-spamless-forms' ), 'description' => __( 'Blocks submissions completed below the minimum fill time in Protect mode. Test carefully before using on busy forms.', '2o2code-spamless-forms' ), ), ) ); echo '
'; } /** * Render logging and privacy settings. * * @param array $settings Settings. */ private function render_logging_privacy_settings( array $settings ): void { echo '
'; echo '' . esc_html__( 'Logging and privacy', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html__( 'Event logging stores minimal metadata only. Full submissions, raw IP addresses, raw emails, and raw user agents are not stored.', '2o2code-spamless-forms' ) . '

'; $this->render_checkbox_field( 'event_logging_enabled', __( 'Enable minimal event logging', '2o2code-spamless-forms' ), ! empty( $settings['event_logging_enabled'] ), __( 'Records Blocked and Suspicious decisions as minimal Activity metadata. Allowed decisions require both this setting and the separate option below.', '2o2code-spamless-forms' ) ); $this->render_number_field( 'event_retention_days', __( 'Retention days', '2o2code-spamless-forms' ), (string) $settings['event_retention_days'], 1, 90, '1', __( 'Events older than this are removed when new events are written.', '2o2code-spamless-forms' ) ); $this->render_number_field( 'max_event_records', __( 'Maximum stored events', '2o2code-spamless-forms' ), (string) $settings['max_event_records'], 25, 1000, '1', __( 'Limits how many Activity events are kept.', '2o2code-spamless-forms' ) ); $this->render_checkbox_field( 'log_allowed_events', __( 'Log allowed submissions', '2o2code-spamless-forms' ), ! empty( $settings['log_allowed_events'] ), __( 'Records successful submissions as Allowed in Activity. Requires event logging and Protect or Monitor mode.', '2o2code-spamless-forms' ) ); if ( empty( $settings['event_logging_enabled'] ) ) { $this->render_warning_note( __( 'Event logging is off, so no new Activity is created. Allowed-submission logging has no effect. Retention and maximum-event settings remain saved, and existing Activity remains subject to those limits.', '2o2code-spamless-forms' ) ); } $this->render_checkbox_field( 'delete_data_on_uninstall', __( 'Delete data on uninstall', '2o2code-spamless-forms' ), ! empty( $settings['delete_data_on_uninstall'] ), __( 'When enabled, plugin settings and minimal event metadata are removed when the plugin is deleted.', '2o2code-spamless-forms' ) ); echo '
'; } /** * Render advanced settings. * * @param array $settings Settings. */ private function render_advanced_settings( array $settings ): void { echo '
'; echo '' . esc_html__( 'Advanced thresholds', '2o2code-spamless-forms' ) . ''; echo '
'; echo '' . esc_html__( 'Advanced', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html__( 'These defaults are intentionally conservative. Change them only when a site needs different sensitivity.', '2o2code-spamless-forms' ) . '

'; $this->render_number_field( 'minimum_fill_time_seconds', __( 'Minimum fill time', '2o2code-spamless-forms' ), (string) $settings['minimum_fill_time_seconds'], 1, 60, '1', __( 'Shortest expected time, in seconds, for a human submission.', '2o2code-spamless-forms' ) ); $this->render_number_field( 'token_lifetime_seconds', __( 'Token lifetime', '2o2code-spamless-forms' ), (string) $settings['token_lifetime_seconds'], 60, 86400, '60', __( 'How long a generated form token remains valid.', '2o2code-spamless-forms' ) ); $this->render_number_field( 'rate_limit_window_seconds', __( 'Rate limit window', '2o2code-spamless-forms' ), (string) $settings['rate_limit_window_seconds'], 60, 86400, '60', __( 'Time window used for local rate-limit checks.', '2o2code-spamless-forms' ) ); $this->render_number_field( 'rate_limit_threshold', __( 'Rate limit threshold', '2o2code-spamless-forms' ), (string) $settings['rate_limit_threshold'], 1, 1000, '1', __( 'Maximum allowed submissions in the rate-limit window.', '2o2code-spamless-forms' ) ); $this->render_number_field( 'link_density_threshold', __( 'Link density threshold', '2o2code-spamless-forms' ), (string) $settings['link_density_threshold'], 0.05, 1, '0.05', __( 'Maximum link density before a submission is treated as suspicious.', '2o2code-spamless-forms' ) ); echo '
'; echo '
'; } /** * Render form security fields. * * @param string $section Settings section. */ private function render_form_security_fields( string $section ): void { if ( function_exists( 'wp_nonce_field' ) ) { wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); } echo ''; echo ''; } /** * Render a page title and description. * * @param string $id Heading ID. * @param string $title Title. * @param string $description Description. */ private function render_page_header( string $id, string $title, string $description ): void { echo '
'; echo '

' . esc_html( $title ) . '

'; echo '

' . esc_html( $description ) . '

'; echo '
'; } /** * Render the main dashboard status band. * * @param array $status Status data. * @param string $strictness Strictness label. */ private function render_status_band( array $status, string $strictness ): void { $status_key = isset( $status['key'] ) && is_scalar( $status['key'] ) ? (string) $status['key'] : 'protect'; $headline = isset( $status['headline'] ) && is_scalar( $status['headline'] ) ? (string) $status['headline'] : __( 'Protection is active.', '2o2code-spamless-forms' ); $description = isset( $status['description'] ) && is_scalar( $status['description'] ) ? (string) $status['description'] : __( '2o2code Spamless Forms is protecting enabled integrations with local checks.', '2o2code-spamless-forms' ); $mode_label = isset( $status['label'] ) && is_scalar( $status['label'] ) ? (string) $status['label'] : __( 'Protect', '2o2code-spamless-forms' ); $sanitized_status_key = preg_replace( '/[^a-z0-9_-]/', '', strtolower( $status_key ) ); $status_key = is_string( $sanitized_status_key ) && '' !== $sanitized_status_key ? $sanitized_status_key : 'protect'; $status_class = 'twootwocode-zsf-admin__status-band twootwocode-zsf-admin__status-band--' . $status_key; echo '
'; echo '
'; echo '' . esc_html__( 'Current protection', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html( $headline ) . '

'; echo '

' . esc_html( $description ) . '

'; echo '
'; echo '
'; echo '
' . esc_html__( 'Mode', '2o2code-spamless-forms' ) . '
' . esc_html( $mode_label ) . '
'; echo '
' . esc_html__( 'Strictness', '2o2code-spamless-forms' ) . '
' . esc_html( $strictness ) . '
'; echo '
'; echo '
'; } /** * Render the next recommended action panel. * * @param array $status Protection status. * @param array $summary Summary. * @param array> $events Events. * @param array $settings Settings. */ private function render_next_action( array $status, array $summary, array $events, array $settings ): void { $action = $this->get_next_recommended_action( $status, $summary, $events, $settings ); echo '
'; echo '' . esc_html__( 'Next recommended action', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html( $action['title'] ) . '

'; echo '

' . esc_html( $action['description'] ) . '

'; echo '

'; echo '' . esc_html( $action['button'] ) . ''; echo '' . esc_html__( 'Check integrations', '2o2code-spamless-forms' ) . ''; echo '

'; echo '
'; } /** * Render a lightweight onboarding checklist. */ private function render_onboarding_checklist(): void { echo '
'; echo '

' . esc_html__( 'First-run checklist', '2o2code-spamless-forms' ) . '

'; echo '
    '; echo '
  1. ' . esc_html__( 'Review detected integrations.', '2o2code-spamless-forms' ) . '
  2. '; echo '
  3. ' . esc_html__( 'Keep the recommended defaults unless this is a sensitive form.', '2o2code-spamless-forms' ) . '
  4. '; echo '
  5. ' . esc_html__( 'Submit the form once with normal test content.', '2o2code-spamless-forms' ) . '
  6. '; echo '
  7. ' . esc_html__( 'Submit again with obvious spam-like content and confirm Activity is recorded.', '2o2code-spamless-forms' ) . '
  8. '; echo '
  9. ' . esc_html__( 'Use Monitor first if this form is business-critical.', '2o2code-spamless-forms' ) . '
  10. '; echo '
'; echo '
'; } /** * Render dashboard integration summary. * * @param array> $integrations Integrations. * @param string $protection_mode Protection mode. */ private function render_dashboard_integration_summary( array $integrations, string $protection_mode ): void { if ( array() === $integrations ) { $this->render_empty_state( __( 'No integration status is available yet.', '2o2code-spamless-forms' ), __( 'Open Integrations after activation to review supported Free sources.', '2o2code-spamless-forms' ) ); return; } echo '
    '; foreach ( $integrations as $integration ) { $state = $this->get_integration_state_label( $integration, $protection_mode ); echo '
  • '; echo '' . esc_html( (string) $integration['label'] ) . ''; echo '' . esc_html( $state ) . ''; echo '
  • '; } echo '
'; } /** * Render integration management cards. * * @param array> $integrations Integrations. * @param array $settings Settings. * @param array> $events Recent events. */ private function render_integration_management_cards( array $integrations, array $settings, array $events ): void { if ( array() === $integrations ) { $this->render_empty_state( __( 'No Free integration status is available yet.', '2o2code-spamless-forms' ), __( 'The Free version supports Contact Form 7, WPForms Lite, Elementor Form Lite, WordPress comments, WordPress login, and WordPress registration.', '2o2code-spamless-forms' ) ); return; } echo '
'; foreach ( $integrations as $integration ) { $setting_key = isset( $integration['setting_key'] ) ? (string) $integration['setting_key'] : ''; if ( '' === $setting_key ) { continue; } $key = (string) $integration['key']; $last_event = $this->get_latest_event_for_source( $events, $key ); $is_enabled = ! empty( $settings[ $setting_key ] ); $is_detected = ! empty( $integration['available'] ); echo '
'; echo '
'; echo '

' . esc_html( (string) $integration['label'] ) . '

'; echo '
'; echo '

'; $this->render_status_pill( $is_detected ? __( 'Detected', '2o2code-spamless-forms' ) : __( 'Not detected', '2o2code-spamless-forms' ), $is_detected ); $this->render_status_pill( $is_enabled ? __( 'Enabled', '2o2code-spamless-forms' ) : __( 'Disabled', '2o2code-spamless-forms' ), $is_enabled ); echo '

'; echo '

' . esc_html( $this->get_integration_state_label( $integration, (string) $settings['protection_mode'] ) ) . '

'; echo '

' . esc_html( $this->get_integration_helper_text( $key ) ) . '

'; echo '

' . esc_html( $this->get_last_event_label( $last_event ) ) . '

'; echo ''; echo '
'; echo '' . esc_html__( 'How to test', '2o2code-spamless-forms' ) . ''; echo '

' . esc_html__( 'Submit the form once with normal test content.', '2o2code-spamless-forms' ) . '

'; echo '

' . esc_html__( 'Submit again with obvious spam-like content and confirm the activity is recorded.', '2o2code-spamless-forms' ) . '

'; echo '
'; echo '
'; } if ( $this->is_free_build() ) { $this->render_elementor_pro_information_card(); } echo '
'; } /** * Render the non-interactive Elementor Pro boundary note in Free. */ private function render_elementor_pro_information_card(): void { echo '
'; echo '
'; echo '

' . esc_html__( 'Elementor Pro Forms', '2o2code-spamless-forms' ) . '

'; echo '
'; echo '

'; $this->render_status_pill( __( 'Planned for PRO', '2o2code-spamless-forms' ), false ); echo '

'; echo '

' . esc_html__( 'Elementor Pro Forms protection is planned for a future PRO release.', '2o2code-spamless-forms' ) . '

'; echo '

' . esc_html__( 'Elementor Form Lite is included in Free. Elementor Atomic Forms are not supported.', '2o2code-spamless-forms' ) . '

'; echo '
'; } /** * Render safe recent event rows. * * @param array> $events Recent events. */ private function render_recent_events( array $events ): void { if ( array() === $events ) { $this->render_empty_state( __( 'No activity to display yet.', '2o2code-spamless-forms' ), __( 'Recent decisions will appear here when their logging options are enabled. This is normal for a new install or a quiet site.', '2o2code-spamless-forms' ) ); return; } echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $events as $event ) { $reason_labels = isset( $event['reason_labels'] ) && is_array( $event['reason_labels'] ) ? $event['reason_labels'] : array(); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'Date', '2o2code-spamless-forms' ) . '' . esc_html__( 'Source', '2o2code-spamless-forms' ) . '' . esc_html__( 'Action', '2o2code-spamless-forms' ) . '' . esc_html__( 'Reason', '2o2code-spamless-forms' ) . '' . esc_html__( 'Score', '2o2code-spamless-forms' ) . '
' . esc_html( $this->format_event_date( isset( $event['created_at'] ) ? (int) $event['created_at'] : 0 ) ) . '' . esc_html( $this->get_event_source_label( isset( $event['source_key'] ) ? (string) $event['source_key'] : '' ) ) . '' . esc_html( $this->get_event_action_label( isset( $event['action'] ) ? (string) $event['action'] : '' ) ) . '' . esc_html( implode( ', ', array_map( 'strval', $reason_labels ) ) ) . '' . esc_html( (string) ( isset( $event['score'] ) ? (int) $event['score'] : 0 ) ) . '
'; echo '
'; } /** * Render integration diagnostics table. * * @param array> $integrations Integrations. * @param string $protection_mode Protection mode. */ private function render_integration_table( array $integrations, string $protection_mode ): void { if ( array() === $integrations ) { $this->render_empty_state( __( 'Integration status is unavailable.', '2o2code-spamless-forms' ), __( 'Refresh this page. If the problem continues, verify that 2o2code Spamless Forms is active.', '2o2code-spamless-forms' ) ); return; } echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $integrations as $integration ) { echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'Integration', '2o2code-spamless-forms' ) . '' . esc_html__( 'Source key', '2o2code-spamless-forms' ) . '' . esc_html__( 'Detected', '2o2code-spamless-forms' ) . '' . esc_html__( 'Enabled', '2o2code-spamless-forms' ) . '' . esc_html__( 'Integration state', '2o2code-spamless-forms' ) . '
' . esc_html( (string) $integration['label'] ) . '' . esc_html( (string) $integration['key'] ) . '' . esc_html( ! empty( $integration['available'] ) ? __( 'Yes', '2o2code-spamless-forms' ) : __( 'No', '2o2code-spamless-forms' ) ) . '' . esc_html( ! empty( $integration['enabled'] ) ? __( 'Yes', '2o2code-spamless-forms' ) : __( 'No', '2o2code-spamless-forms' ) ) . '' . esc_html( $this->get_integration_state_label( $integration, $protection_mode ) ) . '
'; echo '
'; } /** * Build a concise activity summary from visible event classes. * * @param array $summary Event summary. * @param bool $show_allowed Whether allowed events are visible. */ private function get_activity_summary_label( array $summary, bool $show_allowed ): string { if ( $show_allowed ) { return sprintf( /* translators: 1: blocked count, 2: suspicious count, 3: allowed count. */ __( '%1$d blocked, %2$d suspicious, %3$d allowed', '2o2code-spamless-forms' ), isset( $summary['blocked'] ) ? (int) $summary['blocked'] : 0, isset( $summary['suspicious'] ) ? (int) $summary['suspicious'] : 0, isset( $summary['allowed'] ) ? (int) $summary['allowed'] : 0 ); } return sprintf( /* translators: 1: blocked count, 2: suspicious count. */ __( '%1$d blocked, %2$d suspicious', '2o2code-spamless-forms' ), isset( $summary['blocked'] ) ? (int) $summary['blocked'] : 0, isset( $summary['suspicious'] ) ? (int) $summary['suspicious'] : 0 ); } /** * Render a metric card. * * @param string $label Metric label. * @param string $value Metric value. * @param string $description Metric description. */ private function render_metric_card( string $label, string $value, string $description = '' ): void { echo '
'; echo '' . esc_html( $label ) . ''; echo '' . esc_html( $value ) . ''; if ( '' !== $description ) { echo '' . esc_html( $description ) . ''; } echo '
'; } /** * Render radio-card controls. * * @param string $key Setting key. * @param string $value Current value. * @param array $options Options. */ private function render_radio_cards( string $key, string $value, array $options ): void { echo '
'; foreach ( $options as $option_value => $option ) { $field_id = 'twootwocode-zsf-' . str_replace( '_', '-', $key ) . '-' . str_replace( '_', '-', (string) $option_value ); echo ''; } echo '
'; } /** * Render a number field. * * @param string $key Setting key. * @param string $label Field label. * @param string $value Current value. * @param int|float $min Minimum. * @param int|float $max Maximum. * @param string $step Step. * @param string $description Description. */ private function render_number_field( string $key, string $label, string $value, $min, $max, string $step, string $description = '' ): void { echo ''; } /** * Render a checkbox field. * * @param string $key Setting key. * @param string $label Field label. * @param bool $checked Whether checked. * @param string $description Description. */ private function render_checkbox_field( string $key, string $label, bool $checked, string $description ): void { echo ''; } /** * Render a status pill. * * @param string $label Pill label. * @param bool $positive Whether status is positive. */ private function render_status_pill( string $label, bool $positive ): void { $class = $positive ? 'twootwocode-zsf-admin__pill twootwocode-zsf-admin__pill--positive' : 'twootwocode-zsf-admin__pill'; echo '' . esc_html( $label ) . ''; } /** * Render a privacy note. * * @param string $text Note text. */ private function render_privacy_note( string $text ): void { echo '

' . esc_html( $text ) . '

'; } /** * Render a warning note. * * @param string $text Note text. */ private function render_warning_note( string $text ): void { echo '

' . esc_html( $text ) . '

'; } /** * Render an empty state. * * @param string $title Title. * @param string $description Description. */ private function render_empty_state( string $title, string $description ): void { echo '
'; echo '' . esc_html( $title ) . ''; echo '

' . esc_html( $description ) . '

'; echo '
'; } /** * Render a diagnostics row. * * @param string $label Label. * @param string $value Value. */ private function render_diagnostic_row( string $label, string $value ): void { echo '' . esc_html( $label ) . '' . esc_html( $value ) . ''; } /** * Handle the settings save action. */ private function maybe_handle_save(): string { if ( 'POST' !== $this->get_request_method() ) { return ''; } if ( ! Capability::can_manage_settings() ) { wp_die( esc_html__( 'You do not have permission to change 2o2code Spamless Forms settings.', '2o2code-spamless-forms' ) ); } $action = $this->get_post_scalar( 'twootwocode_zsf_action' ); if ( self::ACTION_SAVE !== $action ) { return ''; } $nonce_is_valid = function_exists( 'wp_verify_nonce' ) && wp_verify_nonce( $this->get_post_scalar( self::NONCE_FIELD ), self::NONCE_ACTION ); if ( ! $nonce_is_valid ) { wp_die( esc_html__( 'Settings could not be verified. Please refresh and try again.', '2o2code-spamless-forms' ) ); } $section = $this->get_post_scalar( self::SECTION_FIELD ); if ( ! in_array( $section, array( self::SECTION_PROTECTION, self::SECTION_INTEGRATIONS ), true ) ) { return __( 'Settings could not be saved. Please refresh and try again.', '2o2code-spamless-forms' ); } $settings = array_merge( $this->settings_repository->get_settings(), $this->prepare_settings_for_save( $this->get_post_settings(), $section ) ); if ( ! $this->settings_repository->save( $settings ) ) { return __( 'Settings could not be saved. Please refresh and try again.', '2o2code-spamless-forms' ); } return __( 'Settings saved.', '2o2code-spamless-forms' ); } /** * Prepare known settings from posted form data. * * @param array $posted_settings Posted settings. * @param string $section Settings section. * * @return array */ private function prepare_settings_for_save( array $posted_settings, string $section ): array { if ( self::SECTION_INTEGRATIONS === $section ) { return $this->prepare_integration_settings_for_save( $posted_settings ); } return $this->prepare_protection_settings_for_save( $posted_settings ); } /** * Prepare known protection settings. * * @param array $posted_settings Posted settings. * * @return array */ private function prepare_protection_settings_for_save( array $posted_settings ): array { $known_keys = array( 'protection_mode', 'strictness', 'minimum_fill_time_seconds', 'token_lifetime_seconds', 'rate_limit_window_seconds', 'rate_limit_threshold', 'link_density_threshold', 'event_retention_days', 'max_event_records', ); $settings = array(); foreach ( $known_keys as $known_key ) { if ( isset( $posted_settings[ $known_key ] ) && is_scalar( $posted_settings[ $known_key ] ) ) { $settings[ $known_key ] = sanitize_text_field( wp_unslash( $posted_settings[ $known_key ] ) ); } } foreach ( array( 'event_logging_enabled', 'log_allowed_events', 'delete_data_on_uninstall' ) as $boolean_key ) { $settings[ $boolean_key ] = isset( $posted_settings[ $boolean_key ] ) && is_scalar( $posted_settings[ $boolean_key ] ) ? sanitize_text_field( wp_unslash( $posted_settings[ $boolean_key ] ) ) : '0'; } return $settings; } /** * Prepare known integration settings. * * @param array $posted_settings Posted settings. * * @return array */ private function prepare_integration_settings_for_save( array $posted_settings ): array { $settings = array(); foreach ( $this->integration_status_provider->get_setting_keys() as $setting_key ) { $settings[ $setting_key ] = isset( $posted_settings[ $setting_key ] ) && is_scalar( $posted_settings[ $setting_key ] ) ? sanitize_text_field( wp_unslash( $posted_settings[ $setting_key ] ) ) : '0'; } return $settings; } /** * Get supported tabs. * * @return array */ private function get_tabs(): array { return array( self::TAB_DASHBOARD => __( 'Dashboard', '2o2code-spamless-forms' ), self::TAB_PROTECTION => __( 'Protection', '2o2code-spamless-forms' ), self::TAB_INTEGRATIONS => __( 'Integrations', '2o2code-spamless-forms' ), self::TAB_ACTIVITY => __( 'Activity', '2o2code-spamless-forms' ), self::TAB_TOOLS => __( 'Tools', '2o2code-spamless-forms' ), self::TAB_ABOUT => __( 'About', '2o2code-spamless-forms' ), ); } /** * Get current tab. */ private function get_current_tab(): string { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reading a tab value does not change state. $tab = isset( $_GET['tab'] ) && is_scalar( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : self::TAB_DASHBOARD; // phpcs:enable WordPress.Security.NonceVerification.Recommended return array_key_exists( $tab, $this->get_tabs() ) ? $tab : self::TAB_DASHBOARD; } /** * Get current request method. */ private function get_request_method(): string { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) { return ''; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Request method is normalized immediately. return strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ); } /** * Get a scalar POST value. * * @param string $key POST key. */ private function get_post_scalar( string $key ): string { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is checked before mutating settings. if ( ! isset( $_POST[ $key ] ) || ! is_scalar( $_POST[ $key ] ) ) { return ''; } return sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); // phpcs:enable WordPress.Security.NonceVerification.Missing } /** * Get posted settings. * * @return array */ private function get_post_settings(): array { // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Settings are allowlisted and sanitized before saving. if ( ! isset( $_POST['twootwocode_zsf_settings'] ) || ! is_array( $_POST['twootwocode_zsf_settings'] ) ) { return array(); } return wp_unslash( $_POST['twootwocode_zsf_settings'] ); // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } /** * Get tab URL. * * @param string $tab Tab key. */ private function get_tab_url( string $tab ): string { if ( ! function_exists( 'admin_url' ) ) { return ''; } return admin_url( 'options-general.php?page=' . AdminMenu::PAGE_SLUG . '&tab=' . rawurlencode( $tab ) ); } /** * Checked attribute helper. * * @param bool $checked Whether checked. */ private function checked_attr( bool $checked ): string { return $checked ? ' checked="checked"' : ''; } /** * Get a safe integration label for an event source. * * @param string $source_key Source key. */ private function get_event_source_label( string $source_key ): string { foreach ( $this->integration_status_provider->get_statuses() as $integration ) { if ( $source_key === (string) $integration['key'] ) { return (string) $integration['label']; } } return __( 'Unknown source', '2o2code-spamless-forms' ); } /** * Get a safe action label. * * @param string $action Event action. */ private function get_event_action_label( string $action ): string { if ( SpamEvent::ACTION_BLOCKED === $action ) { return __( 'Blocked', '2o2code-spamless-forms' ); } if ( SpamEvent::ACTION_SUSPICIOUS === $action ) { return __( 'Suspicious', '2o2code-spamless-forms' ); } if ( SpamEvent::ACTION_ALLOWED === $action ) { return __( 'Allowed', '2o2code-spamless-forms' ); } return __( 'Unknown', '2o2code-spamless-forms' ); } /** * Format event timestamp. * * @param int $timestamp UTC timestamp. */ private function format_event_date( int $timestamp ): string { if ( $timestamp < 1 ) { return ''; } if ( function_exists( 'date_i18n' ) ) { return date_i18n( 'Y-m-d H:i', $timestamp, true ); } return gmdate( 'Y-m-d H:i', $timestamp ); } /** * Count integrations that are enabled and detected. * * @param array> $integrations Integrations. */ private function count_protected_integrations( array $integrations ): int { $count = 0; foreach ( $integrations as $integration ) { if ( ! empty( $integration['available'] ) && ! empty( $integration['enabled'] ) ) { ++$count; } } return $count; } /** * Get mode-aware integration metric copy. * * @param string $protection_mode Protection mode. * @param int $active_count Detected and enabled integration count. * @param int $total_count Total integration count. * * @return array{label:string,value:string,description:string} */ private function get_integration_metric_copy( string $protection_mode, int $active_count, int $total_count ): array { if ( SettingsSnapshot::MODE_MONITOR === $protection_mode ) { return array( 'label' => __( 'Monitored integrations', '2o2code-spamless-forms' ), 'value' => sprintf( /* translators: 1: monitored integrations, 2: total integrations. */ __( '%1$d of %2$d monitored', '2o2code-spamless-forms' ), $active_count, $total_count ), 'description' => __( 'Enabled and detected integrations are observed without blocking.', '2o2code-spamless-forms' ), ); } if ( SettingsSnapshot::MODE_DISABLED === $protection_mode ) { return array( 'label' => __( 'Enabled integrations', '2o2code-spamless-forms' ), 'value' => sprintf( /* translators: 1: ready integrations, 2: total integrations. */ __( '%1$d of %2$d ready', '2o2code-spamless-forms' ), $active_count, $total_count ), 'description' => __( 'Integration preferences remain saved while protection is disabled.', '2o2code-spamless-forms' ), ); } return array( 'label' => __( 'Protected integrations', '2o2code-spamless-forms' ), 'value' => sprintf( /* translators: 1: protected integrations, 2: total integrations. */ __( '%1$d of %2$d protected', '2o2code-spamless-forms' ), $active_count, $total_count ), 'description' => __( 'Enabled and detected integrations are protected.', '2o2code-spamless-forms' ), ); } /** * Determine whether an external form plugin is detected. * * @param array> $integrations Integrations. */ private function has_detected_external_integration( array $integrations ): bool { foreach ( $integrations as $integration ) { $key = isset( $integration['key'] ) ? (string) $integration['key'] : ''; if ( ! $this->is_native_integration( $key ) && ! empty( $integration['available'] ) ) { return true; } } return false; } /** * Check whether an integration is a native WordPress source. * * @param string $key Integration key. */ private function is_native_integration( string $key ): bool { return in_array( $key, array( 'wordpress_comments', 'wordpress_login', 'wordpress_registration' ), true ); } /** * Get integration state label. * * @param array $integration Integration row. * @param string $protection_mode Protection mode. */ private function get_integration_state_label( array $integration, string $protection_mode = SettingsSnapshot::MODE_PROTECT ): string { $available = ! empty( $integration['available'] ); $enabled = ! empty( $integration['enabled'] ); if ( $available && $enabled ) { if ( SettingsSnapshot::MODE_MONITOR === $protection_mode ) { return __( 'Detected and monitored.', '2o2code-spamless-forms' ); } if ( SettingsSnapshot::MODE_DISABLED === $protection_mode ) { return __( 'Detected and enabled, but protection is disabled.', '2o2code-spamless-forms' ); } return __( 'Detected and protected.', '2o2code-spamless-forms' ); } if ( $available ) { return __( 'Detected, but protection is off for this integration.', '2o2code-spamless-forms' ); } if ( $enabled ) { return __( 'Enabled preference saved; integration not detected.', '2o2code-spamless-forms' ); } return __( 'Not detected or enabled.', '2o2code-spamless-forms' ); } /** * Get integration helper text. * * @param string $key Integration key. */ private function get_integration_helper_text( string $key ): string { if ( 'contact_form_7' === $key ) { return __( 'Protects Contact Form 7 submissions when the plugin is active.', '2o2code-spamless-forms' ); } if ( 'wpforms' === $key ) { return __( 'Protects WPForms Lite submissions when WPForms Lite is active.', '2o2code-spamless-forms' ); } if ( 'elementor_form_lite' === $key ) { return __( 'Requires both Elementor and the Hello Plus companion plugin. Submit a Form Lite test and confirm its Activity source is Elementor Form Lite.', '2o2code-spamless-forms' ); } if ( 'wordpress_comments' === $key ) { return __( 'Protects native WordPress comment submissions.', '2o2code-spamless-forms' ); } if ( 'wordpress_login' === $key ) { return __( 'Protects native WordPress login attempts.', '2o2code-spamless-forms' ); } if ( 'wordpress_registration' === $key ) { return __( 'Protects native WordPress registration attempts.', '2o2code-spamless-forms' ); } return __( 'Protects this supported Free source when available.', '2o2code-spamless-forms' ); } /** * Get latest event for a source from safe recent events. * * @param array> $events Events. * @param string $source_key Source key. * * @return array|null */ private function get_latest_event_for_source( array $events, string $source_key ): ?array { foreach ( $events as $event ) { if ( isset( $event['source_key'] ) && $source_key === (string) $event['source_key'] ) { return $event; } } return null; } /** * Get last event label. * * @param array|null $event Event. */ private function get_last_event_label( ?array $event ): string { if ( null === $event ) { return __( 'No activity recorded yet.', '2o2code-spamless-forms' ); } return sprintf( /* translators: %s: formatted event date. */ __( 'Last activity: %s', '2o2code-spamless-forms' ), $this->format_event_date( isset( $event['created_at'] ) ? (int) $event['created_at'] : 0 ) ); } /** * Get next recommended action. * * @param array $status Status. * @param array $summary Summary. * @param array> $events Events. * @param array $settings Settings. * * @return array{title:string,description:string,button:string,url:string} */ private function get_next_recommended_action( array $status, array $summary, array $events, array $settings ): array { $status_key = isset( $status['key'] ) && is_scalar( $status['key'] ) ? (string) $status['key'] : 'protect'; if ( 'disabled' === $status_key ) { return array( 'title' => __( 'Review Protection settings', '2o2code-spamless-forms' ), 'description' => __( 'Protection is disabled. Turn it back on when troubleshooting is complete.', '2o2code-spamless-forms' ), 'button' => __( 'Review Protection', '2o2code-spamless-forms' ), 'url' => $this->get_tab_url( self::TAB_PROTECTION ), ); } if ( empty( $settings['event_logging_enabled'] ) ) { return array( 'title' => __( 'Review Protection settings', '2o2code-spamless-forms' ), 'description' => __( 'Event logging is off, so Activity will have limited detail.', '2o2code-spamless-forms' ), 'button' => __( 'Review Protection', '2o2code-spamless-forms' ), 'url' => $this->get_tab_url( self::TAB_PROTECTION ), ); } if ( isset( $summary['enabled'] ) && (int) $summary['enabled'] < 1 ) { return array( 'title' => __( 'Check Integrations', '2o2code-spamless-forms' ), 'description' => __( 'Choose which supported Free sources should be protected.', '2o2code-spamless-forms' ), 'button' => __( 'Open Integrations', '2o2code-spamless-forms' ), 'url' => $this->get_tab_url( self::TAB_INTEGRATIONS ), ); } if ( array() === $events ) { return array( 'title' => __( 'Test a form', '2o2code-spamless-forms' ), 'description' => __( 'Submit normal and spam-like test content, then confirm Activity stays privacy-safe.', '2o2code-spamless-forms' ), 'button' => __( 'View Integrations', '2o2code-spamless-forms' ), 'url' => $this->get_tab_url( self::TAB_INTEGRATIONS ), ); } return array( 'title' => __( 'Review recent Activity', '2o2code-spamless-forms' ), 'description' => __( 'Recent blocked or suspicious decisions are available without exposing raw submission data.', '2o2code-spamless-forms' ), 'button' => __( 'View Activity', '2o2code-spamless-forms' ), 'url' => $this->get_tab_url( self::TAB_ACTIVITY ), ); } /** * Get default status data. * * @return array{key:string,label:string,headline:string,description:string} */ private function get_default_status(): array { return array( 'key' => 'protect', 'label' => __( 'Protect', '2o2code-spamless-forms' ), 'headline' => __( 'Protection is active.', '2o2code-spamless-forms' ), 'description' => __( '2o2code Spamless Forms is protecting enabled integrations with local checks.', '2o2code-spamless-forms' ), ); } /** * Get default integration summary. * * @return array{total:int,available:int,enabled:int} */ private function get_default_summary(): array { return array( 'total' => 0, 'available' => 0, 'enabled' => 0, ); } /** * Get default event summary. * * @return array{allowed:int,blocked:int,suspicious:int,total:int} */ private function get_default_event_summary(): array { return array( 'allowed' => 0, 'blocked' => 0, 'suspicious' => 0, 'total' => 0, ); } /** * Get mode label. * * @param string $mode Mode. */ private function get_mode_label( string $mode ): string { if ( SettingsSnapshot::MODE_MONITOR === $mode ) { return __( 'Monitor', '2o2code-spamless-forms' ); } if ( SettingsSnapshot::MODE_DISABLED === $mode ) { return __( 'Disabled', '2o2code-spamless-forms' ); } return __( 'Protect', '2o2code-spamless-forms' ); } /** * Get strictness label. * * @param string $strictness Strictness. */ private function get_strictness_label( string $strictness ): string { if ( SettingsSnapshot::STRICTNESS_SAFE === $strictness ) { return __( 'Safe', '2o2code-spamless-forms' ); } if ( SettingsSnapshot::STRICTNESS_STRICT === $strictness ) { return __( 'Strict', '2o2code-spamless-forms' ); } return __( 'Balanced', '2o2code-spamless-forms' ); } /** * Get free feature labels. * * @return array */ private function get_free_feature_labels(): array { return array( __( 'Local form checks with no external anti-spam API.', '2o2code-spamless-forms' ), __( 'No CAPTCHA or puzzle challenges.', '2o2code-spamless-forms' ), __( 'Protect, Monitor, and Disabled modes.', '2o2code-spamless-forms' ), __( 'Safe, Balanced, and Strict sensitivity settings.', '2o2code-spamless-forms' ), __( 'Privacy-first defaults with no full submission storage.', '2o2code-spamless-forms' ), __( 'Simple Activity review using minimal metadata only.', '2o2code-spamless-forms' ), ); } /** * Get supported Free integration labels. * * @return array */ private function get_supported_free_integration_labels(): array { return array( __( 'Contact Form 7', '2o2code-spamless-forms' ), __( 'WPForms Lite', '2o2code-spamless-forms' ), __( 'Elementor Form Lite', '2o2code-spamless-forms' ), __( 'WordPress comments', '2o2code-spamless-forms' ), __( 'WordPress login', '2o2code-spamless-forms' ), __( 'WordPress registration', '2o2code-spamless-forms' ), ); } /** * Whether the current package is the Free build. */ private function is_free_build(): bool { return ( new BuildProfileResolver() )->resolve()->is_free(); } }