| @@ -21,9 +21,19 @@ | ||
| 21 | 21 | */ |
| 22 | 22 | class Export extends Controller{ |
| 23 | 23 | |
| 24 | 24 | use Helper; |
| 25 | + | |
| 25 | 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 | + /** | |
| 26 | 36 | * Class constructor |
| 27 | 37 | * |
| 28 | 38 | * @since 0.0.4 |
| 29 | 39 | */ |
| @@ -77,9 +87,11 @@ | ||
| 77 | 87 | $filter_args['status'] = sanitize_text_field($_REQUEST['status']); |
| 78 | 88 | } |
| 79 | 89 | |
| 80 | 90 | $filter_args['per_page'] = '999999999'; |
| 81 | - $all_transactions = $transaction_model->get_transactions($filter_args); | |
| 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'); | |
| 82 | 94 | $filename = 'better-payment-transactions-filtered-' . date('Y-m-d') . '.csv'; |
| 83 | 95 | } else { |
| 84 | 96 | $all_transactions = $transaction_model->get_transactions(); |
| 85 | 97 | $filename = 'better-payment-transactions-' . date('Y-m-d') . '.csv'; |
| @@ -87,16 +99,28 @@ | ||
| 87 | 99 | |
| 88 | 100 | return $this->transactions_array_to_csv_download($all_transactions, $filename); |
| 89 | 101 | } |
| 90 | 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 | + | |
| 91 | 116 | public function transactions_array_to_csv_download($array, $filename = "export.csv", $delimiter=";") { |
| 92 | - $bp_helper_obj = new ClassesHelper(); | |
| 93 | 117 | $wp_date_format = get_option('date_format'); |
| 94 | 118 | $wp_time_format = get_option('time_format'); |
| 95 | 119 | |
| 96 | 120 | $f = fopen('php://memory', 'w'); |
| 97 | 121 | |
| 98 | - fputcsv($f, $this->export_transactions_heading(), $delimiter); | |
| 122 | + self::put_csv_row($f, $this->export_transactions_heading(), $delimiter); | |
| 99 | 123 | |
| 100 | 124 | foreach ($array as $line) { |
| 101 | 125 | $line = (array)$line; |
| 102 | 126 | unset($line['id']); |
| @@ -107,17 +131,21 @@ | ||
| 107 | 131 | // unset($line['form_fields_info']); |
| 108 | 132 | unset($line['referer']); |
| 109 | 133 | |
| 110 | 134 | |
| 111 | - $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 ) ); | |
| 112 | 138 | $line['source'] = esc_html( ucfirst( $line['source'] ) ); |
| 113 | 139 | $line['payment_date'] = wp_date($wp_date_format.' '.$wp_time_format, strtotime($line['payment_date']));; |
| 114 | 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 | + | |
| 115 | 144 | $line['form_fields_info'] = $this->form_fields_info_formatted($line['form_fields_info']); |
| 116 | - | |
| 145 | + | |
| 117 | 146 | $line['form_fields_info_name'] = isset($line['form_fields_info']['customer_name']) ? $line['form_fields_info']['customer_name'] : ''; |
| 118 | 147 | $line['form_fields_info_email'] = isset($line['form_fields_info']['customer_email']) ? $line['form_fields_info']['customer_email'] : ''; |
| 119 | - $line['payment_type'] = ! empty( $line['form_fields_info']['subscription_id'] ) ? 'Subscription' : 'One Time'; | |
| 120 | 148 | |
| 121 | 149 | unset($line['form_fields_info']['customer_name']); |
| 122 | 150 | unset($line['form_fields_info']['customer_email']); |
| 123 | 151 | |
| @@ -136,9 +164,9 @@ | ||
| 136 | 164 | $line['payment_date'], |
| 137 | 165 | ! empty( $line['form_fields_info'] ) ? maybe_serialize( $line['form_fields_info'] ) : '', |
| 138 | 166 | ); |
| 139 | 167 | |
| 140 | - fputcsv($f, $line_formatted, $delimiter); | |
| 168 | + self::put_csv_row($f, $line_formatted, $delimiter); | |
| 141 | 169 | } |
| 142 | 170 | |
| 143 | 171 | // reset the file pointer to the start of the file |
| 144 | 172 | fseek($f, 0); |
| @@ -170,22 +198,21 @@ | ||
| 170 | 198 | // 'Referer' |
| 171 | 199 | ); |
| 172 | 200 | } |
| 173 | 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 | + */ | |
| 174 | 212 | public function form_fields_info_formatted($form_fields_info){ |
| 175 | 213 | $bp_form_fields_info = maybe_unserialize($form_fields_info); |
| 176 | 214 | |
| 177 | - $bp_transaction_subscription_id = isset($bp_form_fields_info['subscription_id']) ? sanitize_text_field($bp_form_fields_info['subscription_id']) : ''; | |
| 178 | - $bp_transaction_subscription_customer_id = isset($bp_form_fields_info['subscription_customer_id']) ? sanitize_text_field($bp_form_fields_info['subscription_customer_id']) : ''; | |
| 179 | - $bp_transaction_subscription_plan_id = isset($bp_form_fields_info['subscription_plan_id']) ? sanitize_text_field($bp_form_fields_info['subscription_plan_id']) : ''; | |
| 180 | - | |
| 181 | - $subscription_interval = ! empty($bp_form_fields_info['subscription_interval']) ? sanitize_text_field( $bp_form_fields_info['subscription_interval'] ) : ''; | |
| 182 | - $subscription_current_period_start = ! empty($bp_form_fields_info['subscription_current_period_start']) ? sanitize_text_field( $bp_form_fields_info['subscription_current_period_start'] ) : ''; | |
| 183 | - $subscription_current_period_end = ! empty($bp_form_fields_info['subscription_current_period_end']) ? sanitize_text_field( $bp_form_fields_info['subscription_current_period_end'] ) : ''; | |
| 184 | - $subscription_status = ! empty($bp_form_fields_info['subscription_status']) ? sanitize_text_field( $bp_form_fields_info['subscription_status'] ) : ''; | |
| 185 | - $subscription_created_date = ! empty($bp_form_fields_info['subscription_created_date']) ? sanitize_text_field( $bp_form_fields_info['subscription_created_date'] ) : ''; | |
| 186 | - $is_payment_split_payment = ! empty($bp_form_fields_info['is_payment_split_payment']) ? intval( $bp_form_fields_info['is_payment_split_payment'] ) : 0; | |
| 187 | - | |
| 188 | 215 | $bp_transaction_customer_name = isset($bp_form_fields_info['primary_first_name']) ? sanitize_text_field($bp_form_fields_info['primary_first_name']) : ''; |
| 189 | 216 | $bp_transaction_customer_name .= ' '; |
| 190 | 217 | $bp_transaction_customer_name .= isset($bp_form_fields_info['primary_last_name']) ? sanitize_text_field($bp_form_fields_info['primary_last_name']) : ''; |
| 191 | 218 | |
| @@ -201,20 +228,17 @@ | ||
| 201 | 228 | if( empty($bp_transaction_customer_email) ){ |
| 202 | 229 | $bp_transaction_customer_email = isset($bp_form_fields_info['email']) ? sanitize_text_field($bp_form_fields_info['email']) : ''; |
| 203 | 230 | } |
| 204 | 231 | |
| 205 | - return [ | |
| 232 | + $formatted = [ | |
| 206 | 233 | 'customer_name' => $bp_transaction_customer_name, |
| 207 | 234 | 'customer_email' => $bp_transaction_customer_email, |
| 208 | - 'subscription_id' => $bp_transaction_subscription_id, | |
| 209 | - 'subscription_customer_id' => $bp_transaction_subscription_customer_id, | |
| 210 | - 'subscription_plan_id' => $bp_transaction_subscription_plan_id, | |
| 211 | - 'subscription_interval' => $subscription_interval, | |
| 212 | - 'subscription_current_period_start' => $subscription_current_period_start, | |
| 213 | - 'subscription_current_period_end' => $subscription_current_period_end, | |
| 214 | - 'subscription_status' => $subscription_status, | |
| 215 | - 'subscription_created_date' => $subscription_created_date, | |
| 216 | - 'is_payment_split_payment' => $is_payment_split_payment, | |
| 217 | 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; | |
| 218 | 242 | } |
| 219 | 243 | |
| 220 | 244 | } |