| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Better_Payment\Lite\Classes; |
| 4 | 4 | |
| 5 | +use Better_Payment\Lite\Classes\Helper as ClassesHelper; | |
| 5 | 6 | use Better_Payment\Lite\Controller; |
| 6 | 7 | use Better_Payment\Lite\Models\TransactionModel; |
| 7 | 8 | use Better_Payment\Lite\Traits\Helper; |
| 8 | 9 | |
| @@ -20,9 +21,19 @@ | ||
| 20 | 21 | */ |
| 21 | 22 | class Export extends Controller{ |
| 22 | 23 | |
| 23 | 24 | use Helper; |
| 25 | + | |
| 24 | 26 | /** |
| 27 | + * CSV escape character, passed to fputcsv() explicitly on every call. | |
| 28 | + * | |
| 29 | + * PHP 8.4 deprecates relying on fputcsv()'s default $escape, because that | |
| 30 | + * default will change in a later release. Passing the current default (a | |
| 31 | + * backslash) keeps the output byte-identical on PHP 7.4 through 8.5. | |
| 32 | + */ | |
| 33 | + const CSV_ESCAPE = "\\"; | |
| 34 | + | |
| 35 | + /** | |
| 25 | 36 | * Class constructor |
| 26 | 37 | * |
| 27 | 38 | * @since 0.0.4 |
| 28 | 39 | */ |
| @@ -32,9 +43,9 @@ | ||
| 32 | 43 | |
| 33 | 44 | public function export_transactions() { |
| 34 | 45 | $response = []; |
| 35 | 46 | |
| 36 | - if (!wp_verify_nonce($_REQUEST['nonce'], 'better_payment_admin_nonce')) { | |
| 47 | + if (!wp_verify_nonce($_REQUEST['nonce'], 'wp_rest')) { | |
| 37 | 48 | $response['message'] = __("Access Denied!", 'better-payment'); |
| 38 | 49 | wp_send_json_error($response); |
| 39 | 50 | } |
| 40 | 51 | |
| @@ -43,22 +54,73 @@ | ||
| 43 | 54 | wp_send_json_error($response); |
| 44 | 55 | } |
| 45 | 56 | |
| 46 | 57 | $transaction_model = new TransactionModel(); |
| 47 | - $all_transactions = $transaction_model->get_transactions(); | |
| 58 | + | |
| 59 | + // Check if this is a filtered export | |
| 60 | + $is_filtered = isset($_REQUEST['filtered']) && $_REQUEST['filtered'] === '1'; | |
| 61 | + | |
| 62 | + if ($is_filtered) { | |
| 63 | + // Build filter arguments | |
| 64 | + $filter_args = []; | |
| 65 | + | |
| 66 | + if (!empty($_REQUEST['search_text'])) { | |
| 67 | + $filter_args['search_text'] = sanitize_text_field($_REQUEST['search_text']); | |
| 68 | + } | |
| 69 | + | |
| 70 | + if (!empty($_REQUEST['payment_date_from'])) { | |
| 71 | + $filter_args['payment_date_from'] = sanitize_text_field($_REQUEST['payment_date_from']); | |
| 72 | + } | |
| 73 | + | |
| 74 | + if (!empty($_REQUEST['payment_date_to'])) { | |
| 75 | + $filter_args['payment_date_to'] = sanitize_text_field($_REQUEST['payment_date_to']); | |
| 76 | + } | |
| 77 | + | |
| 78 | + if (!empty($_REQUEST['source'])) { | |
| 79 | + $filter_args['source'] = sanitize_text_field($_REQUEST['source']); | |
| 80 | + } | |
| 81 | + | |
| 82 | + if (!empty($_REQUEST['currency'])) { | |
| 83 | + $filter_args['currency'] = sanitize_text_field($_REQUEST['currency']); | |
| 84 | + } | |
| 85 | + | |
| 86 | + if (!empty($_REQUEST['status'])) { | |
| 87 | + $filter_args['status'] = sanitize_text_field($_REQUEST['status']); | |
| 88 | + } | |
| 89 | + | |
| 90 | + $filter_args['per_page'] = '999999999'; | |
| 91 | + // The admin list's v2 tab buckets, so an export of a tab holds exactly the rows that tab | |
| 92 | + // lists. The v1 path matched `completed` against the raw status `completed` only. | |
| 93 | + $all_transactions = $transaction_model->get_transactions($filter_args, 'v2'); | |
| 94 | + $filename = 'better-payment-transactions-filtered-' . date('Y-m-d') . '.csv'; | |
| 95 | + } else { | |
| 96 | + $all_transactions = $transaction_model->get_transactions(); | |
| 97 | + $filename = 'better-payment-transactions-' . date('Y-m-d') . '.csv'; | |
| 98 | + } | |
| 48 | 99 | |
| 49 | - $filename = 'better-payment-transactions-' . date('Y-m-d') . '.csv'; | |
| 50 | 100 | return $this->transactions_array_to_csv_download($all_transactions, $filename); |
| 51 | 101 | } |
| 52 | 102 | |
| 103 | + /** | |
| 104 | + * Write one CSV row. All CSV writes go through here so none can rely on the | |
| 105 | + * deprecated default $escape. | |
| 106 | + * | |
| 107 | + * @param resource $handle Open, writable stream. | |
| 108 | + * @param array $fields Row values. | |
| 109 | + * @param string $delimiter Field delimiter. | |
| 110 | + * @return int|false Bytes written, or false on failure. | |
| 111 | + */ | |
| 112 | + public static function put_csv_row($handle, array $fields, $delimiter = ";") { | |
| 113 | + return fputcsv($handle, $fields, $delimiter, "\"", self::CSV_ESCAPE); | |
| 114 | + } | |
| 115 | + | |
| 53 | 116 | public function transactions_array_to_csv_download($array, $filename = "export.csv", $delimiter=";") { |
| 54 | - $bp_helper_obj = new Better_Payment_Helper(); | |
| 55 | 117 | $wp_date_format = get_option('date_format'); |
| 56 | 118 | $wp_time_format = get_option('time_format'); |
| 57 | 119 | |
| 58 | 120 | $f = fopen('php://memory', 'w'); |
| 59 | 121 | |
| 60 | - fputcsv($f, $this->export_transactions_heading(), $delimiter); | |
| 122 | + self::put_csv_row($f, $this->export_transactions_heading(), $delimiter); | |
| 61 | 123 | |
| 62 | 124 | foreach ($array as $line) { |
| 63 | 125 | $line = (array)$line; |
| 64 | 126 | unset($line['id']); |
| @@ -69,28 +131,42 @@ | ||
| 69 | 131 | // unset($line['form_fields_info']); |
| 70 | 132 | unset($line['referer']); |
| 71 | 133 | |
| 72 | 134 | |
| 73 | - $line['status'] = esc_html(ucfirst($bp_helper_obj->get_type_by_transaction_status($line['status'], 'v2'))); | |
| 135 | + // From the stored row (form_fields_info still serialized): an unconfirmed pre-2.4.0 free | |
| 136 | + // checkout exports as Incomplete (Helper::transaction_status_type()). | |
| 137 | + $line['status'] = esc_html( ClassesHelper::transaction_status_label( $line ) ); | |
| 74 | 138 | $line['source'] = esc_html( ucfirst( $line['source'] ) ); |
| 75 | 139 | $line['payment_date'] = wp_date($wp_date_format.' '.$wp_time_format, strtotime($line['payment_date']));; |
| 76 | 140 | |
| 141 | + // 'One Time' unless Pro labels the row (better_payment/transaction/payment_type). | |
| 142 | + $line['payment_type'] = ClassesHelper::payment_type_label( maybe_unserialize( $line['form_fields_info'] ) ); | |
| 143 | + | |
| 77 | 144 | $line['form_fields_info'] = $this->form_fields_info_formatted($line['form_fields_info']); |
| 78 | - | |
| 145 | + | |
| 79 | 146 | $line['form_fields_info_name'] = isset($line['form_fields_info']['customer_name']) ? $line['form_fields_info']['customer_name'] : ''; |
| 80 | 147 | $line['form_fields_info_email'] = isset($line['form_fields_info']['customer_email']) ? $line['form_fields_info']['customer_email'] : ''; |
| 81 | 148 | |
| 149 | + unset($line['form_fields_info']['customer_name']); | |
| 150 | + unset($line['form_fields_info']['customer_email']); | |
| 151 | + | |
| 152 | + $line['form_fields_info'] = array_filter($line['form_fields_info'], function($value) { | |
| 153 | + return !empty($value); | |
| 154 | + }); | |
| 155 | + | |
| 82 | 156 | $line_formatted = array( |
| 83 | 157 | $line['form_fields_info_name'], |
| 84 | 158 | $line['form_fields_info_email'], |
| 85 | 159 | $line['currency'] . ' ' . $line['amount'], |
| 160 | + $line['payment_type'], | |
| 86 | 161 | $line['transaction_id'], |
| 87 | 162 | $line['source'], |
| 88 | 163 | $line['status'], |
| 89 | 164 | $line['payment_date'], |
| 165 | + ! empty( $line['form_fields_info'] ) ? maybe_serialize( $line['form_fields_info'] ) : '', | |
| 90 | 166 | ); |
| 91 | 167 | |
| 92 | - fputcsv($f, $line_formatted, $delimiter); | |
| 168 | + self::put_csv_row($f, $line_formatted, $delimiter); | |
| 93 | 169 | } |
| 94 | 170 | |
| 95 | 171 | // reset the file pointer to the start of the file |
| 96 | 172 | fseek($f, 0); |
| @@ -105,15 +181,17 @@ | ||
| 105 | 181 | } |
| 106 | 182 | |
| 107 | 183 | public function export_transactions_heading(){ |
| 108 | 184 | return array( |
| 109 | - 'Name', | |
| 110 | - 'Email Address', | |
| 111 | - 'Amount', | |
| 112 | - 'Transaction ID', | |
| 113 | - 'Source', | |
| 114 | - 'Status', | |
| 115 | - 'Date', | |
| 185 | + esc_html('name'), | |
| 186 | + esc_html('email'), | |
| 187 | + esc_html('amount'), | |
| 188 | + esc_html('payment_type'), | |
| 189 | + esc_html('transaction_id'), | |
| 190 | + esc_html('source'), | |
| 191 | + esc_html('status'), | |
| 192 | + esc_html('payment_date'), | |
| 193 | + esc_html('form_fields_info'), | |
| 116 | 194 | // 'Order ID', |
| 117 | 195 | // 'Customer Info', |
| 118 | 196 | // 'Form Fields Info', |
| 119 | 197 | // 'Currency', |
| @@ -120,8 +198,18 @@ | ||
| 120 | 198 | // 'Referer' |
| 121 | 199 | ); |
| 122 | 200 | } |
| 123 | 201 | |
| 202 | + /** | |
| 203 | + * The fields a transaction contributes to the CSV: customer name and email, plus | |
| 204 | + * whatever `better_payment/export/form_fields_info` adds. | |
| 205 | + * | |
| 206 | + * Pro adds the subscription keys its recurring and split checkouts store; a free | |
| 207 | + * install exports name and email. | |
| 208 | + * | |
| 209 | + * @param mixed $form_fields_info Serialized or unserialized form_fields_info. | |
| 210 | + * @return array | |
| 211 | + */ | |
| 124 | 212 | public function form_fields_info_formatted($form_fields_info){ |
| 125 | 213 | $bp_form_fields_info = maybe_unserialize($form_fields_info); |
| 126 | 214 | |
| 127 | 215 | $bp_transaction_customer_name = isset($bp_form_fields_info['primary_first_name']) ? sanitize_text_field($bp_form_fields_info['primary_first_name']) : ''; |
| @@ -140,11 +228,17 @@ | ||
| 140 | 228 | if( empty($bp_transaction_customer_email) ){ |
| 141 | 229 | $bp_transaction_customer_email = isset($bp_form_fields_info['email']) ? sanitize_text_field($bp_form_fields_info['email']) : ''; |
| 142 | 230 | } |
| 143 | 231 | |
| 144 | - return [ | |
| 145 | - 'customer_name' => $bp_transaction_customer_name, | |
| 146 | - 'customer_email' => $bp_transaction_customer_email | |
| 232 | + $formatted = [ | |
| 233 | + 'customer_name' => $bp_transaction_customer_name, | |
| 234 | + 'customer_email' => $bp_transaction_customer_email, | |
| 147 | 235 | ]; |
| 236 | + | |
| 237 | + $raw_fields = is_array( $bp_form_fields_info ) ? $bp_form_fields_info : []; | |
| 238 | + | |
| 239 | + $filtered = apply_filters( 'better_payment/export/form_fields_info', $formatted, $raw_fields ); | |
| 240 | + | |
| 241 | + return is_array( $filtered ) ? $filtered : $formatted; | |
| 148 | 242 | } |
| 149 | 243 | |
| 150 | 244 | } |