prefix . 'acquis_submissions';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- one-time schema check on our own table
if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $acquis_sub_table ) ) ) === $acquis_sub_table ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- one-time schema check on our own table
if ( ! $wpdb->get_var( $wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $acquis_sub_table, 'source' ) ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- adding a column to our own table; name comes from $wpdb->prefix
$wpdb->query( "ALTER TABLE {$acquis_sub_table} ADD COLUMN source VARCHAR(40) NOT NULL DEFAULT '' AFTER ip_address, ADD KEY source (source)" );
}
update_option( 'acquis_schema_submission_source', 1, false );
}
}
// ── Schema migrations run on EVERY page load ───────────────────────
// the site was on when the migration was introduced. This prevents
// the silent failure where a site already matching ACQUIS_LITE_VERSION would
// skip the early-return gate and never receive schema changes added
// in a later patch within the same version series.
//
// Each migration is guarded by a table-existence check first so that
// a fresh install (where dbDelta() has not yet run) does not attempt
// Pro table migrations handled by AcquisitionSuite Pro plugin
// ── Version-gated setup (seeds, table creation, defaults) ─────────
// These only need to run when version changes they are NOT safe to
// run on every request (seeding would overwrite admin changes).
if ( get_option( 'acquis_db_version' ) === ACQUIS_LITE_VERSION ) {
return;
}
self::create_tables();
self::set_defaults();
self::seed_default_email_template();
self::seed_default_standalone_email_template();
if ( class_exists( 'ACQUIS_Tags' ) ) {
ACQUIS_Tags::migrate_legacy_options();
}
update_option( 'acquis_db_version', ACQUIS_LITE_VERSION );
}
/**
* Same one-time-migration treatment as seed_default_email_template(),
* but for the three Payments email templates same underlying
* add_option() no-op problem, same fix: only fill in the new branded
* default when the existing value is genuinely still blank, OR is
* still the old plain-text default from before this email design
* was introduced (detected via a short, distinctive fragment unique
* to each old default extremely unlikely to appear in any
* genuinely admin-authored custom template, so this can't accidentally
* clobber real customization).
*/
public static function deactivate() {
flush_rewrite_rules();
}
public static function create_tables() {
global $wpdb;
$c = $wpdb->get_charset_collate();
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
// ── Phase 1: Leads ────────────────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_leads (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
full_name varchar(191) NOT NULL,
email varchar(191) NOT NULL,
file_id BIGINT(20) UNSIGNED NOT NULL,
ip_address VARCHAR(45) NOT NULL DEFAULT '',
user_agent VARCHAR(500) NOT NULL DEFAULT '',
device VARCHAR(20) NOT NULL DEFAULT '',
browser VARCHAR(50) NOT NULL DEFAULT '',
os VARCHAR(50) NOT NULL DEFAULT '',
country VARCHAR(100) NOT NULL DEFAULT '',
region VARCHAR(100) NOT NULL DEFAULT '',
city VARCHAR(100) NOT NULL DEFAULT '',
visit_count INT(11) UNSIGNED NOT NULL DEFAULT 1,
last_seen_at DATETIME DEFAULT NULL,
deal_value DECIMAL(12,2) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY email (email),
KEY file_id (file_id),
KEY created_at (created_at),
KEY country (country)
) $c;" );
// ── Phase 1: Tokens ───────────────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_tokens (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
token VARCHAR(64) NOT NULL,
lead_id BIGINT(20) UNSIGNED NOT NULL,
file_id BIGINT(20) UNSIGNED NOT NULL,
expires_at DATETIME NOT NULL,
used TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY expires_at (expires_at)
) $c;" );
// ── Phase 2: Email logs ───────────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_email_logs (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
lead_id BIGINT(20) UNSIGNED NOT NULL,
email varchar(191) NOT NULL,
subject VARCHAR(500) NOT NULL DEFAULT '',
status VARCHAR(20) NOT NULL DEFAULT 'queued',
provider VARCHAR(50) NOT NULL DEFAULT 'wp_mail',
error_msg TEXT,
opened_at DATETIME DEFAULT NULL,
click_count INT(11) UNSIGNED NOT NULL DEFAULT 0,
last_clicked_at DATETIME DEFAULT NULL,
sequence_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
sequence_step BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY lead_id (lead_id),
KEY status (status),
KEY created_at (created_at)
) $c;" );
// ── Phase 2: Integration logs ─────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_integration_logs (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
lead_id BIGINT(20) UNSIGNED NOT NULL,
integration VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'skipped',
response TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY lead_id (lead_id)
) $c;" );
// ── Phase 3: Forms ────────────────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_forms (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
title varchar(191) NOT NULL,
description TEXT,
steps TINYINT(3) UNSIGNED NOT NULL DEFAULT 1,
settings LONGTEXT,
status VARCHAR(20) NOT NULL DEFAULT 'active',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY status (status)
) $c;" );
// ── Phase 3: Form fields ──────────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_form_fields (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
form_id BIGINT(20) UNSIGNED NOT NULL,
field_key VARCHAR(100) NOT NULL,
field_type VARCHAR(50) NOT NULL,
label VARCHAR(300) NOT NULL,
placeholder VARCHAR(300) NOT NULL DEFAULT '',
help_text VARCHAR(500) NOT NULL DEFAULT '',
options TEXT,
step TINYINT(3) UNSIGNED NOT NULL DEFAULT 1,
sort_order SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0,
required TINYINT(1) NOT NULL DEFAULT 0,
validation TEXT,
conditions TEXT,
settings TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY form_id (form_id),
KEY sort_order (sort_order)
) $c;" );
// ── Phase 3: Submissions ──────────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_submissions (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
form_id BIGINT(20) UNSIGNED NOT NULL,
lead_id BIGINT(20) UNSIGNED NOT NULL,
file_id BIGINT(20) UNSIGNED NOT NULL,
source_url VARCHAR(1000) NOT NULL DEFAULT '',
source_post BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
referrer VARCHAR(1000) NOT NULL DEFAULT '',
utm_source varchar(191) NOT NULL DEFAULT '',
utm_medium varchar(191) NOT NULL DEFAULT '',
utm_campaign varchar(191) NOT NULL DEFAULT '',
ip_address VARCHAR(45) NOT NULL DEFAULT '',
source VARCHAR(40) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY form_id (form_id),
KEY source (source),
KEY lead_id (lead_id),
KEY file_id (file_id),
KEY created_at (created_at)
) $c;" );
// ── Phase 3: Submission field values ──────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_submission_values (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
submission_id BIGINT(20) UNSIGNED NOT NULL,
field_key VARCHAR(100) NOT NULL,
field_label VARCHAR(300) NOT NULL DEFAULT '',
value TEXT,
PRIMARY KEY (id),
KEY submission_id (submission_id),
KEY field_key (field_key)
) $c;" );
// ── Phase 3: Analytics events ─────────────────────────────────────
dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}acquis_analytics (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
event_type VARCHAR(30) NOT NULL,
form_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
file_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
lead_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
source_post BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
source_url VARCHAR(1000) NOT NULL DEFAULT '',
utm_source varchar(191) NOT NULL DEFAULT '',
utm_medium varchar(191) NOT NULL DEFAULT '',
utm_campaign varchar(191) NOT NULL DEFAULT '',
ip_address VARCHAR(45) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY event_type (event_type),
KEY file_id (file_id),
KEY lead_id (lead_id),
KEY created_at (created_at)
) $c;" );
// Pro tables created by AcquisitionSuite Pro plugin
}
/**
* Returns only the options that belong to the Lite plugin.
* Pro-owned options are initialised by AcquisitionSuite Pro's own activator.
*/
private static function get_default_options() {
return array(
// ── Phase 1 ───────────────────────────────────────────────────
'acquis_link_expiry_minutes' => 30,
'acquis_allowed_types' => array( 'pdf','zip','docx','xlsx','pptx','mp4','mp3','jpg','jpeg','png' ),
'acquis_success_message' => 'Your download is ready! Click the button below.',
'acquis_success_message_standalone' => 'Thank you! Your submission has been received.',
'acquis_button_label' => 'Download File',
'acquis_modal_title' => 'Get Instant Access',
'acquis_modal_subtitle' => 'Enter your details below to download this file for free.',
'acquis_modal_header_color' => '#0D1B4B',
'acquis_modal_accent_color' => '#F97316',
// ── Phase 2 ───────────────────────────────────────────────────
'acquis_delivery_mode' => 'both',
'acquis_email_from_name' => get_bloginfo( 'name' ),
'acquis_email_from_address' => get_bloginfo( 'admin_email' ),
'acquis_email_subject' => 'Your download is ready, {full_name}!',
'acquis_email_template' => '', // seeded separately by seed_default_email_template()
'acquis_standalone_email_subject' => 'Thanks for your submission, {full_name}!',
'acquis_standalone_email_template' => '', // seeded separately by seed_default_standalone_email_template()
'acquis_email_header_color' => '#1a73e8',
'acquis_email_button_color' => '#0f9d58',
'acquis_email_button_text' => 'Download My File',
'acquis_email_footer_text' => 'You received this because you requested a file from {site_name}.',
'acquis_email_logo_url' => ACQUIS_PLUGIN_URL . 'assets/images/logo-minimal.png',
// ── ESP Integrations ──────────────────────────────────────────
'acquis_mailchimp_enabled' => '0',
'acquis_mailchimp_api_key' => '',
'acquis_mailchimp_list_id' => '',
'acquis_mailchimp_tags' => '',
'acquis_mailchimp_double_optin' => '0',
'acquis_mailwizz_enabled' => '0',
'acquis_mailwizz_api_url' => '',
'acquis_mailwizz_public_key' => '',
'acquis_mailwizz_private_key' => '',
'acquis_mailwizz_list_uid' => '',
'acquis_mailwizz_double_optin' => '0',
'acquis_mailwizz_custom_fields' => array(),
'acquis_brevo_enabled' => '0',
'acquis_brevo_api_key' => '',
'acquis_brevo_list_id' => '',
'acquis_brevo_double_optin' => '0',
// ── Phase 3 ───────────────────────────────────────────────────
'acquis_analytics_period' => '30',
// ── Settings ──────────────────────────────────────────────────
'acquis_show_powered_by' => '0',
'acquis_delete_data_on_uninstall' => '0',
'acquis_retention_email_logs' => 30,
// Pro-owned options (whatsapp, scoring, payments, geo) are
// initialised by AcquisitionSuite Pro's own activator.
);
}
/**
* Seed the default download-email template (Lite only).
* Uses add_option() so it never overwrites admin customisations.
*/
private static function seed_default_email_template() {
if ( '' !== trim( (string) get_option( 'acquis_email_template', '' ) ) ) {
return;
}
update_option( 'acquis_email_template', self::lite_email_template_html() );
}
/**
* Seed the default standalone-form email template (3.6.0) — separate
* from the file-download template above since "Your file is ready"
* copy makes no sense for an appointment/registration submission.
* Same add_option()-style guard: never overwrites an admin's own edits.
*/
private static function seed_default_standalone_email_template() {
if ( '' !== trim( (string) get_option( 'acquis_standalone_email_template', '' ) ) ) {
return;
}
// Single source of truth lives on ACQUIS_Email_Template — also used
// as its own runtime fallback and by the Form Builder's "Load
// Default Template" button, so all three can never drift apart.
update_option( 'acquis_standalone_email_template', ACQUIS_Email_Template::get_default_standalone_template() );
}
/**
* Minimal branded HTML email template for the Lite download notification.
*/
private static function lite_email_template_html() {
return '
{{logo_block}}
Your file is ready!
|
|
Hi {full_name},
Thank you for your interest. Your file {file_name} is ready.
Click the button below to download it now.
This link expires in {{expiry_minutes}} minutes. Do not share it.
|
|
{{footer_text}}
© {{current_year}} {site_name}. All rights reserved.
|
|
';
}
private static function set_defaults() {
$defaults = self::get_default_options();
foreach ( $defaults as $key => $value ) {
add_option( $key, $value );
}
}
private static function create_protected_upload_dir() {
$upload_dir = wp_upload_dir();
$protect_dir = $upload_dir['basedir'] . '/acquis-protected';
if ( ! file_exists( $protect_dir ) ) {
wp_mkdir_p( $protect_dir );
}
// Use WP Filesystem API for all file write operations per WP.org Guideline 7
global $wp_filesystem;
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
WP_Filesystem();
$htaccess = $protect_dir . '/.htaccess';
if ( ! file_exists( $htaccess ) ) {
$wp_filesystem->put_contents( $htaccess, "Options -Indexes
Deny from all
", FS_CHMOD_FILE );
}
$index = $protect_dir . '/index.php';
if ( ! file_exists( $index ) ) {
$wp_filesystem->put_contents( $index, '