id); $sub = self::get_submission_for_lead($lead->id); $values = $sub ? self::get_flat_values($sub->id, $fields) : array_fill(0, count($fields), ''); $row = array( $lead->id, $lead->full_name, $lead->email, $lead->file_name ?? '', $sub ? ($sub->form_id ?? '') : '', $score ? $score->total_score : 0, class_exists( 'ACQUIS_Automation_Engine' ) ? implode(', ', ACQUIS_Automation_Engine::get_lead_tags($lead->id)) : '', class_exists( 'ACQUIS_Automation_Engine' ) ? ACQUIS_Automation_Engine::get_lead_segment($lead->id) : '', $lead->utm_source ?? '', $lead->utm_medium ?? '', $lead->utm_campaign ?? '', $lead->source_url ?? '', $lead->ip_address, $lead->created_at, ); foreach ($values as $v) $row[] = $v; fputcsv($fp, $row); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- pairs with the php://output stream opened above fclose($fp); exit; } /** * Generate an XLSX and stream it. * Uses PHP's ZipArchive to build a minimal valid XLSX without dependencies. */ public static function export_xlsx( $filters = array() ) { $leads = ACQUIS_P3_DB::get_leads_for_export($filters); $fields = self::get_all_custom_field_keys(); $cols = array( 'ID','Full Name','Email','File','Form','Lead Score', 'Tags','Segment','UTM Source','UTM Medium','UTM Campaign', 'Source URL','IP Address','Date Submitted', ); foreach ($fields as $label) $cols[] = $label; $rows = array($cols); foreach ($leads as $lead) { $score = ACQUIS_P3_DB::get_lead_score($lead->id); $sub = self::get_submission_for_lead($lead->id); $values = $sub ? self::get_flat_values($sub->id, $fields) : array_fill(0, count($fields), ''); $row = array( $lead->id, $lead->full_name, $lead->email, $lead->file_name ?? '', $sub ? $sub->form_id : '', $score ? $score->total_score : 0, class_exists( 'ACQUIS_Automation_Engine' ) ? implode(', ', ACQUIS_Automation_Engine::get_lead_tags($lead->id)) : '', class_exists( 'ACQUIS_Automation_Engine' ) ? ACQUIS_Automation_Engine::get_lead_segment($lead->id) : '', $lead->utm_source ?? '', $lead->utm_medium ?? '', $lead->utm_campaign ?? '', $lead->source_url ?? '', $lead->ip_address, $lead->created_at, ); foreach ($values as $v) $row[] = $v; $rows[] = $row; } $xlsx_data = self::build_xlsx($rows); $filename = 'acquisitionsuite-export-' . gmdate('Y-m-d') . '.xlsx'; header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . strlen($xlsx_data)); header('Pragma: no-cache'); header('Expires: 0'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary XLSX file download, not HTML output echo $xlsx_data; exit; } // ── Helpers ──────────────────────────────────────────────────────────── private static function get_all_custom_field_keys() { global $wpdb; $keys = $wpdb->get_results( "SELECT DISTINCT sv.field_key, sv.field_label FROM " . ACQUIS_P3_DB::sub_values_table() . " sv ORDER BY sv.field_key ASC" ); $map = array(); foreach ($keys as $k) $map[$k->field_key] = $k->field_label ?: $k->field_key; return $map; } private static function get_submission_for_lead($lead_id) { global $wpdb; return $wpdb->get_row($wpdb->prepare( "SELECT * FROM %i WHERE lead_id=%d ORDER BY id DESC LIMIT 1", ACQUIS_P3_DB::submissions_table(), absint($lead_id) )); } private static function get_flat_values($submission_id, $field_map) { global $wpdb; $rows = $wpdb->get_results($wpdb->prepare( "SELECT field_key, value FROM %i WHERE submission_id=%d", ACQUIS_P3_DB::sub_values_table(), absint($submission_id) )); $flat = array(); foreach ($rows as $r) $flat[$r->field_key] = $r->value; $result = array(); foreach ($field_map as $key => $label) { $result[] = $flat[$key] ?? ''; } return $result; } /** * Build minimal XLSX binary using ZipArchive. * No external library needed pure PHP Open XML. */ private static function build_xlsx( array $rows ) { // Build sheet XML $sheet_xml = '' . '' . ''; $row_num = 1; foreach ($rows as $row) { $sheet_xml .= ''; $col_num = 0; foreach ($row as $cell) { $col_letter = self::col_letter($col_num++); $cell_ref = $col_letter . $row_num; $val = htmlspecialchars((string)$cell, ENT_XML1, 'UTF-8'); if (is_numeric($cell) && $cell !== '') { $sheet_xml .= '' . $val . ''; } else { $sheet_xml .= '' . $val . ''; } } $sheet_xml .= ''; $row_num++; } $sheet_xml .= ''; $wb_xml = '' . '' . ''; $wb_rels = '' . '' . '' . ''; $pkg_rels = '' . '' . '' . ''; $ct = '' . '' . '' . '' . '' . '' . ''; // Build zip in memory $tmp = tempnam(sys_get_temp_dir(), 'acquis_xlsx_'); $zip = new ZipArchive(); $zip->open($tmp, ZipArchive::OVERWRITE); $zip->addFromString('[Content_Types].xml', $ct); $zip->addFromString('_rels/.rels', $pkg_rels); $zip->addFromString('xl/workbook.xml', $wb_xml); $zip->addFromString('xl/_rels/workbook.xml.rels', $wb_rels); $zip->addFromString('xl/worksheets/sheet1.xml', $sheet_xml); $zip->close(); $data = file_get_contents($tmp); wp_delete_file( $tmp ); return $data; } private static function col_letter($n) { $letter = ''; while ($n >= 0) { $letter = chr(65 + ($n % 26)) . $letter; $n = intdiv($n, 26) - 1; } return $letter; } }