Home, Leads (and one lead), Visitors, Settings; first-run setup; * the dashboard widget; notices; and the admin-post actions behind them. Every screen and * action needs manage_options (filterable with capyscout_capability) and every action checks * a nonce. */ if ( ! defined( 'ABSPATH' ) ) { exit; } class CapyScout_Admin { /** @var CapyScout_API */ private $api; /** @var CapyScout_Connect */ private $connect; /** @var CapyScout_Leads */ private $leads; /** @var CapyScout_Scoring */ private $scoring; /** @var CapyScout_Email_Guard */ private $guard; /** @var CapyScout_Digest */ private $digest; public function __construct( CapyScout_API $api, CapyScout_Connect $connect, CapyScout_Leads $leads, CapyScout_Scoring $scoring, CapyScout_Email_Guard $guard, CapyScout_Digest $digest ) { $this->api = $api; $this->connect = $connect; $this->leads = $leads; $this->scoring = $scoring; $this->guard = $guard; $this->digest = $digest; add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) ); add_filter( 'admin_body_class', array( $this, 'body_class' ) ); add_action( 'admin_notices', array( $this, 'notices' ) ); add_action( 'wp_dashboard_setup', array( $this, 'widget' ) ); add_filter( 'plugin_action_links_' . plugin_basename( CAPYSCOUT_FILE ), array( $this, 'action_links' ) ); add_action( 'admin_post_capyscout_export', array( $this, 'export' ) ); add_action( 'admin_post_capyscout_lead', array( $this, 'lead_action' ) ); add_action( 'admin_post_capyscout_research', array( $this, 'research' ) ); add_action( 'admin_post_capyscout_push', array( $this, 'push' ) ); add_action( 'admin_post_capyscout_setup', array( $this, 'setup' ) ); add_action( 'admin_post_capyscout_test_digest', array( $this, 'test_digest' ) ); add_action( 'admin_post_capyscout_dismiss', array( $this, 'dismiss' ) ); add_action( 'admin_post_capyscout_resume', array( $this, 'resume' ) ); add_action( 'admin_post_capyscout_refresh', array( $this, 'refresh' ) ); } public static function capability(): string { return (string) apply_filters( 'capyscout_capability', 'manage_options' ); } // ================================================================== menu and assets public function menu(): void { $cap = self::capability(); add_menu_page( __( 'CapyScout', 'capyscout' ), __( 'CapyScout', 'capyscout' ), $cap, 'capyscout', array( $this, 'page_home' ), CapyScout_UI::menu_icon(), 58 ); add_submenu_page( 'capyscout', __( 'CapyScout', 'capyscout' ), __( 'Home', 'capyscout' ), $cap, 'capyscout', array( $this, 'page_home' ) ); add_submenu_page( 'capyscout', __( 'Leads', 'capyscout' ), __( 'Leads', 'capyscout' ), $cap, 'capyscout-leads', array( $this, 'page_leads' ) ); add_submenu_page( 'capyscout', __( 'Website visitors', 'capyscout' ), __( 'Visitors', 'capyscout' ), $cap, 'capyscout-visitors', array( $this, 'page_visitors' ) ); add_submenu_page( 'capyscout', __( 'CapyScout settings', 'capyscout' ), __( 'Settings', 'capyscout' ), $cap, 'capyscout-settings', array( $this, 'page_settings' ) ); } private static function is_ours(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- reading the current admin page. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; return 0 === strpos( $page, 'capyscout' ); } public function assets( $hook ): void { // The menu icon shows on every admin screen. wp_register_style( 'capyscout-menu', false, array(), CAPYSCOUT_VERSION ); wp_enqueue_style( 'capyscout-menu' ); wp_add_inline_style( 'capyscout-menu', CapyScout_UI::menu_icon_css() ); if ( self::is_ours() || 'index.php' === $hook ) { wp_enqueue_style( 'capyscout-admin', CAPYSCOUT_URL . 'assets/css/admin.css', array(), CAPYSCOUT_VERSION ); } } public function body_class( $classes ) { return self::is_ours() ? $classes . ' capyscout-admin' : $classes; } public function action_links( $links ) { $settings = '' . esc_html__( 'Settings', 'capyscout' ) . ''; array_unshift( $links, $settings ); if ( ! $this->api->is_connected() ) { $links[] = '' . esc_html__( 'Start free trial', 'capyscout' ) . ''; } return $links; } // ================================================================== settings public function register_settings(): void { register_setting( 'capyscout_settings', CapyScout_Email_Guard::OPTION, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_guard' ), 'default' => CapyScout_Email_Guard::defaults(), ) ); register_setting( 'capyscout_settings', CapyScout_Scoring::OPTION, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_scoring' ), 'default' => CapyScout_Scoring::defaults(), ) ); register_setting( 'capyscout_settings', CapyScout_Digest::OPTION, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_digest' ), 'default' => CapyScout_Digest::defaults(), ) ); register_setting( 'capyscout_settings', CapyScout_Tracking::OPTION, array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_tracking' ), 'default' => '0', ) ); } /** Settings forms always post a "_present" marker; a missing section keeps its saved values. */ public function sanitize_guard( $value ): array { if ( ! is_array( $value ) || empty( $value['_present'] ) ) { return is_array( $value ) && ! isset( $value['_present'] ) && isset( $value['disposable'] ) ? self::pick_guard( $value ) : CapyScout_Email_Guard::settings(); } return self::pick_guard( $value ); } private static function pick_guard( array $value ): array { $out = array(); foreach ( array( 'disposable', 'mx', 'free', 'role', 'forms', 'register', 'tags' ) as $k ) { $out[ $k ] = empty( $value[ $k ] ) ? 0 : 1; } $domains = array(); foreach ( preg_split( '/[\s,]+/', strtolower( (string) ( $value['blocklist'] ?? '' ) ) ) as $d ) { $d = ltrim( trim( $d ), '@.' ); if ( '' !== $d && preg_match( '/^[a-z0-9.-]{1,253}\.[a-z]{2,63}$/', $d ) ) { $domains[ $d ] = true; } } $out['blocklist'] = implode( "\n", array_slice( array_keys( $domains ), 0, 500 ) ); return $out; } public function sanitize_scoring( $value ): array { if ( ! is_array( $value ) || empty( $value['_present'] ) ) { return is_array( $value ) && ! isset( $value['_present'] ) && isset( $value['enabled'] ) ? self::pick_scoring( $value ) : CapyScout_Scoring::settings(); } return self::pick_scoring( $value ); } private static function pick_scoring( array $value ): array { $out = array(); foreach ( array( 'enabled', 'free', 'forms', 'register', 'woocommerce', 'notify' ) as $k ) { $out[ $k ] = empty( $value[ $k ] ) ? 0 : 1; } $out['monthly_cap'] = max( 0, min( 100000, (int) ( $value['monthly_cap'] ?? 200 ) ) ); $out['notify_min'] = max( 0, min( 100, (int) ( $value['notify_min'] ?? 50 ) ) ); $to = sanitize_email( (string) ( $value['notify_to'] ?? '' ) ); $out['notify_to'] = is_email( $to ) ? $to : ''; return $out; } public function sanitize_digest( $value ): array { if ( ! is_array( $value ) || empty( $value['_present'] ) ) { return is_array( $value ) && ! isset( $value['_present'] ) && isset( $value['enabled'] ) ? self::pick_digest( $value ) : CapyScout_Digest::settings(); } return self::pick_digest( $value ); } private static function pick_digest( array $value ): array { $to = sanitize_email( (string) ( $value['to'] ?? '' ) ); return array( 'enabled' => empty( $value['enabled'] ) ? 0 : 1, 'to' => is_email( $to ) ? $to : '', ); } public function sanitize_tracking( $value ): string { if ( null === $value ) { return (string) get_option( CapyScout_Tracking::OPTION, '0' ); } return '1' === (string) $value ? '1' : '0'; } // ================================================================== pages private function require_cap(): void { if ( ! current_user_can( self::capability() ) ) { wp_die( esc_html__( 'You do not have permission to view this page.', 'capyscout' ), '', array( 'response' => 403 ) ); } } /** Shared page context: connection and account for the header. */ private function context(): array { $connected = $this->api->is_connected(); return array( $connected, $connected ? $this->api->account() : array() ); } public function page_home(): void { $this->require_cap(); list( $connected, $account ) = $this->context(); if ( ! get_option( 'capyscout_setup_done' ) && ! $connected ) { include CAPYSCOUT_DIR . 'admin/views/setup.php'; return; } $summary = $this->leads->summary( 30 ); $top = $this->leads->company_leads( 30, 6 ); $untouched = $this->leads->count_untouched( 30 ); $daily = $this->leads->daily( 30 ); $blocked = CapyScout_Email_Guard::blocked_this_month(); $detected = CapyScout_Sources::detected(); $scored = CapyScout_Scoring::scored_this_month(); $visitors = $connected && CapyScout_Tracking::enabled() ? get_transient( 'capyscout_visitors_7' ) : false; include CAPYSCOUT_DIR . 'admin/views/dashboard.php'; } public function page_leads(): void { $this->require_cap(); list( $connected, $account ) = $this->context(); // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only filters. $lead_id = isset( $_GET['lead'] ) ? absint( $_GET['lead'] ) : 0; $lead = $lead_id ? $this->leads->get( $lead_id ) : null; if ( $lead_id && ! $lead ) { // The page has started rendering, so fall back to the list instead of redirecting. self::flash( 'error', __( 'That lead is no longer here. Leads are kept for 90 days.', 'capyscout' ) ); } if ( $lead ) { $card = null; $campaigns = array(); $lists = array(); $domain = CapyScout_UI::lead_domain( $lead ); if ( $connected && 'blocked' !== $lead['status'] ) { $card = $this->api->company( in_array( $lead['email_type'], array( 'disposable', 'invalid' ), true ) ? '' : (string) $lead['email'], '', (string) $lead['name'] ); if ( '' !== $domain || in_array( $lead['email_type'], array( 'free', 'relay' ), true ) ) { $campaigns = $this->api->campaigns(); } if ( '' !== $domain ) { $lists = $this->api->lists(); } } include CAPYSCOUT_DIR . 'admin/views/lead.php'; return; } $view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'all'; $view = in_array( $view, array( 'all', 'companies', 'strong', 'blocked' ), true ) ? $view : 'all'; $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; $paged = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; // phpcs:enable $result = $this->leads->page( array( 'view' => $view, 'search' => $search, 'page' => $paged, ) ); $pages = max( 1, (int) ceil( $result['total'] / CapyScout_Leads::PER_PAGE ) ); include CAPYSCOUT_DIR . 'admin/views/leads.php'; } public function page_visitors(): void { $this->require_cap(); list( $connected, $account ) = $this->context(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter. $days = isset( $_GET['days'] ) ? absint( $_GET['days'] ) : 30; $days = in_array( $days, array( 7, 30, 90 ), true ) ? $days : 30; $tracking = CapyScout_Tracking::enabled(); $traffic = $connected ? $this->api->traffic( $days ) : null; $data = $connected ? $this->api->visitors( 7 === $days ? 7 : 30 ) : null; $campaigns = $connected && is_array( $data ) && ! empty( $data['companies'] ) ? $this->api->campaigns() : array(); include CAPYSCOUT_DIR . 'admin/views/visitors.php'; } public function page_settings(): void { $this->require_cap(); list( $connected, $account ) = $this->context(); $guard = CapyScout_Email_Guard::settings(); $scoring = CapyScout_Scoring::settings(); $digest = CapyScout_Digest::settings(); $tracking = CapyScout_Tracking::enabled(); $detected = CapyScout_Sources::detected(); $test = null; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- a read-only check of one address, nonce checked below. if ( isset( $_GET['capyscout_test'], $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'capyscout_test' ) ) { $address = sanitize_email( wp_unslash( $_GET['capyscout_test'] ) ); $c = $this->guard->classify( $address ); $test = array( 'email' => $address, 'type' => $c['type'], 'forms' => $this->guard->problem( $address, 'forms' ), 'mail' => 'invalid' === $c['type'] ? false : $this->guard->receives_mail( $c['domain'] ), ); } // phpcs:enable include CAPYSCOUT_DIR . 'admin/views/settings.php'; } // ================================================================== actions private function require_action( string $nonce_action ): void { if ( ! current_user_can( self::capability() ) ) { wp_die( esc_html__( 'You do not have permission to do that.', 'capyscout' ), '', array( 'response' => 403 ) ); } check_admin_referer( $nonce_action ); } /** A one-time message for the current admin, shown on the next CapyScout screen. */ public static function flash( string $type, string $text, string $url = '' ): void { set_transient( 'capyscout_flash_' . get_current_user_id(), array( 'type' => $type, 'text' => $text, 'url' => $url, ), 120 ); } public static function take_flash(): ?array { $key = 'capyscout_flash_' . get_current_user_id(); $f = get_transient( $key ); delete_transient( $key ); return is_array( $f ) ? $f : null; } private static function back( string $fallback ): void { $ref = wp_get_referer(); wp_safe_redirect( $ref ? $ref : $fallback ); exit; } public function export(): void { $this->require_action( 'capyscout_export' ); $view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified in require_action(). $rows = $this->leads->export_rows( $view ); nocache_headers(); header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="capyscout-leads-' . gmdate( 'Y-m-d' ) . '.csv"' ); $out = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming a download. fputcsv( $out, array( 'received_utc', 'email', 'email_type', 'name', 'source', 'form', 'status', 'stopped_because', 'fit', 'label', 'company', 'company_domain', 'company_details', 'title', 'suggested_next_step' ), ',', '"', '' ); foreach ( $rows as $r ) { // Cells starting with = + - @ are prefixed so a spreadsheet never runs them as formulas. fputcsv( $out, array_map( array( __CLASS__, 'csv_cell' ), array_values( $r ) ), ',', '"', '' ); } fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose exit; } public static function csv_cell( $v ): string { $v = (string) $v; return '' !== $v && in_array( $v[0], array( '=', '+', '-', '@', "\t", "\r" ), true ) ? "'" . $v : $v; } public function lead_action(): void { $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the nonce is per lead and verified on the next line. $this->require_action( 'capyscout_lead_' . $id ); $do = isset( $_GET['do'] ) ? sanitize_key( wp_unslash( $_GET['do'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified in require_action(). $lead = $this->leads->get( $id ); if ( $lead && 'delete' === $do ) { $this->leads->delete( $id ); self::flash( 'success', __( 'Lead deleted from this site.', 'capyscout' ) ); wp_safe_redirect( admin_url( 'admin.php?page=capyscout-leads' ) ); exit; } if ( $lead && 'score' === $do && $this->api->is_connected() && ! in_array( $lead['status'], array( 'blocked', 'queued', 'scored' ), true ) && ! in_array( $lead['email_type'], array( 'disposable', 'invalid' ), true ) ) { $this->leads->update( $id, array( 'status' => 'queued', 'attempts' => 0, ) ); $this->scoring->kick(); self::flash( 'success', __( 'Queued. The score shows up in a minute or two.', 'capyscout' ) ); } self::back( admin_url( 'admin.php?page=capyscout-leads' ) ); } /** Researches a lead's company now (included in the plan) and saves what came back on the lead. */ public function research(): void { $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in require_action. $this->require_action( 'capyscout_lead_' . $id ); $lead = $this->leads->get( $id ); $page = admin_url( 'admin.php?page=capyscout-leads&lead=' . $id ); if ( ! $lead || ! $this->api->is_connected() ) { self::back( $page ); } $domain = CapyScout_UI::lead_domain( $lead ); if ( '' === $domain ) { self::flash( 'error', __( 'This lead used a personal email address, so there is no company to research.', 'capyscout' ) ); self::back( $page ); } $res = $this->api->research( (string) $lead['email'], '', (string) $lead['name'] ); if ( 200 === $res['status'] && is_array( $res['body'] ) ) { $co = isset( $res['body']['company'] ) && is_array( $res['body']['company'] ) ? $res['body']['company'] : array(); $facts = isset( $co['facts'] ) && is_array( $co['facts'] ) ? $co['facts'] : array(); $info = array_filter( array( (string) ( $facts['industry'] ?? '' ), (string) ( $facts['size'] ?? '' ), (string) ( $facts['location'] ?? '' ) ), 'strlen' ); $row = array( 'researched_at' => gmdate( 'Y-m-d H:i:s' ), 'company' => self::cut( $co['name'] ?? '', 120 ), 'company_domain' => self::cut( $co['domain'] ?? $domain, 253 ), 'company_info' => self::cut( implode( ' - ', $info ), 255 ), ); if ( null === $lead['fit'] && isset( $co['fit']['score'] ) && is_numeric( $co['fit']['score'] ) ) { $row['fit'] = max( 0, min( 100, (int) $co['fit']['score'] ) ); } $this->leads->update( $id, $row ); self::flash( 'success', __( 'Research is in.', 'capyscout' ) ); } else { self::flash( 'error', self::problem_text( $res ) ); } self::back( $page ); } /** Adds a lead (or a visiting company) to a CapyScout campaign or list. */ public function push(): void { // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in require_action. $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $domain = isset( $_POST['domain'] ) ? sanitize_text_field( wp_unslash( $_POST['domain'] ) ) : ''; $this->require_action( $id ? 'capyscout_lead_' . $id : 'capyscout_push_company' ); $target = isset( $_POST['target'] ) && 'list' === $_POST['target'] ? 'list' : 'campaign'; $campaign = isset( $_POST['campaign'] ) ? sanitize_text_field( wp_unslash( $_POST['campaign'] ) ) : ''; $list = isset( $_POST['list'] ) ? sanitize_text_field( wp_unslash( $_POST['list'] ) ) : ''; $new_list = isset( $_POST['new_list'] ) ? sanitize_text_field( wp_unslash( $_POST['new_list'] ) ) : ''; $name = isset( $_POST['company_name'] ) ? sanitize_text_field( wp_unslash( $_POST['company_name'] ) ) : ''; // phpcs:enable if ( ! $this->api->is_connected() ) { self::back( admin_url( 'admin.php?page=capyscout' ) ); } $lead = $id ? $this->leads->get( $id ) : null; if ( $id && ! $lead ) { self::back( admin_url( 'admin.php?page=capyscout-leads' ) ); } if ( $lead ) { $payload = array( 'email' => in_array( $lead['email_type'], array( 'disposable', 'invalid' ), true ) ? '' : (string) $lead['email'], 'name' => (string) $lead['name'], 'title' => (string) $lead['person_title'], 'companyName' => (string) $lead['company'], ); } else { $payload = array( 'domain' => preg_match( '/^[a-z0-9.-]{1,253}\.[a-z]{2,63}$/i', $domain ) ? strtolower( $domain ) : '', 'companyName' => $name, ); } if ( 'list' === $target ) { $res = $this->api->add_to_list( array_merge( $payload, array( 'list' => '' !== $new_list ? '' : $list, 'newListName' => $new_list ) ) ); } else { $res = $this->api->add_to_campaign( array_merge( $payload, array( 'campaign' => $campaign ) ) ); } $msg = isset( $res['body']['message'] ) ? sanitize_text_field( (string) $res['body']['message'] ) : ''; $url = isset( $res['body']['url'] ) && 0 === strpos( (string) $res['body']['url'], CAPYSCOUT_BASE . '/' ) ? (string) $res['body']['url'] : ''; if ( 200 === $res['status'] && ! empty( $res['body']['ok'] ) ) { if ( $lead ) { $this->leads->update( $id, array( 'pushed' => self::cut( $msg, 200 ) ) ); } self::flash( 'success', '' !== $msg ? $msg : __( 'Added.', 'capyscout' ), $url ); } else { self::flash( 'error', '' !== $msg ? $msg : self::problem_text( $res ) ); } self::back( admin_url( 'admin.php?page=capyscout-leads' ) ); } /** First-run setup: who you sell to, work email, weekly summary. */ public function setup(): void { $this->require_action( 'capyscout_setup' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in require_action. $audience = isset( $_POST['audience'] ) ? sanitize_key( wp_unslash( $_POST['audience'] ) ) : 'b2b'; $audience = in_array( $audience, array( 'b2b', 'both', 'b2c' ), true ) ? $audience : 'b2b'; $guard = CapyScout_Email_Guard::settings(); $guard['disposable'] = empty( $_POST['disposable'] ) ? 0 : 1; $guard['free'] = ! empty( $_POST['work_email'] ) && 'b2c' !== $audience ? 1 : 0; update_option( CapyScout_Email_Guard::OPTION, $guard ); update_option( CapyScout_Digest::OPTION, array( 'enabled' => empty( $_POST['digest'] ) ? 0 : 1, 'to' => '', ) ); // phpcs:enable update_option( 'capyscout_audience', $audience, false ); update_option( 'capyscout_setup_done', 1, false ); update_option( 'capyscout_welcome_dismissed', 1, false ); if ( $guard['free'] ) { $done = __( 'All set. Your forms now ask for a work email; Gmail, Yahoo and other personal addresses are asked to use one. Store checkout is never blocked. Every lead is logged here.', 'capyscout' ); } elseif ( $guard['disposable'] ) { $done = __( 'All set. Gmail, Yahoo and other personal emails go through as usual; only temporary throwaway inboxes (like mailinator.com) are stopped. Every lead is logged here.', 'capyscout' ); } else { $done = __( 'All set. Every email is accepted, and every lead is logged here.', 'capyscout' ); } self::flash( 'success', $done ); wp_safe_redirect( admin_url( 'admin.php?page=capyscout' ) ); exit; } public function test_digest(): void { $this->require_action( 'capyscout_test_digest' ); $to = CapyScout_Digest::recipient(); $email = $this->digest->build(); if ( '' === $to ) { self::flash( 'error', __( 'Add an email address for the summary first.', 'capyscout' ) ); } elseif ( null === $email ) { self::flash( 'error', __( 'No leads in the last 7 days yet, so there is nothing to summarize.', 'capyscout' ) ); } else { $sent = false; CapyScout_Sources::without_tags( static function () use ( $to, $email, &$sent ) { $sent = wp_mail( $to, $email[0], $email[1] ); } ); /* translators: %s: email address */ self::flash( $sent ? 'success' : 'error', $sent ? sprintf( __( 'Sent a summary to %s.', 'capyscout' ), $to ) : __( 'WordPress could not send the email. Check your site email settings.', 'capyscout' ) ); } self::back( admin_url( 'admin.php?page=capyscout-settings' ) ); } public function dismiss(): void { $this->require_action( 'capyscout_dismiss' ); $what = isset( $_GET['what'] ) ? sanitize_key( wp_unslash( $_GET['what'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified in require_action(). if ( 'welcome' === $what ) { update_option( 'capyscout_welcome_dismissed', 1, false ); } elseif ( 'disconnected' === $what ) { delete_option( 'capyscout_disconnected_notice' ); } elseif ( 'trial' === $what ) { set_transient( 'capyscout_trial_dismissed', 1, DAY_IN_SECONDS ); } self::back( admin_url() ); } public function resume(): void { $this->require_action( 'capyscout_resume' ); CapyScout_Scoring::resume(); $this->api->refresh_account(); $this->scoring->kick(); self::flash( 'success', __( 'Lead scoring will try again in a minute.', 'capyscout' ) ); wp_safe_redirect( admin_url( 'admin.php?page=capyscout-settings' ) ); exit; } public function refresh(): void { $this->require_action( 'capyscout_refresh' ); $this->api->refresh_account(); foreach ( array( 'capyscout_visitors_7', 'capyscout_visitors_30', 'capyscout_traffic_7', 'capyscout_traffic_30', 'capyscout_traffic_90', 'capyscout_campaigns', 'capyscout_lists' ) as $t ) { delete_transient( $t ); } self::back( admin_url( 'admin.php?page=capyscout' ) ); } public static function action_url( string $action, array $args = array(), string $nonce = '' ): string { $url = add_query_arg( array_merge( array( 'action' => $action ), $args ), admin_url( 'admin-post.php' ) ); return wp_nonce_url( $url, '' !== $nonce ? $nonce : $action ); } /** A plain sentence for a failed CapyScout call. */ public static function problem_text( array $res ): string { $code = isset( $res['body']['error']['code'] ) ? (string) $res['body']['error']['code'] : ''; if ( 402 === $res['status'] || 'credits_exhausted' === $code ) { return __( 'Your CapyScout workspace reached its monthly limit. Upgrade to keep researching leads.', 'capyscout' ); } if ( 403 === $res['status'] && 'workspace_paused' === $code ) { return __( 'Your CapyScout workspace is paused. An owner can resume it in Billing.', 'capyscout' ); } if ( 0 === $res['status'] ) { return __( 'CapyScout could not be reached. Try again in a minute.', 'capyscout' ); } if ( isset( $res['body']['error']['message'] ) ) { return sanitize_text_field( (string) $res['body']['error']['message'] ); } return __( 'Something went wrong. Try again in a minute.', 'capyscout' ); } private static function cut( $value, int $max ): string { $value = sanitize_text_field( (string) $value ); return function_exists( 'mb_substr' ) ? mb_substr( $value, 0, $max ) : substr( $value, 0, $max ); } // ================================================================== notices and widget public function notices(): void { if ( ! current_user_can( self::capability() ) ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; $sid = $screen ? (string) $screen->id : ''; $ours = self::is_ours(); $gone = (string) get_option( 'capyscout_disconnected_notice', '' ); if ( '' !== $gone && ! $this->api->is_connected() ) { $text = 'workspace_closed' === $gone ? __( 'CapyScout: the workspace this site was connected to is closed, so lead research and scoring stopped. The email guard still works.', 'capyscout' ) : __( 'CapyScout: this site was disconnected from CapyScout, so lead research and scoring stopped. The email guard still works.', 'capyscout' ); printf( '
', esc_html( $text ), esc_url( CapyScout_Connect::start_url( 'signin' ) ), esc_html__( 'Connect again', 'capyscout' ), esc_url( self::action_url( 'capyscout_dismiss', array( 'what' => 'disconnected' ) ) ), esc_html__( 'Dismiss', 'capyscout' ) ); } // Welcome: once, on the Plugins and Dashboard screens only, until set up or dismissed. if ( '' === $gone && ! get_option( 'capyscout_welcome_dismissed' ) && in_array( $sid, array( 'plugins', 'dashboard' ), true ) && ! $this->api->is_connected() ) { printf( '', esc_html__( 'CapyScout is on.', 'capyscout' ), esc_html__( 'Temporary throwaway inboxes (like mailinator.com) are now stopped on your forms; Gmail, Yahoo and other personal emails still go through. Take 30 seconds to finish setup.', 'capyscout' ), esc_url( admin_url( 'admin.php?page=capyscout' ) ), esc_html__( 'Finish setup', 'capyscout' ), esc_url( self::action_url( 'capyscout_dismiss', array( 'what' => 'welcome' ) ) ), esc_html__( 'Dismiss', 'capyscout' ) ); } if ( ! $ours ) { return; } if ( $this->api->is_connected() ) { $acc = $this->api->account(); if ( ! empty( $acc['checked'] ) && empty( $acc['live'] ) ) { printf( '%s %s %s