← All changes
|
addons/automator/includes/actions/csv-creator-actions.php
+252
-252
8.8.0
→
8.8.1
View file →
| @@ -1,252 +1,252 @@ | ||
| 1 | -<?php | |
| 2 | -/** | |
| 3 | - * CSV Creator Actions | |
| 4 | - * | |
| 5 | - * @package WPbot_Automator | |
| 6 | - */ | |
| 7 | - | |
| 8 | -namespace WPbot_Automator\Actions; | |
| 9 | - | |
| 10 | -use WPbot_Automator\Core\Tables; | |
| 11 | - | |
| 12 | -if ( ! defined( 'ABSPATH' ) ) { | |
| 13 | - exit; | |
| 14 | -} | |
| 15 | - | |
| 16 | -/** | |
| 17 | - * Class CSV_Creator_Actions | |
| 18 | - * | |
| 19 | - * Provides three actions that build CSV files and save them to the WordPress | |
| 20 | - * media library. Each action returns an array merged into $trigger_data so | |
| 21 | - * downstream nodes can reference: | |
| 22 | - * | |
| 23 | - * {csv_url} — public URL of the saved CSV file | |
| 24 | - * {csv_attachment_id} — WordPress attachment post ID | |
| 25 | - * {csv_filename} — filename of the saved CSV file | |
| 26 | - */ | |
| 27 | -class CSV_Creator_Actions extends Action { | |
| 28 | - | |
| 29 | - public function __construct() { | |
| 30 | - $this->id = 'csv_creator'; | |
| 31 | - $this->group = __( 'CSV Creator', 'wpbot-automator' ); | |
| 32 | - } | |
| 33 | - | |
| 34 | - public function execute( $action_data, $trigger_data ) { | |
| 35 | - $action_id = isset( $action_data['actionId'] ) ? $action_data['actionId'] : ''; | |
| 36 | - | |
| 37 | - switch ( $action_id ) { | |
| 38 | - case 'csv_from_table': return $this->csv_from_table( $action_data, $trigger_data ); | |
| 39 | - case 'csv_from_json': return $this->csv_from_json( $action_data, $trigger_data ); | |
| 40 | - case 'csv_from_array': return $this->csv_from_array( $action_data, $trigger_data ); | |
| 41 | - default: return false; | |
| 42 | - } | |
| 43 | - } | |
| 44 | - | |
| 45 | - // ── Sub-actions ────────────────────────────────────────────────────────── | |
| 46 | - | |
| 47 | - /** | |
| 48 | - * Export all (or filtered) records from a wpbot Table to CSV. | |
| 49 | - * | |
| 50 | - * Config fields: table_id, filter_col (opt), filter_val (opt), filename (opt). | |
| 51 | - */ | |
| 52 | - private function csv_from_table( $action_data, $trigger_data ) { | |
| 53 | - global $wpdb; | |
| 54 | - | |
| 55 | - $config = isset( $action_data['config'] ) ? $action_data['config'] : array(); | |
| 56 | - $table_id = absint( $this->parse_tokens( isset( $config['table_id'] ) ? $config['table_id'] : '', $trigger_data ) ); | |
| 57 | - | |
| 58 | - if ( ! $table_id ) { | |
| 59 | - return array( 'success' => false, 'message' => 'CSV Creator: table_id is required.' ); | |
| 60 | - } | |
| 61 | - | |
| 62 | - $table = Tables::get_table_meta_by_id( $table_id ); | |
| 63 | - if ( ! $table ) { | |
| 64 | - return array( 'success' => false, 'message' => 'CSV Creator: table not found (' . $table_id . ').' ); | |
| 65 | - } | |
| 66 | - | |
| 67 | - $ut = Tables::get_user_table( $table['slug'] ); | |
| 68 | - $filter_col = sanitize_key( $this->parse_tokens( isset( $config['filter_col'] ) ? $config['filter_col'] : '', $trigger_data ) ); | |
| 69 | - $filter_val = sanitize_text_field( $this->parse_tokens( isset( $config['filter_val'] ) ? $config['filter_val'] : '', $trigger_data ) ); | |
| 70 | - | |
| 71 | - $where = ''; | |
| 72 | - $query_params = array(); | |
| 73 | - $valid_slugs = wp_list_pluck( $table['columns'], 'slug' ); | |
| 74 | - | |
| 75 | - if ( $filter_col && '' !== $filter_val && in_array( $filter_col, $valid_slugs, true ) ) { | |
| 76 | - $where = 'WHERE `' . esc_sql( $filter_col ) . '` LIKE %s'; | |
| 77 | - $query_params[] = '%' . $wpdb->esc_like( $filter_val ) . '%'; | |
| 78 | - } | |
| 79 | - | |
| 80 | - $sql = "SELECT * FROM `{$ut}` {$where} ORDER BY id ASC"; | |
| 81 | - $records = $query_params | |
| 82 | - ? $wpdb->get_results( $wpdb->prepare( $sql, ...$query_params ), ARRAY_A ) | |
| 83 | - : $wpdb->get_results( $sql, ARRAY_A ); | |
| 84 | - | |
| 85 | - // Build header row from column definitions. | |
| 86 | - $headers = array( 'id' ); | |
| 87 | - foreach ( $table['columns'] as $col ) { | |
| 88 | - $headers[] = $col['name']; | |
| 89 | - } | |
| 90 | - $headers[] = 'created_at'; | |
| 91 | - | |
| 92 | - // Build CSV rows. | |
| 93 | - $csv_rows = array( $headers ); | |
| 94 | - foreach ( $records ?: array() as $rec ) { | |
| 95 | - $row = array( $rec['id'] ?? '' ); | |
| 96 | - foreach ( $table['columns'] as $col ) { | |
| 97 | - $row[] = $rec[ $col['slug'] ] ?? ''; | |
| 98 | - } | |
| 99 | - $row[] = $rec['created_at'] ?? ''; | |
| 100 | - $csv_rows[] = $row; | |
| 101 | - } | |
| 102 | - | |
| 103 | - $raw_filename = isset( $config['filename'] ) ? $this->parse_tokens( $config['filename'], $trigger_data ) : ''; | |
| 104 | - $filename = sanitize_file_name( $raw_filename ) ?: ( sanitize_title( $table['name'] ) . '_' . gmdate( 'Y-m-d' ) . '.csv' ); | |
| 105 | - | |
| 106 | - return $this->save_csv( $csv_rows, $filename ); | |
| 107 | - } | |
| 108 | - | |
| 109 | - /** | |
| 110 | - * Convert a JSON array-of-objects string to CSV. | |
| 111 | - * | |
| 112 | - * Config fields: json_data (supports tokens like {response}), filename (opt). | |
| 113 | - * | |
| 114 | - * Expected input: '[{"col1":"val","col2":"val"}, ...]' | |
| 115 | - */ | |
| 116 | - private function csv_from_json( $action_data, $trigger_data ) { | |
| 117 | - $config = isset( $action_data['config'] ) ? $action_data['config'] : array(); | |
| 118 | - $json_raw = $this->parse_tokens( isset( $config['json_data'] ) ? $config['json_data'] : '', $trigger_data ); | |
| 119 | - | |
| 120 | - // Strip markdown code-fences in case the data came from an AI action. | |
| 121 | - $json_raw = preg_replace( '/^```(?:json)?\s*/i', '', trim( $json_raw ) ); | |
| 122 | - $json_raw = preg_replace( '/\s*```$/', '', $json_raw ); | |
| 123 | - | |
| 124 | - $data = json_decode( $json_raw, true ); | |
| 125 | - | |
| 126 | - if ( ! is_array( $data ) || empty( $data ) ) { | |
| 127 | - return array( 'success' => false, 'message' => 'CSV Creator: json_data is not a valid JSON array.' ); | |
| 128 | - } | |
| 129 | - | |
| 130 | - // Flatten any nested objects one level deep. | |
| 131 | - $first_item = reset( $data ); | |
| 132 | - if ( ! is_array( $first_item ) ) { | |
| 133 | - // Scalar array — wrap each value. | |
| 134 | - $data = array_map( function( $v ) { return array( 'value' => $v ); }, $data ); | |
| 135 | - $first_item = reset( $data ); | |
| 136 | - } | |
| 137 | - | |
| 138 | - $headers = array_keys( $first_item ); | |
| 139 | - $csv_rows = array( $headers ); | |
| 140 | - | |
| 141 | - foreach ( $data as $row ) { | |
| 142 | - $csv_rows[] = array_map( function( $h ) use ( $row ) { | |
| 143 | - $v = isset( $row[ $h ] ) ? $row[ $h ] : ''; | |
| 144 | - return is_array( $v ) ? wp_json_encode( $v ) : (string) $v; | |
| 145 | - }, $headers ); | |
| 146 | - } | |
| 147 | - | |
| 148 | - $raw_filename = isset( $config['filename'] ) ? $this->parse_tokens( $config['filename'], $trigger_data ) : ''; | |
| 149 | - $filename = sanitize_file_name( $raw_filename ) ?: ( 'export_' . gmdate( 'Y-m-d_His' ) . '.csv' ); | |
| 150 | - | |
| 151 | - return $this->save_csv( $csv_rows, $filename ); | |
| 152 | - } | |
| 153 | - | |
| 154 | - /** | |
| 155 | - * Convert a JSON array-of-arrays string to CSV. | |
| 156 | - * | |
| 157 | - * Config fields: array_data, headers (comma-separated, opt), filename (opt). | |
| 158 | - * | |
| 159 | - * Expected input: '[["Alice","[email protected]"],["Bob","[email protected]"]]' | |
| 160 | - */ | |
| 161 | - private function csv_from_array( $action_data, $trigger_data ) { | |
| 162 | - $config = isset( $action_data['config'] ) ? $action_data['config'] : array(); | |
| 163 | - $raw = $this->parse_tokens( isset( $config['array_data'] ) ? $config['array_data'] : '', $trigger_data ); | |
| 164 | - | |
| 165 | - // Strip markdown fences. | |
| 166 | - $raw = preg_replace( '/^```(?:json)?\s*/i', '', trim( $raw ) ); | |
| 167 | - $raw = preg_replace( '/\s*```$/', '', $raw ); | |
| 168 | - | |
| 169 | - $data = json_decode( $raw, true ); | |
| 170 | - | |
| 171 | - if ( ! is_array( $data ) || empty( $data ) ) { | |
| 172 | - return array( 'success' => false, 'message' => 'CSV Creator: array_data is not a valid JSON array.' ); | |
| 173 | - } | |
| 174 | - | |
| 175 | - $csv_rows = array(); | |
| 176 | - | |
| 177 | - // Optional custom headers. | |
| 178 | - $raw_headers = isset( $config['headers'] ) ? $this->parse_tokens( $config['headers'], $trigger_data ) : ''; | |
| 179 | - if ( ! empty( $raw_headers ) ) { | |
| 180 | - $csv_rows[] = array_map( 'trim', explode( ',', $raw_headers ) ); | |
| 181 | - } | |
| 182 | - | |
| 183 | - foreach ( $data as $row ) { | |
| 184 | - if ( is_array( $row ) ) { | |
| 185 | - $csv_rows[] = array_values( $row ); | |
| 186 | - } elseif ( is_scalar( $row ) ) { | |
| 187 | - $csv_rows[] = array( $row ); | |
| 188 | - } | |
| 189 | - } | |
| 190 | - | |
| 191 | - $raw_filename = isset( $config['filename'] ) ? $this->parse_tokens( $config['filename'], $trigger_data ) : ''; | |
| 192 | - $filename = sanitize_file_name( $raw_filename ) ?: ( 'export_' . gmdate( 'Y-m-d_His' ) . '.csv' ); | |
| 193 | - | |
| 194 | - return $this->save_csv( $csv_rows, $filename ); | |
| 195 | - } | |
| 196 | - | |
| 197 | - // ── Shared helper ──────────────────────────────────────────────────────── | |
| 198 | - | |
| 199 | - /** | |
| 200 | - * Build a CSV string from a 2D array and save it to the media library. | |
| 201 | - * | |
| 202 | - * @param array $rows Two-dimensional array; first row is the header. | |
| 203 | - * @param string $filename Target filename including .csv extension. | |
| 204 | - * @return array Action result array with success, csv_url, csv_attachment_id, csv_filename. | |
| 205 | - */ | |
| 206 | - private function save_csv( array $rows, $filename ) { | |
| 207 | - require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 208 | - require_once ABSPATH . 'wp-admin/includes/media.php'; | |
| 209 | - require_once ABSPATH . 'wp-admin/includes/image.php'; | |
| 210 | - | |
| 211 | - // Render CSV content in memory. | |
| 212 | - ob_start(); | |
| 213 | - $fh = fopen( 'php://output', 'w' ); | |
| 214 | - foreach ( $rows as $row ) { | |
| 215 | - fputcsv( $fh, $row ); | |
| 216 | - } | |
| 217 | - fclose( $fh ); | |
| 218 | - $csv_content = ob_get_clean(); | |
| 219 | - | |
| 220 | - // Upload to WordPress uploads directory. | |
| 221 | - $upload = wp_upload_bits( $filename, null, $csv_content ); | |
| 222 | - if ( ! empty( $upload['error'] ) ) { | |
| 223 | - //error_log( 'WPbot CSV_Creator: upload failed — ' . $upload['error'] ); | |
| 224 | - return array( 'success' => false, 'message' => 'CSV upload failed: ' . $upload['error'] ); | |
| 225 | - } | |
| 226 | - | |
| 227 | - // Register as a media library attachment. | |
| 228 | - $attachment_id = wp_insert_attachment( | |
| 229 | - array( | |
| 230 | - 'post_mime_type' => 'text/csv', | |
| 231 | - 'post_title' => sanitize_file_name( $filename ), | |
| 232 | - 'post_content' => '', | |
| 233 | - 'post_status' => 'inherit', | |
| 234 | - ), | |
| 235 | - $upload['file'] | |
| 236 | - ); | |
| 237 | - | |
| 238 | - if ( is_wp_error( $attachment_id ) ) { | |
| 239 | - //error_log( 'WPbot CSV_Creator: attachment insert failed — ' . $attachment_id->get_error_message() ); | |
| 240 | - return array( 'success' => false, 'message' => 'Could not add CSV to media library.' ); | |
| 241 | - } | |
| 242 | - | |
| 243 | - return array( | |
| 244 | - 'success' => true, | |
| 245 | - 'csv_url' => $upload['url'], | |
| 246 | - 'csv_attachment_id' => $attachment_id, | |
| 247 | - 'csv_filename' => $filename, | |
| 248 | - 'csv_path' => $upload['file'], | |
| 249 | - 'csv_row_count' => max( 0, count( $rows ) - 1 ), // exclude header | |
| 250 | - ); | |
| 251 | - } | |
| 252 | -} | |
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * CSV Creator Actions | |
| 4 | + * | |
| 5 | + * @package WPbot_Automator | |
| 6 | + */ | |
| 7 | + | |
| 8 | +namespace WPbot_Automator\Actions; | |
| 9 | + | |
| 10 | +use WPbot_Automator\Core\Tables; | |
| 11 | + | |
| 12 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 13 | + exit; | |
| 14 | +} | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * Class CSV_Creator_Actions | |
| 18 | + * | |
| 19 | + * Provides three actions that build CSV files and save them to the WordPress | |
| 20 | + * media library. Each action returns an array merged into $trigger_data so | |
| 21 | + * downstream nodes can reference: | |
| 22 | + * | |
| 23 | + * {csv_url} — public URL of the saved CSV file | |
| 24 | + * {csv_attachment_id} — WordPress attachment post ID | |
| 25 | + * {csv_filename} — filename of the saved CSV file | |
| 26 | + */ | |
| 27 | +class CSV_Creator_Actions extends Action { | |
| 28 | + | |
| 29 | + public function __construct() { | |
| 30 | + $this->id = 'csv_creator'; | |
| 31 | + $this->group = __( 'CSV Creator', 'wpbot-automator' ); | |
| 32 | + } | |
| 33 | + | |
| 34 | + public function execute( $action_data, $trigger_data ) { | |
| 35 | + $action_id = isset( $action_data['actionId'] ) ? $action_data['actionId'] : ''; | |
| 36 | + | |
| 37 | + switch ( $action_id ) { | |
| 38 | + case 'csv_from_table': return $this->csv_from_table( $action_data, $trigger_data ); | |
| 39 | + case 'csv_from_json': return $this->csv_from_json( $action_data, $trigger_data ); | |
| 40 | + case 'csv_from_array': return $this->csv_from_array( $action_data, $trigger_data ); | |
| 41 | + default: return false; | |
| 42 | + } | |
| 43 | + } | |
| 44 | + | |
| 45 | + // ── Sub-actions ────────────────────────────────────────────────────────── | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Export all (or filtered) records from a wpbot Table to CSV. | |
| 49 | + * | |
| 50 | + * Config fields: table_id, filter_col (opt), filter_val (opt), filename (opt). | |
| 51 | + */ | |
| 52 | + private function csv_from_table( $action_data, $trigger_data ) { | |
| 53 | + global $wpdb; | |
| 54 | + | |
| 55 | + $config = isset( $action_data['config'] ) ? $action_data['config'] : array(); | |
| 56 | + $table_id = absint( $this->parse_tokens( isset( $config['table_id'] ) ? $config['table_id'] : '', $trigger_data ) ); | |
| 57 | + | |
| 58 | + if ( ! $table_id ) { | |
| 59 | + return array( 'success' => false, 'message' => 'CSV Creator: table_id is required.' ); | |
| 60 | + } | |
| 61 | + | |
| 62 | + $table = Tables::get_table_meta_by_id( $table_id ); | |
| 63 | + if ( ! $table ) { | |
| 64 | + return array( 'success' => false, 'message' => 'CSV Creator: table not found (' . $table_id . ').' ); | |
| 65 | + } | |
| 66 | + | |
| 67 | + $ut = Tables::get_user_table( $table['slug'] ); | |
| 68 | + $filter_col = sanitize_key( $this->parse_tokens( isset( $config['filter_col'] ) ? $config['filter_col'] : '', $trigger_data ) ); | |
| 69 | + $filter_val = sanitize_text_field( $this->parse_tokens( isset( $config['filter_val'] ) ? $config['filter_val'] : '', $trigger_data ) ); | |
| 70 | + | |
| 71 | + $where = ''; | |
| 72 | + $query_params = array(); | |
| 73 | + $valid_slugs = wp_list_pluck( $table['columns'], 'slug' ); | |
| 74 | + | |
| 75 | + if ( $filter_col && '' !== $filter_val && in_array( $filter_col, $valid_slugs, true ) ) { | |
| 76 | + $where = 'WHERE `' . esc_sql( $filter_col ) . '` LIKE %s'; | |
| 77 | + $query_params[] = '%' . $wpdb->esc_like( $filter_val ) . '%'; | |
| 78 | + } | |
| 79 | + | |
| 80 | + $sql = "SELECT * FROM `{$ut}` {$where} ORDER BY id ASC"; | |
| 81 | + $records = $query_params | |
| 82 | + ? $wpdb->get_results( $wpdb->prepare( $sql, ...$query_params ), ARRAY_A ) | |
| 83 | + : $wpdb->get_results( $sql, ARRAY_A ); | |
| 84 | + | |
| 85 | + // Build header row from column definitions. | |
| 86 | + $headers = array( 'id' ); | |
| 87 | + foreach ( $table['columns'] as $col ) { | |
| 88 | + $headers[] = $col['name']; | |
| 89 | + } | |
| 90 | + $headers[] = 'created_at'; | |
| 91 | + | |
| 92 | + // Build CSV rows. | |
| 93 | + $csv_rows = array( $headers ); | |
| 94 | + foreach ( $records ?: array() as $rec ) { | |
| 95 | + $row = array( $rec['id'] ?? '' ); | |
| 96 | + foreach ( $table['columns'] as $col ) { | |
| 97 | + $row[] = $rec[ $col['slug'] ] ?? ''; | |
| 98 | + } | |
| 99 | + $row[] = $rec['created_at'] ?? ''; | |
| 100 | + $csv_rows[] = $row; | |
| 101 | + } | |
| 102 | + | |
| 103 | + $raw_filename = isset( $config['filename'] ) ? $this->parse_tokens( $config['filename'], $trigger_data ) : ''; | |
| 104 | + $filename = sanitize_file_name( $raw_filename ) ?: ( sanitize_title( $table['name'] ) . '_' . gmdate( 'Y-m-d' ) . '.csv' ); | |
| 105 | + | |
| 106 | + return $this->save_csv( $csv_rows, $filename ); | |
| 107 | + } | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * Convert a JSON array-of-objects string to CSV. | |
| 111 | + * | |
| 112 | + * Config fields: json_data (supports tokens like {response}), filename (opt). | |
| 113 | + * | |
| 114 | + * Expected input: '[{"col1":"val","col2":"val"}, ...]' | |
| 115 | + */ | |
| 116 | + private function csv_from_json( $action_data, $trigger_data ) { | |
| 117 | + $config = isset( $action_data['config'] ) ? $action_data['config'] : array(); | |
| 118 | + $json_raw = $this->parse_tokens( isset( $config['json_data'] ) ? $config['json_data'] : '', $trigger_data ); | |
| 119 | + | |
| 120 | + // Strip markdown code-fences in case the data came from an AI action. | |
| 121 | + $json_raw = preg_replace( '/^```(?:json)?\s*/i', '', trim( $json_raw ) ); | |
| 122 | + $json_raw = preg_replace( '/\s*```$/', '', $json_raw ); | |
| 123 | + | |
| 124 | + $data = json_decode( $json_raw, true ); | |
| 125 | + | |
| 126 | + if ( ! is_array( $data ) || empty( $data ) ) { | |
| 127 | + return array( 'success' => false, 'message' => 'CSV Creator: json_data is not a valid JSON array.' ); | |
| 128 | + } | |
| 129 | + | |
| 130 | + // Flatten any nested objects one level deep. | |
| 131 | + $first_item = reset( $data ); | |
| 132 | + if ( ! is_array( $first_item ) ) { | |
| 133 | + // Scalar array — wrap each value. | |
| 134 | + $data = array_map( function( $v ) { return array( 'value' => $v ); }, $data ); | |
| 135 | + $first_item = reset( $data ); | |
| 136 | + } | |
| 137 | + | |
| 138 | + $headers = array_keys( $first_item ); | |
| 139 | + $csv_rows = array( $headers ); | |
| 140 | + | |
| 141 | + foreach ( $data as $row ) { | |
| 142 | + $csv_rows[] = array_map( function( $h ) use ( $row ) { | |
| 143 | + $v = isset( $row[ $h ] ) ? $row[ $h ] : ''; | |
| 144 | + return is_array( $v ) ? wp_json_encode( $v ) : (string) $v; | |
| 145 | + }, $headers ); | |
| 146 | + } | |
| 147 | + | |
| 148 | + $raw_filename = isset( $config['filename'] ) ? $this->parse_tokens( $config['filename'], $trigger_data ) : ''; | |
| 149 | + $filename = sanitize_file_name( $raw_filename ) ?: ( 'export_' . gmdate( 'Y-m-d_His' ) . '.csv' ); | |
| 150 | + | |
| 151 | + return $this->save_csv( $csv_rows, $filename ); | |
| 152 | + } | |
| 153 | + | |
| 154 | + /** | |
| 155 | + * Convert a JSON array-of-arrays string to CSV. | |
| 156 | + * | |
| 157 | + * Config fields: array_data, headers (comma-separated, opt), filename (opt). | |
| 158 | + * | |
| 159 | + * Expected input: '[["Alice","[email protected]"],["Bob","[email protected]"]]' | |
| 160 | + */ | |
| 161 | + private function csv_from_array( $action_data, $trigger_data ) { | |
| 162 | + $config = isset( $action_data['config'] ) ? $action_data['config'] : array(); | |
| 163 | + $raw = $this->parse_tokens( isset( $config['array_data'] ) ? $config['array_data'] : '', $trigger_data ); | |
| 164 | + | |
| 165 | + // Strip markdown fences. | |
| 166 | + $raw = preg_replace( '/^```(?:json)?\s*/i', '', trim( $raw ) ); | |
| 167 | + $raw = preg_replace( '/\s*```$/', '', $raw ); | |
| 168 | + | |
| 169 | + $data = json_decode( $raw, true ); | |
| 170 | + | |
| 171 | + if ( ! is_array( $data ) || empty( $data ) ) { | |
| 172 | + return array( 'success' => false, 'message' => 'CSV Creator: array_data is not a valid JSON array.' ); | |
| 173 | + } | |
| 174 | + | |
| 175 | + $csv_rows = array(); | |
| 176 | + | |
| 177 | + // Optional custom headers. | |
| 178 | + $raw_headers = isset( $config['headers'] ) ? $this->parse_tokens( $config['headers'], $trigger_data ) : ''; | |
| 179 | + if ( ! empty( $raw_headers ) ) { | |
| 180 | + $csv_rows[] = array_map( 'trim', explode( ',', $raw_headers ) ); | |
| 181 | + } | |
| 182 | + | |
| 183 | + foreach ( $data as $row ) { | |
| 184 | + if ( is_array( $row ) ) { | |
| 185 | + $csv_rows[] = array_values( $row ); | |
| 186 | + } elseif ( is_scalar( $row ) ) { | |
| 187 | + $csv_rows[] = array( $row ); | |
| 188 | + } | |
| 189 | + } | |
| 190 | + | |
| 191 | + $raw_filename = isset( $config['filename'] ) ? $this->parse_tokens( $config['filename'], $trigger_data ) : ''; | |
| 192 | + $filename = sanitize_file_name( $raw_filename ) ?: ( 'export_' . gmdate( 'Y-m-d_His' ) . '.csv' ); | |
| 193 | + | |
| 194 | + return $this->save_csv( $csv_rows, $filename ); | |
| 195 | + } | |
| 196 | + | |
| 197 | + // ── Shared helper ──────────────────────────────────────────────────────── | |
| 198 | + | |
| 199 | + /** | |
| 200 | + * Build a CSV string from a 2D array and save it to the media library. | |
| 201 | + * | |
| 202 | + * @param array $rows Two-dimensional array; first row is the header. | |
| 203 | + * @param string $filename Target filename including .csv extension. | |
| 204 | + * @return array Action result array with success, csv_url, csv_attachment_id, csv_filename. | |
| 205 | + */ | |
| 206 | + private function save_csv( array $rows, $filename ) { | |
| 207 | + require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 208 | + require_once ABSPATH . 'wp-admin/includes/media.php'; | |
| 209 | + require_once ABSPATH . 'wp-admin/includes/image.php'; | |
| 210 | + | |
| 211 | + // Render CSV content in memory. | |
| 212 | + ob_start(); | |
| 213 | + $fh = fopen( 'php://output', 'w' ); | |
| 214 | + foreach ( $rows as $row ) { | |
| 215 | + fputcsv( $fh, $row ); | |
| 216 | + } | |
| 217 | + fclose( $fh ); | |
| 218 | + $csv_content = ob_get_clean(); | |
| 219 | + | |
| 220 | + // Upload to WordPress uploads directory. | |
| 221 | + $upload = wp_upload_bits( $filename, null, $csv_content ); | |
| 222 | + if ( ! empty( $upload['error'] ) ) { | |
| 223 | + //error_log( 'WPbot CSV_Creator: upload failed — ' . $upload['error'] ); | |
| 224 | + return array( 'success' => false, 'message' => 'CSV upload failed: ' . $upload['error'] ); | |
| 225 | + } | |
| 226 | + | |
| 227 | + // Register as a media library attachment. | |
| 228 | + $attachment_id = wp_insert_attachment( | |
| 229 | + array( | |
| 230 | + 'post_mime_type' => 'text/csv', | |
| 231 | + 'post_title' => sanitize_file_name( $filename ), | |
| 232 | + 'post_content' => '', | |
| 233 | + 'post_status' => 'inherit', | |
| 234 | + ), | |
| 235 | + $upload['file'] | |
| 236 | + ); | |
| 237 | + | |
| 238 | + if ( is_wp_error( $attachment_id ) ) { | |
| 239 | + //error_log( 'WPbot CSV_Creator: attachment insert failed — ' . $attachment_id->get_error_message() ); | |
| 240 | + return array( 'success' => false, 'message' => 'Could not add CSV to media library.' ); | |
| 241 | + } | |
| 242 | + | |
| 243 | + return array( | |
| 244 | + 'success' => true, | |
| 245 | + 'csv_url' => $upload['url'], | |
| 246 | + 'csv_attachment_id' => $attachment_id, | |
| 247 | + 'csv_filename' => $filename, | |
| 248 | + 'csv_path' => $upload['file'], | |
| 249 | + 'csv_row_count' => max( 0, count( $rows ) - 1 ), // exclude header | |
| 250 | + ); | |
| 251 | + } | |
| 252 | +} | |