is_acquis_screen() ) return; remove_all_actions( 'admin_notices' ); remove_all_actions( 'all_admin_notices' ); add_action( 'admin_notices', array( $this, 'render_promo_banner' ) ); } /** * Whether the current admin screen belongs to AcquisitionSuite (Lite or * Pro, both register every submenu under the same 'acquisitionsuite' * top-level parent). Deliberately does NOT use * get_current_screen()->parent_base: confirmed via debug logging that * it's still empty at the point 'in_admin_header' fires (parent_base * only gets populated later, while WordPress renders the admin menu * sidebar, which happens after in_admin_header in the page's own * output order) meaning every check built on it, including the * original version of suppress_foreign_admin_notices(), silently * never matched anything since this suppression was first added. * $screen->id, in contrast, is set as soon as set_current_screen() * runs and reliably follows WordPress's own "{parent_slug}_page_ * {submenu_slug}" convention (or "toplevel_page_{slug}" for the * top-level item itself), confirmed empirically for both Lite and Pro * submenu screens. * * @return bool */ private function is_acquis_screen() { $screen = get_current_screen(); if ( ! $screen ) return false; return 'toplevel_page_acquisitionsuite' === $screen->id || 0 === strpos( $screen->id, 'acquisitionsuite_page_' ); } /** * Output global admin CSS via WP Stylesheet API. * Uses a virtual handle (no external file) so wp_add_inline_style() works * on every admin screen without requiring a physical CSS file. */ public function output_global_icon_css() { wp_register_style( 'acquis-admin-global', false, array(), ACQUIS_VERSION ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- version passed as 4th arg wp_enqueue_style( 'acquis-admin-global' ); wp_add_inline_style( 'acquis-admin-global', ' /* ── Menu icon — WP renders the SVG data-URI as , let it breathe ─ */ #adminmenu #toplevel_page_acquisitionsuite .wp-menu-image img { width: 18px !important; height: 18px !important; padding: 0 !important; margin-top: 7px !important; opacity: 0.6; } #adminmenu #toplevel_page_acquisitionsuite:hover .wp-menu-image img, #adminmenu #toplevel_page_acquisitionsuite.wp-has-current-submenu .wp-menu-image img, #adminmenu #toplevel_page_acquisitionsuite.current .wp-menu-image img { opacity: 1; } /* ── Upgrade to Pro submenu CTA ─────────────────────────────── */ #adminmenu a[href*="page=acquis-upgrade"] { color: #f97316 !important; font-weight: 700 !important; } #adminmenu a[href*="page=acquis-upgrade"]:hover, #adminmenu a[href*="page=acquis-upgrade"]:focus { color: #f97316 !important; font-weight: 700 !important; } ' ); } public function enqueue_assets( $hook ) { if ( strpos( $hook, 'acquisitionsuite' ) === false ) return; wp_enqueue_style( 'acquis-admin', ACQUIS_PLUGIN_URL . 'admin/acquis-admin.css', array(), acquis_asset_ver( ACQUIS_PLUGIN_DIR . 'admin/acquis-admin.css' ) ); wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'acquis-admin', ACQUIS_PLUGIN_URL . 'admin/acquis-admin.js', array( 'jquery', 'wp-color-picker' ), acquis_asset_ver( ACQUIS_PLUGIN_DIR . 'admin/acquis-admin.js' ), true ); wp_enqueue_media(); // MailWizz field-map data — needed for initMailwizzFieldMap() in acquis-admin.js $mw_field_map = get_option( 'acquis_mailwizz_custom_fields', array( array( 'tag' => '', 'source' => '' ) ) ); $mw_sources = array( 'full_name' => 'Full Name', 'email' => 'Email', 'file_name' => 'File Name', 'file_ext' => 'File Extension', 'site_name' => 'Site Name', 'site_url' => 'Site URL', ); wp_localize_script( 'acquis-admin', 'ACQUIS_Admin', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'acquis_admin_nonce' ), 'i18n' => array( 'sending' => __( 'Sending…', 'acquisitionsuite' ), 'loading' => __( 'Loading…', 'acquisitionsuite' ), 'testing' => __( 'Testing…', 'acquisitionsuite' ), 'select_logo' => __( 'Select Logo', 'acquisitionsuite' ), 'use_logo' => __( 'Use This Image', 'acquisitionsuite' ), ), ) ); // Pass MailWizz field-map config as a separate object so acquis-admin.js // can initialise the dynamic row builder without any inline script in PHP. wp_add_inline_script( 'acquis-admin', 'window.ACQUIS_MW = ' . wp_json_encode( array( 'idx' => count( (array) $mw_field_map ), 'sources' => $mw_sources, ) ) . ';', 'before' ); } public function allow_mime_types( $mimes ) { return array_merge( $mimes, ACQUIS_File_Manager::allowed_mime_types() ); } // DASHBOARD public function render_dashboard() { $total_leads = ACQUIS_Database::count_leads(); $total_sent = ACQUIS_Database::count_email_logs( 'sent' ); $total_failed = ACQUIS_Database::count_email_logs( 'failed' ); $fq = new WP_Query( array( 'post_type' => ACQUIS_File_Manager::POST_TYPE, 'posts_per_page' => -1, 'fields' => 'ids' ) ); $total_files = $fq->found_posts; $recent_leads = ACQUIS_Database::get_leads( 5, 1 ); ?>
admin_header( esc_html__( 'Dashboard', 'acquisitionsuite' ) ); ?>

Email
full_name ); ?> email ); ?> file_name ?: ' ' ); ?> created_at ) ) ); ?>

