PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.1
1.6.1 1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
← All changes | inc/api/import-export-api.php +31 -5 1.4.0 → 1.6.1 View file →
@@ -175,15 +175,30 @@
175 175 $truncated = $total_count > $export_cap;
176 176 $donations = Donations::get_for_export( $filters, $export_cap, 0 );
177 177
178 178 // First pass: collect the union of custom-field labels so every row
179 - // shares one consistent set of trailing columns.
180 - $field_labels = [];
179 + // shares one consistent set of trailing columns. The label is
180 + // attacker-controlled (submitted for any slug absent from the saved
181 + // form), so the union is capped and looked up by key rather than
182 + // `in_array()` — otherwise a form fed thousands of distinct labels
183 + // turns this into an O(field_count x label_count) scan and a
184 + // same-sized column set. Labels past the cap are not dropped; they are
185 + // exported instead in a single trailing JSON column below.
186 + $max_field_columns = 50;
187 + $field_labels = [];
188 + $field_label_index = [];
189 + $fields_truncated = false;
181 190 foreach ( $donations as $donation ) {
182 191 foreach ( $this->get_donation_custom_fields( $donation ) as $label => $value ) {
183 - if ( ! in_array( $label, $field_labels, true ) ) {
184 - $field_labels[] = $label;
192 + if ( isset( $field_label_index[ $label ] ) ) {
193 + continue;
185 194 }
195 + if ( count( $field_labels ) >= $max_field_columns ) {
196 + $fields_truncated = true;
197 + continue;
198 + }
199 + $field_label_index[ $label ] = true;
200 + $field_labels[] = $label;
186 201 }
187 202 }
188 203
189 204 $rows = [];
@@ -188,9 +203,10 @@
188 203
189 204 $rows = [];
190 205 $rows[] = array_merge(
191 206 Column_Map::standard_donation_export_labels(),
192 - $field_labels
207 + $field_labels,
208 + $fields_truncated ? [ Column_Map::other_fields_export_label() ] : []
193 209 );
194 210
195 211 $title_cache = [];
196 212 foreach ( $donations as $donation ) {
@@ -226,8 +242,13 @@
226 242 // anonymous donor on a localised site. Every other value in this
227 243 // row is raw for the same reason.
228 244 ! empty( $donation['is_anonymous'] ) ? 'yes' : 'no',
229 245 $donation['donor_comment'] ?? '',
246 + // Untranslated on purpose, same as the anonymity flag above: this is
247 + // the column that decides whether a comment is public, so a translated
248 + // or missing value on re-import would republish comments a moderator
249 + // had rejected.
250 + $donation['donor_comment_status'] ?? '',
230 251 $donation['ip_address'] ?? '',
231 252 $donation['created_at'] ?? '',
232 253 $donation['import_source'] ?? '',
233 254 ! empty( $donation['import_source_id'] ) ? $donation['import_source_id'] : '',
@@ -235,8 +256,13 @@
235 256
236 257 $field_values = $this->get_donation_custom_fields( $donation );
237 258 foreach ( $field_labels as $label ) {
238 259 $row[] = $field_values[ $label ] ?? '';
260 + }
261 +
262 + if ( $fields_truncated ) {
263 + $overflow = array_diff_key( $field_values, $field_label_index );
264 + $row[] = ! empty( $overflow ) ? wp_json_encode( $overflow ) : '';
239 265 }
240 266
241 267 $rows[] = $row;
242 268 }