PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.0
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.0
2.4.1 2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 All 68 releases
better-payment / includes / Classes / Import.php

Import.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.4.0, at includes/Classes/Import.php

227 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Classes;
4
5 use Better_Payment\Lite\Admin\DB;
6 use Better_Payment\Lite\Controller;
7 use Better_Payment\Lite\Models\TransactionModel;
8 use Better_Payment\Lite\Traits\Helper;
9 use Exception;
10 use League\Csv\Reader;
11 use League\Csv\Statement;
12
13 /**
14 * Exit if accessed directly
15 */
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 /**
21 * Export handler class
22 *
23 * @since 0.0.4
24 */
25 class Import extends Controller{
26
27 use Helper;
28 /**
29 * Class constructor
30 *
31 * @since 0.0.4
32 */
33 public function __construct() {
34
35 }
36
37 /**
38 * Decode a CSV `form_fields_info` cell into an array.
39 *
40 * The cell comes from an uploaded file, so it is never handed to maybe_unserialize():
41 * that instantiates any class a serialized payload names, which turns an import into an
42 * object-injection sink for whatever gadget chain the site's plugins happen to provide.
43 * Objects are refused outright; only a plain array survives.
44 *
45 * @since 2.3.4
46 *
47 * @param mixed $value Cell value.
48 * @return array
49 */
50 public static function decode_form_fields_info( $value ) {
51 if ( ! is_string( $value ) || ! is_serialized( $value ) ) {
52 return [];
53 }
54
55 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize, WordPress.PHP.NoSilencedErrors.Discouraged -- classes are disallowed; a malformed cell is expected input.
56 $decoded = @unserialize( trim( $value ), [ 'allowed_classes' => false ] );
57
58 return is_array( $decoded ) ? $decoded : [];
59 }
60
61 public function import_transactions() {
62 $message = __('Invalid File!', 'better-payment');
63
64 $invalid_nonce = ! wp_verify_nonce( $_REQUEST['nonce'], 'better_payment_transaction_import_nonce' );
65 $invalid_access = ! current_user_can('manage_options') || ! isset( $_FILES['better-payment-transaction-import-input'] );
66
67 if ( $invalid_nonce || $invalid_access ) {
68 set_transient('better_payment_import_transactions_error', esc_html( $message ), 30);
69 wp_safe_redirect( $_SERVER['HTTP_REFERER'] );
70 exit;
71 }
72
73 $file = $_FILES['better-payment-transaction-import-input'];
74
75 try {
76 $this->validate_csv_file($file);
77 $csv = Reader::createFromPath($file['tmp_name'], 'r');
78 $csv->setHeaderOffset(0);
79
80 // Header validation
81 $csvHeader = $csv->getHeader();
82 if ( isset( $csvHeader[0] ) && false !== strpos( $csvHeader[0], ';' ) ) {
83 $csv->setDelimiter(';');
84 $csvHeader = $csv->getHeader();
85 }
86
87 if ( ! is_array( $csvHeader ) || ! count( $csvHeader) ){
88 throw new Exception( $message );
89 }
90
91 $csvHeader = array_map('sanitize_text_field', $csvHeader);
92
93 $exportObj = new Export();
94 $allowedHeaders = $exportObj->export_transactions_heading();
95
96 if ( count( $allowedHeaders ) !== count( $csvHeader ) ) {
97 throw new Exception( $message );
98 }
99
100 // Fetch rows
101 $stmt = Statement::create()->offset(0)->limit(3000);
102 $records = $stmt->process($csv);
103
104 $data = array();
105
106 foreach ( $records as $record ) {
107 $better_form_fields = $this->build_form_fields( $record );
108
109 $amount_currency_array = explode(' ', $record['amount']);
110 $currency = is_array($amount_currency_array) && count($amount_currency_array) > 1 ? $amount_currency_array[0] : esc_html('USD');
111 $amount = is_array($amount_currency_array) && count($amount_currency_array) > 1 ? $amount_currency_array[1] : $record['amount'];
112
113 $data[] = array(
114 'email' => sanitize_email( $record['email'] ),
115 'currency' => sanitize_text_field( $currency ),
116 'amount' => floatval( $amount ),
117 // 'payment_type' => sanitize_text_field( $record['payment_type'] ),
118 'transaction_id' => sanitize_text_field( $record['transaction_id'] ),
119 'source' => sanitize_text_field( $record['source'] ),
120 'status' => sanitize_text_field( $record['status'] ),
121 'payment_date' => date( 'Y-m-d H:i:s', strtotime( sanitize_text_field( $record['payment_date'] ) ) ),
122 'form_fields_info' => maybe_serialize( $better_form_fields ),
123 );
124 }
125
126 if ( empty( $data ) ) {
127 throw new Exception( __('No valid records found in the CSV file!', 'better-payment') );
128 }
129
130 $table = DB::get_table_name();
131 $rowsInserted = $this->bulk_insert_transactions( $table, $data );
132 if ( $rowsInserted !== false && $rowsInserted > 0 ) {
133 set_transient('better_payment_import_transactions_success', esc_html__( $rowsInserted . ' rows inserted successfully!', 'better-payment' ), 30);
134 } else {
135 throw new Exception( __('Failed to insert records into database!', 'better-payment') );
136 }
137 } catch (\Exception $exception) {
138 set_transient('better_payment_import_transactions_error', esc_html( $exception->getMessage() ), 30);
139 wp_safe_redirect( $_SERVER['HTTP_REFERER'] );
140 exit;
141 }
142
143 wp_safe_redirect( $_SERVER['HTTP_REFERER'] );
144 exit;
145 }
146
147 /**
148 * The `form_fields_info` an imported CSV row is stored with.
149 *
150 * Lite keeps the name, email and source and marks the row imported.
151 * `better_payment/import/form_fields_info` receives that, the CSV row and the
152 * row's decoded `form_fields_info` cell: Pro adds the subscription keys of a
153 * "Subscription" row there. Lite's copy of that
154 * (ProSubscriptionTransactionData::import_fields()) serves only an active Pro
155 * that does not (<= 1.4.3).
156 *
157 * @since 2.4.0
158 *
159 * @param mixed $record CSV row keyed by header; anything else is treated as empty.
160 * @return array
161 */
162 public function build_form_fields( $record ) {
163 $record = is_array( $record ) ? $record : [];
164
165 $better_form_fields = [
166 'primary_first_name' => sanitize_text_field( $record['name'] ?? '' ),
167 'email' => sanitize_email( $record['email'] ?? '' ),
168 'source' => sanitize_text_field( $record['source'] ?? '' ),
169 'is_imported' => intval(1),
170 ];
171
172 // Decoded without instantiating objects; see decode_form_fields_info().
173 $decoded = isset( $record['form_fields_info'] ) ? self::decode_form_fields_info( $record['form_fields_info'] ) : [];
174
175 if ( ProSubscriptionTransactionData::lite_copy_applies() ) {
176 $better_form_fields = ProSubscriptionTransactionData::import_fields( $better_form_fields, $record, $decoded );
177 }
178
179 $filtered = apply_filters( 'better_payment/import/form_fields_info', $better_form_fields, $record, $decoded );
180
181 return is_array( $filtered ) ? $filtered : $better_form_fields;
182 }
183
184 public function validate_csv_file( $file ){
185 $message = __('Invalid File!', 'better-payment');
186
187 if ( empty( $file ) ){
188 throw new Exception( $message );
189 }
190
191 $extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
192 $allowed_extensions = ['csv'];
193
194 if ( empty( $extension ) || ! in_array( $extension, $allowed_extensions ) ) {
195 throw new Exception( $message );
196 }
197
198 return true;
199 }
200
201 public function bulk_insert_transactions($table, $rows) {
202 global $wpdb;
203
204 $headers = array_keys($rows[0]);
205 asort($headers);
206 $columnList = '`' . implode('`, `', $headers) . '`';
207
208 $sql = "INSERT INTO `$table` ($columnList) VALUES\n";
209 $placeholders = array();
210 $data = array();
211
212 foreach ($rows as $row) {
213 ksort($row);
214 $rowPlaceholders = array();
215 foreach ($row as $value) {
216 $data[] = $value;
217 $rowPlaceholders[] = is_numeric($value) ? '%d' : '%s';
218 }
219 $placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')';
220 }
221
222 $sql .= implode(",\n", $placeholders);
223
224 $rowsInserted = $wpdb->query($wpdb->prepare($sql, $data));
225 return $rowsInserted;
226 }
227 }