ACQUIS_Mailchimp::is_enabled(), 'MailWizz' => ACQUIS_Mailwizz::is_enabled(), 'Brevo' => ACQUIS_Brevo::is_enabled() ) as $name => $on ) : ?>
' . esc_html__( 'Active', 'acquisitionsuite' ) . '' : '' . esc_html__( 'Disabled', 'acquisitionsuite' ) . ''; ?>

get_notice_html(); $per_page = 20; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination $page = max( 1, absint( $_GET['acquis_page'] ?? 1 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display filter $fq = new WP_Query( array( 'post_type' => ACQUIS_File_Manager::POST_TYPE, 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'date', 'order' => 'DESC', ) ); $total_files = (int) $fq->found_posts; $total_pages = max( 1, (int) $fq->max_num_pages ); $download_counts = class_exists( 'ACQUIS_P3_DB' ) ? ACQUIS_P3_DB::get_download_counts_by_file() : array(); ?>
admin_header( esc_html__( 'Gated Files', 'acquisitionsuite' ) ); echo wp_kses_post( $notice ); ?>

PDF, ZIP, DOCX, XLSX, PPTX, MP4, MP3, JPG, PNG

have_posts() ) : ?>
have_posts() ) : $fq->the_post(); $pid = get_the_ID(); $source_type = get_post_meta( $pid, '_acquis_file_source_type', true ) ?: 'upload'; $ext = strtoupper( get_post_meta( $pid, '_acquis_file_ext', true ) ?: get_post_meta( $pid, '_sll_file_ext', true ) ); $size = ACQUIS_File_Manager::format_bytes( get_post_meta( $pid, '_acquis_file_size', true ) ?: get_post_meta( $pid, '_sll_file_size', true ) ); $dl_count = $download_counts[ $pid ] ?? 0; ?>
#
[acquis_download id=""]
1 ) : ?>
1 ) : ?>« 1 ) echo '…'; for ( $i = $start; $i <= $end; $i++ ) : ?> …'; ?> »

admin_header( esc_html__( 'Leads', 'acquisitionsuite' ), "" . number_format( $total ) . " leads" ); ?>
id ) : array(); $segment = class_exists( 'ACQUIS_Automation_Engine' ) ? ACQUIS_Automation_Engine::get_lead_segment( $l->id ) : ''; $score = class_exists( 'ACQUIS_Lead_Scorer' ) ? ACQUIS_Lead_Scorer::current_total( $l->id ) : 0; $tier = class_exists( 'ACQUIS_Lead_Scorer' ) ? ACQUIS_Lead_Scorer::score_label( $score ) : array( 'label' => ' ', 'class' => 'acquis-badge-gray' ); $stage = class_exists( 'ACQUIS_Pipeline' ) ? ACQUIS_Pipeline::get_lead_stage( $l->id ) : null; ?>
lead_sort_arrow('full_name', $orderby, $order), array( 'span' => array( 'class' => array() ) ) ); ?> Emaillead_sort_arrow('email', $orderby, $order), array( 'span' => array( 'class' => array() ) ) ); ?> lead_sort_arrow('created_at', $orderby, $order), array( 'span' => array( 'class' => array() ) ) ); ?>
full_name); ?>
email); ?> file_name ?: ' '); ?>
name); ?>
device ) ) : ?>device . ' · ' . $l->browser ); ?>
country ) ) : ?>city ? $l->city . ', ' . $l->country : $l->country ); ?>ip_address ); ?>
created_at))); ?>
1 ) : ?>
1) : ?>« 1) echo '…'; for($i=$start;$i<=$end;$i++): ?> …'; ?> »

' . esc_html__( 'Integration settings saved.', 'acquisitionsuite' ) . '

' : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display filter ?>
admin_header( esc_html__( 'Email Marketing Integrations', 'acquisitionsuite' ) ); echo wp_kses_post( $saved_msg ); ?>
Mailchimp MailWizz Brevo

Mailchimp

admin_header( esc_html__( 'Email Logs', 'acquisitionsuite' ) ); ?>
status === 'sent' ? 'acquis-badge-green' : ( $log->status === 'failed' ? 'acquis-badge-red' : 'acquis-badge-gray' ); ?>
Email
full_name ?: ' ' ); ?> email ); ?> subject ); ?> status ) ); ?> status === 'failed' && $log->error_msg ) : ?> provider ); ?> created_at ) ) ); ?>
1 ) : ?>
1 ) echo '…'; for ( $i = $start; $i <= $end; $i++ ) : ?>…'; ?>
'absint', 'acquis_free_download_limit' => 'absint', 'acquis_success_message' => 'sanitize_textarea_field', 'acquis_success_message_standalone' => 'sanitize_textarea_field', 'acquis_button_label' => 'sanitize_text_field', 'acquis_modal_title' => 'sanitize_text_field', 'acquis_modal_subtitle' => 'sanitize_textarea_field', ) as $key => $fn ) { update_option( $key, call_user_func( $fn, wp_unslash( $_POST[ $key ] ?? '' ) ) ); } // Branding opt-in — checkbox: save '1' if checked, '0' if not update_option( 'acquis_show_powered_by', isset( $_POST['acquis_show_powered_by'] ) ? '1' : '0' ); update_option( 'acquis_modal_header_color', sanitize_hex_color( wp_unslash( $_POST['acquis_modal_header_color'] ?? '#0D1B4B' ) ) ?: '#0D1B4B' ); update_option( 'acquis_modal_accent_color', sanitize_hex_color( wp_unslash( $_POST['acquis_modal_accent_color'] ?? '#F97316' ) ) ?: '#F97316' ); // Uninstall data deletion opt-in update_option( 'acquis_delete_data_on_uninstall', isset( $_POST['acquis_delete_data_on_uninstall'] ) ? '1' : '0' ); echo '

