';
}
/**
* 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.
'🤖',
'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.