PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.1
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
← All changes | includes/Classes/Import.php +89 -21 2.2.1 → 2.4.1 View file →
@@ -33,8 +33,64 @@
33 33 public function __construct() {
34 34
35 35 }
36 36
37 + /**
38 + * A CSV `payment_date` cell as the UTC `Y-m-d H:i:s` the table stores.
39 + *
40 + * The export writes the date in site time, in the site's date and time
41 + * format (`Export`: `wp_date()`), so a cell without a timezone is read as
42 + * site time and converted. It used to be stored as if it were UTC, so an
43 + * export → import round trip moved every row by the site's UTC offset. A
44 + * cell that names its own timezone (`Z`, `+06:00`, `UTC`, `GMT`) is honoured
45 + * as given. An unparseable cell keeps the old result (the epoch).
46 + *
47 + * @since 2.4.1
48 + *
49 + * @param string $value Cell value.
50 + * @return string
51 + */
52 + public static function payment_date_from_csv( $value ) {
53 + $value = trim( (string) $value );
54 + $timestamp = strtotime( $value );
55 +
56 + if ( false === $timestamp ) {
57 + return gmdate( 'Y-m-d H:i:s', 0 );
58 + }
59 +
60 + if ( preg_match( '/(?:Z|[+-]\d{2}:?\d{2}|\bUTC|\bGMT)$/i', $value ) ) {
61 + return gmdate( 'Y-m-d H:i:s', $timestamp );
62 + }
63 +
64 + // strtotime() read the wall-clock time as UTC (WordPress runs PHP in UTC);
65 + // it is site time, so convert it.
66 + return get_gmt_from_date( gmdate( 'Y-m-d H:i:s', $timestamp ), 'Y-m-d H:i:s' );
67 + }
68 +
69 + /**
70 + * Decode a CSV `form_fields_info` cell into an array.
71 + *
72 + * The cell comes from an uploaded file, so it is never handed to maybe_unserialize():
73 + * that instantiates any class a serialized payload names, which turns an import into an
74 + * object-injection sink for whatever gadget chain the site's plugins happen to provide.
75 + * Objects are refused outright; only a plain array survives.
76 + *
77 + * @since 2.3.4
78 + *
79 + * @param mixed $value Cell value.
80 + * @return array
81 + */
82 + public static function decode_form_fields_info( $value ) {
83 + if ( ! is_string( $value ) || ! is_serialized( $value ) ) {
84 + return [];
85 + }
86 +
87 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize, WordPress.PHP.NoSilencedErrors.Discouraged -- classes are disallowed; a malformed cell is expected input.
88 + $decoded = @unserialize( trim( $value ), [ 'allowed_classes' => false ] );
89 +
90 + return is_array( $decoded ) ? $decoded : [];
91 + }
92 +
37 93 public function import_transactions() {
38 94 $message = __('Invalid File!', 'better-payment');
39 95
40 96 $invalid_nonce = ! wp_verify_nonce( $_REQUEST['nonce'], 'better_payment_transaction_import_nonce' );
@@ -79,29 +135,10 @@
79 135
80 136 $data = array();
81 137
82 138 foreach ( $records as $record ) {
83 - $better_form_fields = [
84 - 'primary_first_name' => sanitize_text_field( $record['name'] ),
85 - 'email' => sanitize_email( $record['email'] ),
86 - 'source' => sanitize_text_field( $record['source'] ),
87 - 'is_imported' => intval(1),
88 - ];
139 + $better_form_fields = $this->build_form_fields( $record );
89 140
90 - if ( ! empty( $record['payment_type'] ) && strtolower($record['payment_type']) === 'subscription' ) {
91 - $better_form_fields_selected = isset( $record['form_fields_info'] ) ? maybe_unserialize( $record['form_fields_info'] ) : [];
92 -
93 - $better_form_fields['subscription_id'] = isset( $better_form_fields_selected['subscription_id'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_id'] ) : '';
94 - $better_form_fields['subscription_customer_id'] = isset( $better_form_fields_selected['subscription_customer_id'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_customer_id'] ) : '';
95 - $better_form_fields['subscription_plan_id'] = isset( $better_form_fields_selected['subscription_plan_id'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_plan_id'] ) : '';
96 - $better_form_fields['subscription_interval'] = isset( $better_form_fields_selected['subscription_interval'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_interval'] ) : '';
97 - $better_form_fields['subscription_current_period_start'] = isset( $better_form_fields_selected['subscription_current_period_start'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_current_period_start'] ) : '';
98 - $better_form_fields['subscription_current_period_end'] = isset( $better_form_fields_selected['subscription_current_period_end'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_current_period_end'] ) : '';
99 - $better_form_fields['subscription_status'] = isset( $better_form_fields_selected['subscription_status'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_status'] ) : '';
100 - $better_form_fields['subscription_created_date'] = isset( $better_form_fields_selected['subscription_created_date'] ) ? sanitize_text_field( $better_form_fields_selected['subscription_created_date'] ) : '';
101 - $better_form_fields['is_payment_split_payment'] = isset( $better_form_fields_selected['is_payment_split_payment'] ) ? sanitize_text_field( $better_form_fields_selected['is_payment_split_payment'] ) : '';
102 - }
103 -
104 141 $amount_currency_array = explode(' ', $record['amount']);
105 142 $currency = is_array($amount_currency_array) && count($amount_currency_array) > 1 ? $amount_currency_array[0] : esc_html('USD');
106 143 $amount = is_array($amount_currency_array) && count($amount_currency_array) > 1 ? $amount_currency_array[1] : $record['amount'];
107 144
@@ -112,9 +149,9 @@
112 149 // 'payment_type' => sanitize_text_field( $record['payment_type'] ),
113 150 'transaction_id' => sanitize_text_field( $record['transaction_id'] ),
114 151 'source' => sanitize_text_field( $record['source'] ),
115 152 'status' => sanitize_text_field( $record['status'] ),
116 - 'payment_date' => date( 'Y-m-d H:i:s', strtotime( sanitize_text_field( $record['payment_date'] ) ) ),
153 + 'payment_date' => self::payment_date_from_csv( sanitize_text_field( $record['payment_date'] ) ),
117 154 'form_fields_info' => maybe_serialize( $better_form_fields ),
118 155 );
119 156 }
120 157
@@ -136,8 +173,39 @@
136 173 }
137 174
138 175 wp_safe_redirect( $_SERVER['HTTP_REFERER'] );
139 176 exit;
177 + }
178 +
179 + /**
180 + * The `form_fields_info` an imported CSV row is stored with.
181 + *
182 + * Lite keeps the name, email and source and marks the row imported.
183 + * `better_payment/import/form_fields_info` receives that, the CSV row and the
184 + * row's decoded `form_fields_info` cell: Pro adds the subscription keys of a
185 + * "Subscription" row there.
186 + *
187 + * @since 2.4.0
188 + *
189 + * @param mixed $record CSV row keyed by header; anything else is treated as empty.
190 + * @return array
191 + */
192 + public function build_form_fields( $record ) {
193 + $record = is_array( $record ) ? $record : [];
194 +
195 + $better_form_fields = [
196 + 'primary_first_name' => sanitize_text_field( $record['name'] ?? '' ),
197 + 'email' => sanitize_email( $record['email'] ?? '' ),
198 + 'source' => sanitize_text_field( $record['source'] ?? '' ),
199 + 'is_imported' => intval(1),
200 + ];
201 +
202 + // Decoded without instantiating objects; see decode_form_fields_info().
203 + $decoded = isset( $record['form_fields_info'] ) ? self::decode_form_fields_info( $record['form_fields_info'] ) : [];
204 +
205 + $filtered = apply_filters( 'better_payment/import/form_fields_info', $better_form_fields, $record, $decoded );
206 +
207 + return is_array( $filtered ) ? $filtered : $better_form_fields;
140 208 }
141 209
142 210 public function validate_csv_file( $file ){
143 211 $message = __('Invalid File!', 'better-payment');