' . esc_html__( 'Settings saved.', 'acquisitionsuite' ) . '

'; } $saved_msg = isset( $_GET['acquis_saved'] ) ? '

' . esc_html__( 'Email settings saved.', 'acquisitionsuite' ) . '

' : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display filter $preview_ctx = array( 'full_name' => 'Amara Osei', 'email' => 'amara@example.com', 'file_name' => 'Marketing_Guide_2024.pdf', 'download_link' => '#preview', 'site_name' => get_bloginfo( 'name' ) ); $preview_html = ACQUIS_Email_Template::render( $preview_ctx ); $expiry_mins = absint( get_option( 'acquis_link_expiry_minutes', 30 ) ); ?>
admin_header( esc_html__( 'Settings', 'acquisitionsuite' ) ); echo wp_kses_post( $saved_msg ); ?>

= 60 ) { $hrs = floor( $expiry_mins / 60 ); $mins = $expiry_mins % 60; echo ''; echo '= ' . absint( $hrs ) . ' ' . esc_html( _n( 'hour', 'hours', $hrs, 'acquisitionsuite' ) ); if ( $mins ) echo ' ' . absint( $mins ) . ' ' . esc_html( _n( 'minute', 'minutes', absint( $mins ), 'acquisitionsuite' ) ); echo ''; } else { echo '= ' . absint( $expiry_mins ) . ' ' . esc_html__( 'minutes', 'acquisitionsuite' ) . ''; } ?>

' . esc_html__( 'Payments → Settings → General', 'acquisitionsuite' ) . '' ); ?>

array('width'=>true,'cellpadding'=>true,'cellspacing'=>true,'style'=>true,'align'=>true,'bgcolor'=>true),'td'=>array('align'=>true,'style'=>true,'colspan'=>true,'rowspan'=>true,'bgcolor'=>true),'tr'=>array('style'=>true,'bgcolor'=>true),'th'=>array('style'=>true),'img'=>array('src'=>true,'alt'=>true,'style'=>true,'width'=>true,'height'=>true,'border'=>true),'a'=>array('href'=>true,'style'=>true,'target'=>true,'rel'=>true),'center'=>array('style'=>true),'font'=>array('style'=>true,'color'=>true,'size'=>true) ) ); $template = wp_unslash( $_POST['acquis_email_template'] ?? '' ); update_option( 'acquis_email_template', wp_kses( $template, $allowed_html ) ); update_option( 'acquis_standalone_email_subject', sanitize_text_field( wp_unslash( $_POST['acquis_standalone_email_subject'] ?? '' ) ) ); $standalone_template = wp_unslash( $_POST['acquis_standalone_email_template'] ?? '' ); update_option( 'acquis_standalone_email_template', wp_kses( $standalone_template, $allowed_html ) ); wp_redirect( add_query_arg( array( 'page' => 'acquisitionsuite-settings', 'acquis_saved' => '1' ), admin_url( 'admin.php' ) ) ); exit; } public function handle_save_integrations() { if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Unauthorized', 403 ); check_admin_referer( 'acquis_save_integrations' ); // Mailchimp update_option( 'acquis_mailchimp_enabled', isset( $_POST['acquis_mailchimp_enabled'] ) ? '1' : '0' ); update_option( 'acquis_mailchimp_api_key', sanitize_text_field( wp_unslash( $_POST['acquis_mailchimp_api_key'] ?? '' ) ) ); update_option( 'acquis_mailchimp_list_id', sanitize_text_field( wp_unslash( $_POST['acquis_mailchimp_list_id'] ?? '' ) ) ); update_option( 'acquis_mailchimp_tags', sanitize_text_field( wp_unslash( $_POST['acquis_mailchimp_tags'] ?? '' ) ) ); update_option( 'acquis_mailchimp_double_optin', isset( $_POST['acquis_mailchimp_double_optin'] ) ? '1' : '0' ); // MailWizz update_option( 'acquis_mailwizz_enabled', isset( $_POST['acquis_mailwizz_enabled'] ) ? '1' : '0' ); update_option( 'acquis_mailwizz_api_url', esc_url_raw( wp_unslash( $_POST['acquis_mailwizz_api_url'] ?? '' ) ) ); update_option( 'acquis_mailwizz_public_key', sanitize_text_field( wp_unslash( $_POST['acquis_mailwizz_public_key'] ?? '' ) ) ); update_option( 'acquis_mailwizz_private_key', sanitize_text_field( wp_unslash( $_POST['acquis_mailwizz_private_key'] ?? '' ) ) ); update_option( 'acquis_mailwizz_list_uid', sanitize_text_field( wp_unslash( $_POST['acquis_mailwizz_list_uid'] ?? '' ) ) ); update_option( 'acquis_mailwizz_double_optin', isset( $_POST['acquis_mailwizz_double_optin'] ) ? '1' : '0' ); $clean_fields = array(); foreach ( (array) ( $_POST['acquis_mailwizz_custom_fields'] ?? array() ) as $row ) { if ( ! empty( $row['tag'] ) && ! empty( $row['source'] ) ) { $clean_fields[] = array( 'tag' => sanitize_text_field( $row['tag'] ), 'source' => sanitize_text_field( $row['source'] ) ); } } update_option( 'acquis_mailwizz_custom_fields', $clean_fields ); // Brevo update_option( 'acquis_brevo_enabled', isset( $_POST['acquis_brevo_enabled'] ) ? '1' : '0' ); update_option( 'acquis_brevo_api_key', sanitize_text_field( wp_unslash( $_POST['acquis_brevo_api_key'] ?? '' ) ) ); update_option( 'acquis_brevo_list_id', absint( $_POST['acquis_brevo_list_id'] ?? 0 ) ); update_option( 'acquis_brevo_double_optin', isset( $_POST['acquis_brevo_double_optin'] ) ? '1' : '0' ); update_option( 'acquis_brevo_doi_template_id', absint( $_POST['acquis_brevo_doi_template_id'] ?? 0 ) ); // Allow Pro to save its own integration options (e.g. WhatsApp, Geo Lookup) do_action( 'acquis_save_integrations_extra' ); wp_redirect( add_query_arg( array( 'page' => 'acquisitionsuite-integrations', 'acquis_saved' => '1' ), admin_url( 'admin.php' ) ) ); exit; } public function handle_file_upload() { if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Unauthorized', 403 ); check_admin_referer( 'acquis_upload_file', 'acquis_upload_nonce' ); $title = sanitize_text_field( wp_unslash( $_POST['file_title'] ?? '' ) ); $source_type = ( 'url' === ( $_POST['acquis_source_type'] ?? 'upload' ) ) ? 'url' : 'upload'; if ( empty( $title ) ) { wp_redirect( add_query_arg( 'acquis_msg', 'err_move', admin_url( 'admin.php?page=acquisitionsuite-files' ) ) ); exit; } if ( 'url' === $source_type ) { $url = esc_url_raw( wp_unslash( $_POST['file_url'] ?? '' ) ); $result = ACQUIS_File_Manager::handle_url_file( $url, $title ); $msg = is_wp_error( $result ) ? 'err_url' : 'uploaded'; } else { if ( ! isset( $_FILES['acquis_file'] ) || $_FILES['acquis_file']['error'] !== UPLOAD_ERR_OK ) { wp_redirect( add_query_arg( 'acquis_msg', 'err_move', admin_url( 'admin.php?page=acquisitionsuite-files' ) ) ); exit; } $result = ACQUIS_File_Manager::handle_upload( $_FILES['acquis_file'], $title ); $msg = is_wp_error( $result ) ? ( $result->get_error_code() === 'acquis_invalid_type' ? 'err_type' : 'err_move' ) : 'uploaded'; } if ( ! is_wp_error( $result ) ) { // Allow Pro to save paid download config for the new file do_action( 'acquis_after_file_upload', $result ); } wp_redirect( add_query_arg( 'acquis_msg', $msg, admin_url( 'admin.php?page=acquisitionsuite-files' ) ) ); exit; } public function handle_file_delete() { if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Unauthorized', 403 ); $fid = absint( $_POST['file_id'] ?? 0 ); check_admin_referer( 'acquis_delete_file_' . $fid, 'acquis_delete_nonce' ); $file = ACQUIS_File_Manager::get_file_data( $fid ); if ( $file && file_exists( $file['path'] ) ) { wp_delete_file( $file['path'] ); // WP Filesystem API per WP.org Guideline 7 } wp_delete_post( $fid, true ); wp_redirect( add_query_arg( 'acquis_msg', 'deleted', admin_url( 'admin.php?page=acquisitionsuite-files' ) ) ); exit; } private function admin_header( $title, $extra = '' ) { $title_text = trim( wp_strip_all_tags( $title ) ); ?>

v
rawurlencode( 'Turning file downloads into leads with AcquisitionSuite, the WordPress plugin for lead capture, automation, and paid downloads.' ), 'url' => rawurlencode( 'https://acquisitionsuite.ufirstdev.com' ), ), 'https://twitter.com/intent/tweet' ); $ajax_url = admin_url( 'admin-ajax.php' ); $nonce = wp_create_nonce( 'acquis_admin_nonce' ); ?>

array('success', 'File saved successfully.'), 'deleted' => array('success','File deleted.'), 'err_type' => array('error','File type not allowed.'), 'err_move' => array('error','Upload failed.'), 'err_url' => array('error','Please enter a valid http(s) URL.'), 'pricing_saved' => array('success','Pricing settings saved.') ); $key = sanitize_key( $_GET['acquis_msg'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display filter if ( ! isset( $msgs[$key] ) ) return ''; return '

' . esc_html( $msgs[$key][1] ) . '

'; } /** * Sortable-column helpers for the Leads table. These were previously * declared as global functions inside render_leads_page() itself, * which would trigger a fatal "cannot redeclare" error if that method * were ever called more than once in the same request converted to * private class methods to fix that. */ private function lead_sort_url( $col, $ob, $o ) { $no = ( $col === $ob && $o === 'ASC' ) ? 'DESC' : 'ASC'; return add_query_arg( array( 'orderby' => $col, 'order' => $no, 'acquis_page' => 1 ) ); } private function lead_sort_arrow( $col, $ob, $o ) { return $col != $ob ? '' : ( $o === 'ASC' ? ' ▲' : ' ▼' ); } public function ajax_delete_lead() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized' ); } check_ajax_referer( 'acquis_admin_nonce', 'nonce' ); $lead_id = absint( $_POST['lead_id'] ?? 0 ); if ( ! $lead_id ) { wp_send_json_error( 'Invalid lead ID' ); } global $wpdb; // Delete all data associated with this lead cascade to every // related table so no orphaned records remain. The lead row is // the central entity; without it, all downstream records are // meaningless and waste space. // 1. Submission values (must go before submissions so FK is clean) $sub_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}acquis_submissions WHERE lead_id=%d", $lead_id ) ); if ( $sub_ids ) { $ids = array_map( 'absint', $sub_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $lt is internal table name; placeholders built with array_fill+implode $ph = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); $lt_sv = $wpdb->prefix . 'acquis_submission_values'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from $wpdb->prefix + hardcoded string; $ph is %d placeholders only $wpdb->query( $wpdb->prepare( "DELETE FROM `{$lt_sv}` WHERE submission_id IN ({$ph})", $ids ) ); } // 2. Submissions $wpdb->delete( $wpdb->prefix . 'acquis_submissions', array( 'lead_id' => $lead_id ) ); // 3. Download tokens $wpdb->delete( $wpdb->prefix . 'acquis_tokens', array( 'lead_id' => $lead_id ) ); // 4. Email logs $wpdb->delete( $wpdb->prefix . 'acquis_email_logs', array( 'lead_id' => $lead_id ) ); // Pro plugin handles its own cascade deletes via this hook do_action( 'acquis_before_lead_deleted', $lead_id ); // 10. Finally delete the lead itself $deleted = $wpdb->delete( $wpdb->prefix . 'acquis_leads', array( 'id' => $lead_id ) ); if ( $deleted === false ) { wp_send_json_error( 'Delete failed' ); } wp_send_json_success( array( 'id' => $lead_id ) ); } /** * AJAX — bulk delete multiple leads by ID array. * Each deletion follows the same path as single-lead delete so Pro cascade hook fires. */ public function ajax_bulk_delete_leads() { check_ajax_referer( 'acquis_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Forbidden.' ), 403 ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below per-ID $raw_ids = isset( $_POST['ids'] ) ? (array) $_POST['ids'] : array(); $ids = array_filter( array_map( 'absint', $raw_ids ) ); if ( empty( $ids ) ) { wp_send_json_error( array( 'message' => esc_html__( 'No leads selected.', 'acquisitionsuite' ) ) ); } $deleted = 0; foreach ( $ids as $lead_id ) { $this->delete_lead( $lead_id ); $deleted++; } wp_send_json_success( array( 'message' => sprintf( /* translators: %d: number of leads deleted */ esc_html__( '%d lead(s) deleted.', 'acquisitionsuite' ), $deleted ), 'deleted' => $deleted, ) ); } public function ajax_delete_email_log() { if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error(); check_ajax_referer( 'acquis_admin_nonce', 'nonce' ); global $wpdb; $id = absint( $_POST['id'] ?? 0 ); if ( ! $id ) wp_send_json_error(); $wpdb->delete( $wpdb->prefix . 'acquis_email_logs', array( 'id' => $id ) ); wp_send_json_success(); } public function ajax_bulk_clear_email_logs() { if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error(); check_ajax_referer( 'acquis_admin_nonce', 'nonce' ); global $wpdb; $status = sanitize_key( $_POST['status'] ?? '' ); $before = sanitize_text_field( $_POST['before'] ?? '' ); $table = $wpdb->prefix . 'acquis_email_logs'; $where = '1=1'; $args = array(); if ( $status ) { $where .= ' AND status=%s'; $args[] = $status; } if ( $before ) { $where .= ' AND created_at<%s'; $args[] = $before . ' 00:00:00'; } // Use %i placeholder for table name so plugin check sees no bare interpolation. // $where is built entirely from wpdb->prepare() calls — safe to pass as string. if ( $args ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where built with prepare(); $table from $wpdb->prefix + hardcoded string $count = $wpdb->query( $wpdb->prepare( "DELETE FROM `{$table}` WHERE {$where}", ...$args ) ); } else { // No filters applied — $where is the literal string '1=1'; $table from $wpdb->prefix only // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $table from $wpdb->prefix + hardcoded string; no user input in query $count = $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE 1=1', $table ) ); } wp_send_json_success( array( 'count' => (int) $count ) ); } /** * AJAX — resend the download email from the Email Logs page. * Looks up the original log row, fetches the lead + file, and re-sends. */ public function ajax_resend_email_log() { check_ajax_referer( 'acquis_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Forbidden.' ), 403 ); } $log_id = absint( $_POST['log_id'] ?? 0 ); if ( ! $log_id ) { wp_send_json_error( array( 'message' => esc_html__( 'Invalid log ID.', 'acquisitionsuite' ) ) ); } global $wpdb; $log = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}acquis_email_logs WHERE id = %d", $log_id ) ); if ( ! $log ) { wp_send_json_error( array( 'message' => esc_html__( 'Email log not found.', 'acquisitionsuite' ) ) ); } $lead = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}acquis_leads WHERE id = %d", absint( $log->lead_id ) ) ); if ( ! $lead ) { wp_send_json_error( array( 'message' => esc_html__( 'Lead not found.', 'acquisitionsuite' ) ) ); } // Regenerate a fresh download token so the link is valid $token = ACQUIS_Database::create_token( $lead->id, absint( $log->file_id ) ); $dl_url = add_query_arg( array( 'acquis_download' => 1, 'token' => $token ), home_url( '/' ) ); $file = ACQUIS_File_Manager::get_file_data( absint( $log->file_id ) ); if ( class_exists( 'ACQUIS_Email_Engine' ) ) { ACQUIS_Email_Engine::send( $lead->email, $lead->full_name, $file, $dl_url ); } wp_send_json_success( array( 'message' => esc_html__( 'Email resent successfully.', 'acquisitionsuite' ) ) ); } /** Daily cron: auto-purge old records per retention settings. */ public function run_daily_cleanup() { global $wpdb; $log_days = absint( get_option( 'acquis_retention_email_logs', 30 ) ); if ( $log_days > 0 ) { $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}acquis_email_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $log_days ) ); } // Pro retention cleanup (sequences, orders, transactions) handled by Pro do_action( 'acquis_daily_cleanup_pro' ); } // ══════════════════════════════════════════════════════════════════════════ // SYSTEM STATUS PAGE // ══════════════════════════════════════════════════════════════════════════ public function render_upgrade_to_pro_page() { $pricing_url = esc_url( 'https://acquisitionsuite.ufirstdev.com/pricing/' ); ?>
admin_header( '' ); ?>
⚡ ACQUISITIONSUITE PRO

Your leads deserve more than a spreadsheet.

AcquisitionSuite Pro gives you the tools to automatically follow up, score, segment, and sell to every lead you capture. Everything runs inside WordPress. No third-party tools required.

⚡ See Plans & Pricing

14-day money-back guarantee on all paid plans

What you have with Lite

✓

Unlocked with Pro

⚡

What Pro makes possible

'🤖', 'title' => 'Marketing Automation', 'tagline' => 'Work smarter, not harder.', 'body' => 'Build automation rules that fire when a lead submits a form, reaches a score threshold, makes a payment, or visits a page. Each rule can trigger one or more actions: send an email, apply a tag, assign a pipeline stage, fire a webhook, add them to a sequence, or notify your team. No coding. No Zapier.', ), array( 'icon' => '📧', 'title' => 'Email Sequences', 'tagline' => 'Follow up while you sleep.', 'body' => 'Create drip campaigns with any number of steps, each sent at a delay you control (minutes, hours, or days). Trigger sequences automatically when a form is submitted or a payment completes. Every email uses your branded template and supports personalisation tags.', ), array( 'icon' => '🎯', 'title' => 'Lead Scoring', 'tagline' => 'Know who is ready to buy.', 'body' => 'Define scoring rules based on form field values, geography, device type, download behavior, email engagement, and payment history. Every lead gets a live score and a tier label (Cold, Warm, Qualified, Hot, Sales Ready) so you know exactly where to focus your time.', ), array( 'icon' => '💳', 'title' => 'Paid Downloads', 'tagline' => 'Sell your files directly from WordPress.', 'body' => 'Turn any uploaded file into a paid product. Accept payments through Stripe, Paystack, or Flutterwave. Buyers complete payment on your site, receive a receipt by email, and get a secure time-limited download link. All order and transaction history is managed inside WordPress. No commission taken.', ), array( 'icon' => '📊', 'title' => 'Sales Pipeline', 'tagline' => 'Turn your lead list into a sales process.', 'body' => 'A visual kanban board where every lead lives in a stage. Drag leads between stages as they progress. Create custom stages that match your actual sales process. Move leads automatically with automation rules when specific conditions are met.', ), array( 'icon' => '💫', 'title' => 'Lead Intelligence', 'tagline' => 'Know your leads before you call them.', 'body' => 'For every new lead, AcquisitionSuite Pro automatically detects their country, city, ISP, device type, operating system, and browser. This data feeds into lead scoring and is visible in the lead detail view, giving you context before you ever make contact.', ), ); foreach ( $deep_dives as $dive ) : ?>

AcquisitionSuite Lite captures leads.
Pro turns them into customers.

Everything runs inside your WordPress site. No monthly SaaS fees. No per-email charges. No limits on leads captured.

⚡ View Plans & Upgrade Now
'#16a34a', 'warn' => '#d97706', 'fail' => '#dc2626' ); $icons = array( 'ok' => '✓', 'warn' => '!', 'fail' => '✗' ); $color = $colors[ $status ] ?? $colors['ok']; $icon = $icons[ $status ] ?? '✓'; echo ''; echo '' . esc_html( $label ) . ''; echo ''; echo ''; echo '' . esc_html( $icon ) . ''; echo esc_html( $value ); echo ''; if ( $note ) echo ' ' . esc_html( $note ) . ''; echo ''; }; $section = function( $title ) { echo '' . esc_html( $title ) . ''; }; ?>
admin_header( ' ' . esc_html__( 'System Status', 'acquisitionsuite' ) ); ?>

=' ); $row( 'WordPress Version', $wp_ver . ' (required: 6.2+)', $wp_ok ? 'ok' : 'fail', $wp_ok ? '' : 'Please update WordPress.' ); $php_ver = PHP_VERSION; $php_ok = version_compare( $php_ver, '7.4', '>=' ); $row( 'PHP Version', $php_ver . ' (required: 7.4+)', $php_ok ? 'ok' : 'fail', $php_ok ? '' : 'AcquisitionSuite requires PHP 7.4 or higher. Please upgrade.' ); $php_warn = version_compare( $php_ver, '8.0', '>=' ); $row( 'PHP 8.0+ (recommended)', $php_warn ? 'Yes' : 'No', $php_warn ? 'ok' : 'warn', $php_warn ? '' : 'PHP 8.0+ is recommended for best performance and security.' ); $memory_limit = ini_get('memory_limit'); $memory_mb = (int) $memory_limit; $mem_status = $memory_mb >= 256 ? 'ok' : ( $memory_mb >= 128 ? 'warn' : 'fail' ); $row( 'PHP Memory Limit', $memory_limit, $mem_status, $memory_mb < 256 ? '256MB+ recommended.' : '' ); $max_upload = ini_get('upload_max_filesize'); $row( 'Max Upload Size (upload_max_filesize)', $max_upload, 'ok' ); $post_max = ini_get('post_max_size'); $row( 'Max POST Size (post_max_size)', $post_max, 'ok' ); $max_exec = ini_get('max_execution_time'); $exec_ok = (int) $max_exec === 0 || (int) $max_exec >= 30; $row( 'Max Execution Time', $max_exec . 's', $exec_ok ? 'ok' : 'warn', $exec_ok ? '' : 'At least 30s recommended.' ); $row( 'WordPress Timezone', wp_timezone_string(), 'ok' ); $row( 'Server Time (UTC)', gmdate( 'Y-m-d H:i:s' ), 'ok' ); $row( 'WordPress Time', current_time('Y-m-d H:i:s'), 'ok' ); // ── PHP Extensions ───────────────────────────────────────────────── $section( esc_html__( 'PHP Extensions', 'acquisitionsuite' ) ); foreach ( array( 'curl' => array( true, 'Required for gateway API calls and WhatsApp.' ), 'json' => array( true, 'Required for all API communication.' ), 'mbstring' => array( true, 'Required for multi-byte string handling.' ), 'openssl' => array( true, 'Required for webhook signature verification (Stripe, Paystack, Flutterwave).' ), 'fileinfo' => array( false, 'Recommended for better MIME type detection on file uploads.' ), 'zip' => array( false, 'Optional needed only if you plan to serve ZIP files.' ), ) as $ext => list( $required, $note ) ) { $has = extension_loaded( $ext ); $status = $has ? 'ok' : ( $required ? 'fail' : 'warn' ); $row( 'PHP Extension: ' . $ext, $has ? 'Enabled' : 'Disabled', $status, $has ? '' : $note ); } // ── WordPress Configuration ───────────────────────────────────────── $section( esc_html__( 'WordPress Configuration', 'acquisitionsuite' ) ); $row( 'WordPress Cron (WP-Cron)', defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? 'Disabled' : 'Enabled', defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? 'warn' : 'ok', defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? 'WP-Cron is disabled. Sequences and time-based automation will not fire unless you have a real server-side cron job configured.' : '' ); $row( 'SSL / HTTPS', is_ssl() ? 'Yes' : 'No', is_ssl() ? 'ok' : 'warn', is_ssl() ? '' : 'HTTPS is required for live payment gateway webhooks.' ); $row( 'WP Debug Mode', WP_DEBUG ? 'On' : 'Off', WP_DEBUG ? 'warn' : 'ok', WP_DEBUG ? 'Turn off WP_DEBUG on production sites.' : '' ); $row( 'WP Debug Log', ( defined('WP_DEBUG_LOG') && WP_DEBUG_LOG ) ? 'On' : 'Off', 'ok' ); $row( 'Plugin Version', ACQUIS_VERSION, 'ok' ); $row( 'Database Version', get_option('acquis_db_version', 'not set'), get_option('acquis_db_version') === ACQUIS_VERSION ? 'ok' : 'warn', get_option('acquis_db_version') !== ACQUIS_VERSION ? 'Run the upgrade by deactivating and reactivating the plugin, or upgrading to the latest version.' : '' ); // ── File System ───────────────────────────────────────────────────── $section( esc_html__( 'File System & Protected Directory', 'acquisitionsuite' ) ); $upload_dir = wp_upload_dir(); $protect_dir = $upload_dir['basedir'] . '/acquis-protected'; $row( 'Protected Upload Directory', $protect_dir, 'ok' ); $row( 'Directory Exists', is_dir( $protect_dir ) ? 'Yes' : 'No', is_dir( $protect_dir ) ? 'ok' : 'fail', ! is_dir( $protect_dir ) ? 'Upload a file to create it.' : '' ); $row( 'Directory Writable', is_writable( $protect_dir ) ? 'Yes' : ( is_dir( $protect_dir ) ? 'No ACTION REQUIRED' : 'N/A' ), is_dir( $protect_dir ) ? ( is_writable( $protect_dir ) ? 'ok' : 'fail' ) : 'ok', ! is_writable( $protect_dir ) && is_dir( $protect_dir ) ? 'WordPress must be able to write to this directory to store gated files.' : '' ); $htaccess_ok = file_exists( $protect_dir . '/.htaccess' ); $row( '.htaccess Protection File', $htaccess_ok ? 'Present' : 'Missing', $htaccess_ok ? 'ok' : 'fail', ! $htaccess_ok ? 'Upload any file to regenerate it.' : '' ); // ── Database ─────────────────────────────────────────────────────── $section( esc_html__( 'Database Tables', 'acquisitionsuite' ) ); // Only check Lite-owned tables. Pro plugin adds its own table check // via the acquis_system_status_tables filter. $lite_tables = array( 'acquis_leads', 'acquis_tokens', 'acquis_email_logs', 'acquis_integration_logs', 'acquis_forms', 'acquis_form_fields', 'acquis_submissions', 'acquis_submission_values', 'acquis_analytics', ); $expected_tables = apply_filters( 'acquis_system_status_tables', $lite_tables ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery -- % appended after esc_like() so it is the LIKE wildcard, not a literal $existing_tables = $wpdb->get_col( $wpdb->prepare( "SHOW TABLES LIKE %s", $wpdb->esc_like( $wpdb->prefix . 'acquis_' ) . '%' ) ); $missing = array(); foreach ( $expected_tables as $t ) { if ( ! in_array( $wpdb->prefix . $t, $existing_tables, true ) ) { $missing[] = $t; } } $row( 'Expected Tables', count( $expected_tables ), 'ok' ); $row( 'Tables Found', count( $existing_tables ), count( $missing ) === 0 ? 'ok' : 'fail' ); if ( $missing ) { $row( 'Missing Tables', implode( ', ', $missing ), 'fail', 'Deactivate and reactivate the plugin to recreate missing tables.' ); } // Delay unit migration check $steps_table = $wpdb->prefix . 'acquis_sequence_steps'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- INFORMATION_SCHEMA is a system catalog; table name from $wpdb->prefix + hardcoded string $has_unit = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'delay_unit'", DB_NAME, $steps_table ) ); $row( 'Sequence Steps: delay_unit column', $has_unit ? 'Present' : 'Missing upgrade required', $has_unit ? 'ok' : 'warn', ! $has_unit ? 'Run maybe_upgrade() by deactivating and reactivating the plugin.' : '' ); // ── Cron ──────────────────────────────────────────────────────────── $section( esc_html__( 'Scheduled Tasks (Cron)', 'acquisitionsuite' ) ); $seq_hook = class_exists( 'ACQUIS_Sequences' ) ? ACQUIS_Sequences::CRON_HOOK : 'acquis_process_sequences'; $tb_hook = class_exists( 'ACQUIS_Time_Based_Scheduler' ) ? ACQUIS_Time_Based_Scheduler::CRON_HOOK : 'acquis_process_time_based_rules'; $seq_next = wp_next_scheduled( $seq_hook ); $tb_next = wp_next_scheduled( $tb_hook ); $row( 'Email Sequences Cron', $seq_next ? 'Scheduled (next: ' . date_i18n( 'Y-m-d H:i:s', $seq_next ) . ')' : 'NOT SCHEDULED', $seq_next ? 'ok' : 'fail', ! $seq_next ? 'Re-activate the plugin to reschedule.' : '' ); $row( 'Time-Based Automation Cron', $tb_next ? 'Scheduled (next: ' . date_i18n( 'Y-m-d H:i:s', $tb_next ) . ')' : 'NOT SCHEDULED', $tb_next ? 'ok' : 'fail', ! $tb_next ? 'Re-activate the plugin to reschedule.' : '' ); // ── Payment Gateways ─────────────────────────────────────────────── $section( esc_html__( 'Payment Gateways', 'acquisitionsuite' ) ); if ( class_exists( 'ACQUIS_Pay_Gateway_Registry' ) ) { foreach ( ACQUIS_Pay_Gateway_Registry::get_all() as $slug => $gateway ) { $configured = $gateway->is_configured(); $row( $gateway->get_label() . ' (' . $slug . ')', $configured ? 'Configured' : 'Not configured', $configured ? 'ok' : 'warn', $configured ? '' : 'Add API credentials under Payments → Settings.' ); } } else { $row( 'Gateway Registry', 'Unavailable', 'fail' ); } $currency = get_option( 'acquis_pay_default_currency', 'USD' ); $symbol = class_exists( 'ACQUIS_Pay_DB' ) ? ACQUIS_Pay_DB::get_currency_symbol( $currency ) : $currency; $row( 'Active Currency', $currency . ' (' . $symbol . ')', 'ok' ); // ── Email ────────────────────────────────────────────────────────── $section( esc_html__( 'Email', 'acquisitionsuite' ) ); $from_name = get_option( 'acquis_email_from_name', '' ); $from_address = get_option( 'acquis_email_from_address', '' ); $row( 'From Name', $from_name ?: esc_html__( '(using site name)', 'acquisitionsuite' ), $from_name ? 'ok' : 'warn' ); $row( 'From Address', $from_address ?: esc_html__( '(using admin email)', 'acquisitionsuite' ), ( $from_address && is_email( $from_address ) ) ? 'ok' : 'warn', ( $from_address && ! is_email( $from_address ) ) ? 'Invalid email address.' : '' ); // Detect SMTP / transactional mail plugin checks common plugins including AWS SES $smtp_plugin_name = ''; if ( defined( 'WPMS_PLUGIN_VER' ) || class_exists( 'WPMailSMTP\Core' ) || function_exists( 'wp_mail_smtp' ) ) { $smtp_plugin_name = 'WP Mail SMTP'; } elseif ( class_exists( 'WP_Offload_SES' ) || class_exists( 'WP_Offload_SES_Lite' ) ) { $smtp_plugin_name = 'WP Offload SES (AWS SES)'; } elseif ( get_option( 'wpses_settings' ) || class_exists( 'WP_SES' ) ) { $smtp_plugin_name = 'WP SES (AWS SES)'; } elseif ( defined( 'FLUENT_MAIL_PLUGIN_FILE' ) ) { $smtp_plugin_name = 'FluentSMTP'; } elseif ( class_exists( 'Postmark_Mail' ) || defined( 'POSTMARK_API_KEY' ) ) { $smtp_plugin_name = 'Postmark'; } elseif ( function_exists( 'mailgun_init' ) || get_option( 'mailgun' ) ) { $smtp_plugin_name = 'Mailgun'; } elseif ( defined( 'SENDGRID_API_KEY' ) ) { $smtp_plugin_name = 'SendGrid'; } elseif ( defined( 'SENDINBLUE_SMTP' ) ) { $smtp_plugin_name = 'Brevo / Sendinblue'; } elseif ( has_action( 'phpmailer_init' ) ) { $smtp_plugin_name = 'Custom SMTP handler detected'; } $smtp_detected = ! empty( $smtp_plugin_name ); $row( 'SMTP / Mail Plugin', $smtp_detected ? $smtp_plugin_name : 'None detected', $smtp_detected ? 'ok' : 'warn', ! $smtp_detected ? 'No SMTP plugin found. Without one, emails likely fail silently. Install WP Offload SES, WP Mail SMTP, or FluentSMTP.' : '' ); ?>