PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / trunk
GiveWP – Donation Plugin and Fundraising Platform vtrunk
4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / includes / admin / tools / import / class-give-import-subscriptions.php
give / includes / admin / tools / import Last commit date
class-give-import-core-settings.php 1 year ago class-give-import-donations.php 2 years ago class-give-import-subscriptions.php 4 months ago
class-give-import-subscriptions.php
1295 lines
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit; // Exit if accessed directly
5 }
6
7 use Give\Donations\Models\Donation;
8 use Give\Donations\ValueObjects\DonationMetaKeys;
9 use Give\Framework\Database\DB;
10
11 if (!class_exists('Give_Import_Subscriptions')) {
12
13 /**
14 * Give_Import_Subscriptions.
15 *
16 * @since 4.11.0
17 */
18 final class Give_Import_Subscriptions
19 {
20 /**
21 * Importer type
22 *
23 * @var string
24 */
25 private $importer_type = 'import_subscriptions';
26
27 /**
28 * Instance.
29 *
30 * @var static
31 */
32 private static $instance;
33
34 /**
35 * Importing rows per page.
36 *
37 * @var int
38 */
39 public static $per_page = 25;
40
41 /**
42 * CSV valid redirect URL
43 *
44 * @var string|bool
45 */
46 public $is_csv_valid = false;
47
48 /**
49 * Singleton
50 * @since 4.11.0
51 */
52 private function __construct()
53 {
54 self::$per_page = !empty($_GET['per_page']) ? absint($_GET['per_page']) : self::$per_page;
55 }
56
57 /**
58 * Get instance
59 *
60 * @since 4.11.0
61 * @return static
62 */
63 public static function get_instance()
64 {
65 if (null === static::$instance) {
66 self::$instance = new static();
67 }
68
69 return self::$instance;
70 }
71
72 /**
73 * Setup
74 *
75 * @since 4.11.0
76 */
77 public function setup()
78 {
79 $this->setup_hooks();
80 }
81
82 /**
83 * Setup Hooks.
84 *
85 * @since 4.11.0
86 */
87 private function setup_hooks()
88 {
89 if (!$this->is_subscriptions_import_page()) {
90 return;
91 }
92
93 // Do not render main import tools page.
94 remove_action('give_admin_field_tools_import', ['Give_Settings_Import', 'render_import_field']);
95
96 // Render subscriptions import page
97 add_action('give_admin_field_tools_import', [$this, 'render_page']);
98
99 // Print the HTML.
100 add_action('give_tools_import_subscriptions_form_start', [$this, 'html'], 10);
101
102 // Handle submit
103 add_action('give-tools_save_import', [$this, 'save']);
104
105 add_action('give-tools_update_notices', [$this, 'update_notices'], 11, 1);
106
107 // Used to add submit button.
108 add_action('give_tools_import_subscriptions_form_end', [$this, 'submit'], 10);
109 }
110
111 /**
112 * Update notice
113 *
114 * @since 4.11.0
115 * @param $messages
116 *
117 * @return mixed
118 */
119 public function update_notices($messages)
120 {
121 if (!empty($_GET['tab']) && 'import' === give_clean($_GET['tab'])) {
122 unset($messages['give-setting-updated']);
123 }
124
125 return $messages;
126 }
127
128 /**
129 * Print submit and nonce button.
130 *
131 * @since 4.11.0
132 */
133 public function submit()
134 {
135 wp_nonce_field('give-save-settings', '_give-save-settings');
136 ?>
137 <input type="hidden" class="import-step" id="import-step" name="step"
138 value="<?php echo esc_attr($this->get_step()); ?>" />
139 <input type="hidden" class="importer-type" value="<?php echo esc_attr($this->importer_type); ?>" />
140 <?php
141 }
142
143 /**
144 * Print the HTML for importer.
145 *
146 * @since 4.11.0
147 */
148 public function html()
149 {
150 $step = $this->get_step();
151
152 // Show progress.
153 $this->render_progress();
154 ?>
155 <section>
156 <table
157 class="widefat export-options-table give-table <?php echo esc_attr("step-{$step}"); ?> <?php echo esc_attr((1 === $step && !empty($this->is_csv_valid) ? 'give-hidden' : '')); ?> "
158 id="<?php echo esc_attr("step-{$step}"); ?>">
159 <tbody>
160 <?php
161 switch ($step) {
162 case 1:
163 $this->render_media_csv();
164 break;
165
166 case 2:
167 $this->render_dropdown();
168 break;
169
170 case 3:
171 $this->start_import();
172 break;
173
174 case 4:
175 $this->import_success();
176 }
177 if (false === $this->check_for_dropdown_or_import()) {
178 ?>
179 <tr valign="top">
180 <th>
181 <input type="submit"
182 class="button button-primary button-large button-secondary <?php echo esc_attr("step-{$step}"); ?>"
183 id="recount-stats-submit"
184 value="
185 <?php
186 echo esc_attr(apply_filters('give_import_subscription_submit_button_text', __('Submit', 'give')));
187 ?>
188 " />
189 </th>
190 <th>
191 <?php
192 do_action('give_import_subscription_submit_button');
193 ?>
194 </th>
195 </tr>
196 <?php
197 }
198 ?>
199 </tbody>
200 </table>
201 </section>
202 <?php
203 }
204
205 /**
206 * Show success notice
207 *
208 * @since 4.11.0
209 */
210 public function import_success()
211 {
212 check_admin_referer('give_subscription_import_success');
213
214 $delete_csv = (!empty($_GET['delete_csv']) ? absint($_GET['delete_csv']) : false);
215 $csv = (!empty($_GET['csv']) ? absint($_GET['csv']) : false);
216 if (!empty($delete_csv) && !empty($csv)) {
217 wp_delete_attachment($csv, true);
218 }
219
220 $report = $this->get_report();
221
222 $total = (int)$_GET['total'];
223 --$total;
224 $success = (bool)$_GET['success'];
225 $dry_run = empty($_GET['dry_run']) ? 0 : absint($_GET['dry_run']);
226 ?>
227 <tr valign="top" class="give-import-dropdown">
228 <th colspan="2">
229 <h2>
230 <?php
231 if ($success) {
232 if ($dry_run) {
233 printf(
234 _n('Dry run import complete! %s row processed', 'Dry run import complete! %s rows processed', $total, 'give'),
235 "<strong>{$total}</strong>"
236 );
237 } else {
238 printf(
239 _n('Import complete! %s row processed', 'Import complete! %s rows processed', $total, 'give'),
240 "<strong>{$total}</strong>"
241 );
242 }
243 } else {
244 printf(
245 _n('Failed to import %s row', 'Failed to import %s rows', $total, 'give'),
246 "<strong>{$total}</strong>"
247 );
248 }
249 ?>
250 </h2>
251
252 <?php
253 $text = __('Import Subscriptions', 'give');
254 $query_arg = [
255 'post_type' => 'give_forms',
256 'page' => 'give-tools',
257 'tab' => 'import',
258 ];
259 if ($success) {
260 if ($dry_run) {
261 $query_arg = [
262 'post_type' => 'give_forms',
263 'page' => 'give-tools',
264 'tab' => 'import',
265 'importer-type' => 'import_subscriptions',
266 ];
267 $text = __('Start Import', 'give');
268 } else {
269 $query_arg = [
270 'post_type' => 'give_forms',
271 'page' => 'give-subscriptions',
272 ];
273 $text = __('View Subscriptions', 'give');
274 }
275 }
276
277 if (!empty($report)) {
278 if (isset($report['create_subscription'])) {
279 echo '<p>' . sprintf(_n('%s subscription created', '%s subscriptions created', (int)$report['create_subscription'], 'give'), (int)$report['create_subscription']) . '</p>';
280 }
281 if (isset($report['failed_subscription'])) {
282 echo '<p>' . sprintf(_n('%s subscription failed', '%s subscriptions failed', (int)$report['failed_subscription'], 'give'), (int)$report['failed_subscription']) . '</p>';
283 }
284 if (!empty($report['failed_subscription_initial_donation'])) {
285 echo '<p>' . sprintf(_n('%s initial donation failed', '%s initial donations failed', (int)$report['failed_subscription_initial_donation'], 'give'), (int)$report['failed_subscription_initial_donation']) . '</p>';
286 }
287 if (!empty($report['errors']) && is_array($report['errors'])) {
288 echo '<div class="notice notice-error" style="margin-top:10px;">';
289 echo '<p><strong>' . esc_html__('Errors', 'give') . ':</strong></p>';
290 echo '<ul style="margin-left:20px;list-style:disc;">';
291 foreach ($report['errors'] as $err) {
292 echo '<li>' . esc_html($err) . '</li>';
293 }
294 echo '</ul>';
295 echo '</div>';
296 }
297 }
298 ?>
299
300 <p>
301 <a class="button button-large button-secondary"
302 href="<?php echo esc_url(add_query_arg($query_arg, admin_url('edit.php'))); ?>"><?php echo esc_html($text); ?></a>
303 </p>
304 </th>
305 </tr>
306 <?php
307 }
308
309 /**
310 * Start Import
311 * @since 4.11.0
312 */
313 public function start_import()
314 {
315 $this->reset_report();
316
317 $csv = absint($_REQUEST['csv']);
318 $delimiter = (!empty($_REQUEST['delimiter']) ? give_clean($_REQUEST['delimiter']) : 'csv');
319 $index_start = 1;
320 $next = true;
321 $total = self::get_csv_total($csv);
322 if (self::$per_page < $total) {
323 $total_ajax = ceil($total / self::$per_page);
324 $index_end = self::$per_page;
325 } else {
326 $total_ajax = 1;
327 $index_end = $total;
328 $next = false;
329 }
330 $current_percentage = 100 / ($total_ajax + 1);
331
332 ?>
333 <tr valign="top" class="give-import-dropdown">
334 <th colspan="2">
335 <h2 id="give-import-title"><?php _e('Importing', 'give'); ?></h2>
336 <p class="give-field-description"><?php _e('Your subscriptions are now being imported...', 'give'); ?></p>
337 </th>
338 </tr>
339
340 <tr valign="top" class="give-import-dropdown">
341 <th colspan="2">
342 <span class="spinner is-active"></span>
343 <div class="give-progress"
344 data-current="1"
345 data-total_ajax="<?php echo esc_attr((int)$total_ajax); ?>"
346 data-start="<?php echo esc_attr((int)$index_start); ?>"
347 data-end="<?php echo esc_attr((int)$index_end); ?>"
348 data-next="<?php echo esc_attr((int)$next); ?>"
349 data-total="<?php echo esc_attr((int)$total); ?>"
350 data-per_page="<?php echo esc_attr((int)self::$per_page); ?>">
351
352 <div style="width: <?php echo esc_attr((float)$current_percentage); ?>%"></div>
353 </div>
354 <input type="hidden" value="3" name="step">
355 <input type="hidden" value='<?php echo esc_attr(maybe_serialize($_REQUEST['mapto'])); ?>' name="mapto" class="mapto">
356 <input type="hidden" value="<?php echo esc_attr((int)$csv); ?>" name="csv" class="csv">
357 <input type="hidden" value="<?php echo esc_attr(isset($_REQUEST['mode']) ? sanitize_text_field((string)$_REQUEST['mode']) : ''); ?>" name="mode" class="mode">
358 <input type="hidden" value="<?php echo esc_attr(isset($_REQUEST['create_user']) ? (int)$_REQUEST['create_user'] : 0); ?>" name="create_user" class="create_user">
359 <input type="hidden" value="<?php echo esc_attr(isset($_REQUEST['delete_csv']) ? (int)$_REQUEST['delete_csv'] : 0); ?>" name="delete_csv" class="delete_csv">
360 <input type="hidden" value="<?php echo esc_attr($delimiter); ?>" name="delimiter">
361 <input type="hidden" value="<?php echo esc_attr(isset($_REQUEST['dry_run']) ? (int)$_REQUEST['dry_run'] : 0); ?>" name="dry_run">
362 <input type="hidden" value='<?php echo esc_attr(maybe_serialize(self::get_importer($csv, 0, $delimiter))); ?>' name="main_key" class="main_key">
363 </th>
364 </tr>
365 <?php
366 }
367
368 /**
369 * Validate required mapped fields
370 *
371 * 4.14.1 Check if donor_id or email is mapped to the columns
372 * @since 4.11.0
373 */
374 public function check_for_dropdown_or_import()
375 {
376 $return = true;
377 if (isset($_REQUEST['mapto'])) {
378 $mapto = (array)$_REQUEST['mapto'];
379 $required = ['form_id', 'period', 'frequency', 'amount', 'status'];
380
381 // Add donor_id or email to required based on what's present
382 if (in_array('donor_id', $mapto)) {
383 $required[] = 'donor_id';
384 } elseif (in_array('email', $mapto)) {
385 $required[] = 'email';
386 } else {
387 // Neither is present, show custom error message
388 Give_Admin_Settings::add_error('give-import-csv-subscriptions', __('A column must be mapped to "donor_id" or "email".', 'give'));
389 $return = false;
390 }
391
392 foreach ($required as $key) {
393 if (false === in_array($key, $mapto)) {
394 Give_Admin_Settings::add_error('give-import-csv-subscriptions', sprintf(__('A column must be mapped to "%s".', 'give'), $key));
395 $return = false;
396 }
397 }
398 } else {
399 $return = false;
400 }
401
402 return $return;
403 }
404
405 /**
406 * Print the Dropdown option for CSV.
407 * @since 4.11.0
408 */
409 public function render_dropdown()
410 {
411 if (!$this->is_nonce_valid()) {
412 Give_Admin_Settings::add_error('give-import-csv', __('Something went wrong.', 'give'));
413 ?>
414 <input type="hidden" name="csv_not_valid" class="csv_not_valid" value="<?php echo esc_attr(give_import_page_url()); ?>" />
415 <?php
416 wp_die();
417 }
418
419 $csv = (int)$_GET['csv'];
420 $delimiter = (!empty($_GET['delimiter']) ? give_clean($_GET['delimiter']) : 'csv');
421
422 if (!$this->is_valid_csv($csv)) {
423 $url = give_import_page_url();
424 ?>
425 <input type="hidden" name="csv_not_valid" class="csv_not_valid" value="<?php echo esc_attr($url); ?>" />
426 <?php
427 } else {
428 ?>
429 <tr valign="top" class="give-import-dropdown">
430 <th colspan="2">
431 <h2 id="give-import-title"><?php _e('Map CSV fields to subscriptions', 'give'); ?></h2>
432
433 <p class="give-import-donation-required-fields-title"><?php _e('Required Fields', 'give'); ?></p>
434
435 <p class="give-field-description"><?php _e('These fields are required for the import to be submitted', 'give'); ?></p>
436
437 <ul class="give-import-subscription-required-fields">
438 <li class="give-import-subscription-required-donorId" title="Please configure all required fields to start the import process.">
439 <span class="give-import-donation-required-text"><?php _e('Form ID', 'give'); ?></span>
440 </li>
441 <li class="give-import-subscription-required-donationFormId" title="Please configure all required fields to start the import process.">
442 <span class="give-import-donation-required-text"><?php _e('Donor ID or Donor Email', 'give'); ?></span>
443 </li>
444 <li class="give-import-subscription-required-period" title="Please configure all required fields to start the import process.">
445 <span class="give-import-donation-required-text"><?php _e('Period', 'give'); ?> (day, week, month, year)</span>
446 </li>
447 <li class="give-import-subscription-required-frequency" title="Please configure all required fields to start the import process.">
448 <span class="give-import-donation-required-text"><?php _e('Frequency', 'give'); ?></span>
449 </li>
450 <li class="give-import-subscription-required-amount" title="Please configure all required fields to start the import process.">
451 <span class="give-import-donation-required-text"><?php _e('Amount (donor facing amount)', 'give'); ?></span>
452 </li>
453 <li class="give-import-subscription-required-status" title="Please configure all required fields to start the import process.">
454 <span class="give-import-donation-required-text"><?php _e('Status', 'give'); ?> (active, expired, cancelled, suspended, paused, pending)</span>
455 </li>
456 </ul>
457
458 <p class="give-field-description"><?php _e('Select fields from your CSV file to map against subscription fields or to ignore during import.', 'give'); ?></p>
459 </th>
460 </tr>
461
462 <tr valign="top" class="give-import-dropdown">
463 <th><b><?php _e('Column name', 'give'); ?></b></th>
464 <th><b><?php _e('Map to field', 'give'); ?></b></th>
465 </tr>
466
467 <?php
468 $selectedOptions = [];
469 $raw_key = $this->get_importer($csv, 0, $delimiter);
470 $mapto = (array)(isset($_REQUEST['mapto']) ? $_REQUEST['mapto'] : []);
471
472 foreach ($raw_key as $index => $value) {
473 ?>
474 <tr valign="middle" class="give-import-option">
475 <th><?php echo esc_html($value); ?></th>
476 <th>
477 <?php $this->get_columns($index, $value, $mapto, $selectedOptions); ?>
478 </th>
479 </tr>
480 <?php
481 }
482 }
483 }
484
485 /**
486 * Determine selected option by heuristics
487 */
488 public function selected($option_value, $value)
489 {
490 $option_value = strtolower($option_value);
491 $value = strtolower($value);
492
493 $selected = '';
494 if (stristr($value, $option_value)) {
495 $selected = 'selected';
496 }
497
498 return $selected;
499 }
500
501 /**
502 * Print the columns from the CSV.
503 */
504 private function get_columns($index, $value = false, $mapto = [], &$selectedOptions = [])
505 {
506 $default = give_import_default_options();
507 $current_mapto = (string)(!empty($mapto[$index]) ? $mapto[$index] : '');
508 ?>
509 <select name="mapto[<?php echo esc_attr($index); ?>]">
510 <?php $this->get_dropdown_option_html($default, $current_mapto, $value, $selectedOptions); ?>
511
512 <optgroup label="<?php _e('Subscriptions', 'give'); ?>">
513 <?php $this->get_dropdown_option_html($this->get_subscription_options(), $current_mapto, $value, $selectedOptions); ?>
514 </optgroup>
515 </select>
516 <?php
517 }
518
519 /**
520 * Print the option html for select in importer
521 */
522 public function get_dropdown_option_html($options, $current_mapto, $value = false, &$selectedOptions = [])
523 {
524 foreach ($options as $option => $option_value) {
525 $ignore = [];
526 if (isset($option_value['ignore']) && is_array($option_value['ignore'])) {
527 $ignore = $option_value['ignore'];
528 unset($option_value['ignore']);
529 }
530
531 $option_value_texts = (array)$option_value;
532 $option_text = $option_value_texts[0];
533
534 $selected = false;
535
536 if ($current_mapto === $option && !in_array($option, $selectedOptions)) {
537 $selected = 'selected';
538 $selectedOptions[] = $option;
539 } else {
540 if (!in_array($value, $ignore) && !in_array($option, $selectedOptions)) {
541 foreach ($option_value_texts as $option_value_text) {
542 $selected = $this->selected($option_value_text, $value);
543 if ($selected) {
544 $selectedOptions[] = $option;
545 break;
546 }
547 }
548 // Extra heuristics: match header to option key by normalized token
549 if (!$selected) {
550 $normalize = static function ($str) {
551 $str = strtolower((string)$str);
552 return preg_replace('/[^a-z0-9]/', '', $str);
553 };
554
555 $valueNorm = $normalize($value);
556 $optionNorm = $normalize($option);
557
558 if ($valueNorm && $optionNorm && $valueNorm === $optionNorm) {
559 $selected = 'selected';
560 $selectedOptions[] = $option;
561 } else {
562 // Try normalized match against visible label too
563 $labelNorm = $normalize($option_text);
564 if ($labelNorm && $valueNorm && $labelNorm === $valueNorm) {
565 $selected = 'selected';
566 $selectedOptions[] = $option;
567 }
568 }
569 }
570 }
571 }
572 ?>
573 <option value="<?php echo esc_attr($option); ?>" <?php echo esc_html($selected); ?>><?php echo esc_html($option_text); ?></option>
574 <?php
575 }
576 }
577
578 /**
579 * Get column count of csv file.
580 */
581 public function get_csv_total($file_id)
582 {
583 $total = false;
584 if ($file_id) {
585 $file_dir = get_attached_file($file_id);
586 if ($file_dir) {
587 $total = $this->get_csv_data_from_file_dir($file_dir);
588 }
589 }
590
591 return $total;
592 }
593
594 /**
595 * Get data from File
596 */
597 public function get_csv_data_from_file_dir($file_dir)
598 {
599 $total = false;
600 if ($file_dir) {
601 $file = new SplFileObject($file_dir, 'r');
602 $file->seek(PHP_INT_MAX);
603 $total = $file->key() + 1;
604 }
605
606 return $total;
607 }
608
609 /**
610 * Read a slice of CSV rows for subscriptions import
611 */
612 public function get_subscription_data_from_csv($file_id, $start, $end, $delimiter = 'csv')
613 {
614 $delimiter = (string)apply_filters('give_import_delimiter_set', $delimiter);
615 $file_dir = give_get_file_data_by_file_id($file_id);
616 return give_get_raw_data_from_file($file_dir, $start, $end, $delimiter);
617 }
618
619 /**
620 * Get the CSV fields title from the CSV.
621 */
622 public function get_importer($file_id, $index = 0, $delimiter = 'csv')
623 {
624 $delimiter = (string)apply_filters('give_import_delimiter_set', $delimiter);
625
626 $raw_data = false;
627 $file_dir = get_attached_file($file_id);
628 if ($file_dir) {
629 if (false !== ($handle = fopen($file_dir, 'r'))) {
630 $raw_data = fgetcsv($handle, $index, $delimiter);
631 if (isset($raw_data[0])) {
632 $raw_data[0] = $this->remove_utf8_bom($raw_data[0]);
633 }
634 }
635 }
636
637 return $raw_data;
638 }
639
640 /**
641 * Remove UTF-8 BOM signature.
642 */
643 public function remove_utf8_bom($string)
644 {
645 if ('efbbbf' === substr(bin2hex($string), 0, 6)) {
646 $string = substr($string, 3);
647 }
648
649 return $string;
650 }
651
652 /**
653 * Render progress steps
654 */
655 public function render_progress()
656 {
657 $step = $this->get_step();
658 ?>
659 <ol class="give-progress-steps">
660 <li class="<?php echo esc_attr(1 === $step ? 'active' : ''); ?>">
661 <?php _e('Upload CSV file', 'give'); ?>
662 </li>
663 <li class="<?php echo esc_attr(2 === $step ? 'active' : ''); ?>">
664 <?php _e('Column mapping', 'give'); ?>
665 </li>
666 <li class="<?php echo esc_attr(3 === $step ? 'active' : ''); ?>">
667 <?php _e('Import', 'give'); ?>
668 </li>
669 <li class="<?php echo esc_attr(4 === $step ? 'active' : ''); ?>">
670 <?php _e('Done!', 'give'); ?>
671 </li>
672 </ol>
673 <?php
674 }
675
676 /**
677 * Will return the import step.
678 */
679 public function get_step()
680 {
681 $step = (int)(isset($_REQUEST['step']) ? give_clean($_REQUEST['step']) : 0);
682 $on_step = 1;
683
684 if (empty($step) || 1 === $step) {
685 $on_step = 1;
686 } elseif ($this->check_for_dropdown_or_import()) {
687 $on_step = 3;
688 } elseif (2 === $step) {
689 $on_step = 2;
690 } elseif (4 === $step) {
691 $on_step = 4;
692 }
693
694 return $on_step;
695 }
696
697 /**
698 * Render subscriptions import page
699 */
700 public function render_page()
701 {
702 include_once GIVE_PLUGIN_DIR . 'includes/admin/tools/views/html-admin-page-import-subscriptions.php';
703 }
704
705 /**
706 * Dry Run checkbox and helper
707 */
708 public function give_import_subscription_submit_button_render_media_csv()
709 {
710 $dry_run = isset($_POST['dry_run']) ? absint($_POST['dry_run']) : 1;
711 ?>
712 <div>
713 <label for="dry_run">
714 <input type="hidden" name="dry_run" value="0" />
715 <input type="checkbox" name="dry_run" id="dry_run" class="dry_run"
716 value="1" <?php checked(1, $dry_run); ?>>
717 <strong><?php _e('Dry Run', 'give'); ?></strong>
718 </label>
719 <p class="give-field-description">
720 <?php _e('Preview what the import would look like without making any changes.', 'give'); ?>
721 </p>
722 </div>
723 <?php
724 }
725
726 /**
727 * Change submit button text on first step
728 */
729 function give_import_subscription_submit_text_render_media_csv($text)
730 {
731 return __('Begin Import', 'give');
732 }
733
734 /**
735 * Add CSV upload HTMl
736 */
737 public function render_media_csv()
738 {
739 add_filter(
740 'give_import_subscription_submit_button_text',
741 [$this, 'give_import_subscription_submit_text_render_media_csv']
742 );
743 add_action(
744 'give_import_subscription_submit_button',
745 [$this, 'give_import_subscription_submit_button_render_media_csv']
746 );
747 ?>
748 <tr valign="top">
749 <th colspan="2">
750 <h2 id="give-import-title"><?php _e('Import subscriptions from a CSV file', 'give'); ?></h2>
751 <p class="give-field-description"><?php _e('This tool allows you to import subscription data via a CSV file.', 'give'); ?></p>
752 </th>
753 </tr>
754 <?php
755 $csv = (isset($_POST['csv']) ? give_clean($_POST['csv']) : '');
756 $csv_id = (isset($_POST['csv_id']) ? give_clean($_POST['csv_id']) : '');
757 $delimiter = (isset($_POST['delimiter']) ? give_clean($_POST['delimiter']) : 'csv');
758 $mode = empty($_POST['mode']) ? 'disabled' : (give_is_setting_enabled(give_clean($_POST['mode'])) ? 'enabled' : 'disabled');
759 $create_user = empty($_POST['create_user']) ? 'disabled' : (give_is_setting_enabled(give_clean($_POST['create_user'])) ? 'enabled' : 'disabled');
760 $delete_csv = empty($_POST['delete_csv']) ? 'enabled' : (give_is_setting_enabled(give_clean($_POST['delete_csv'])) ? 'enabled' : 'disabled');
761
762 if (empty($csv_id) || !$this->is_valid_csv($csv_id, $csv)) {
763 $csv_id = $csv = '';
764 }
765 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : self::$per_page;
766
767 $sample_file_text = sprintf(
768 '%s <a href="%s">%s</a>.',
769 __('Download the sample file', 'give'),
770 esc_url(GIVE_PLUGIN_URL . 'sample-data/sample-subscriptions.csv'),
771 __('here', 'give')
772 );
773
774 $csv_description = sprintf(
775 '%1$s %2$s',
776 __('The file must be a Comma Separated Values (CSV) file type only.', 'give'),
777 $sample_file_text
778 );
779
780 $settings = [
781 [
782 'id' => 'csv',
783 'name' => __('Choose a CSV file:', 'give'),
784 'type' => 'file',
785 'attributes' => [
786 'editing' => 'false',
787 'library' => 'text',
788 ],
789 'description' => $csv_description,
790 'fvalue' => 'url',
791 'default' => $csv,
792 ],
793 [
794 'id' => 'csv_id',
795 'type' => 'hidden',
796 'value' => $csv_id,
797 ],
798 [
799 'id' => 'delimiter',
800 'name' => __('CSV Delimiter:', 'give'),
801 'description' => __('If your CSV uses a different delimiter (like a tab), set that here.', 'give'),
802 'default' => $delimiter,
803 'type' => 'select',
804 'options' => [
805 'csv' => __('Comma', 'give'),
806 'tab-separated-values' => __('Tab', 'give'),
807 ],
808 ],
809 [
810 'id' => 'mode',
811 'name' => __('Test Mode:', 'give'),
812 'description' => __('Select whether these subscriptions should be marked as "test".', 'give'),
813 'default' => $mode,
814 'type' => 'radio_inline',
815 'options' => [
816 'enabled' => __('Enabled', 'give'),
817 'disabled' => __('Disabled', 'give'),
818 ],
819 ],
820 [
821 'id' => 'create_user',
822 'name' => __('Create WP users for new donors:', 'give'),
823 'description' => __('Automatically create a WordPress user account for newly created donors. This is required for donors to access their Donor Dashboard and manage their subscriptions.', 'give'),
824 'default' => $create_user,
825 'type' => 'radio_inline',
826 'options' => [
827 'enabled' => __('Enabled', 'give'),
828 'disabled' => __('Disabled', 'give'),
829 ],
830 ],
831 [
832 'id' => 'delete_csv',
833 'name' => __('Delete CSV after import:', 'give'),
834 'description' => __('Delete the uploaded CSV from the Media Library after import.', 'give'),
835 'default' => $delete_csv,
836 'type' => 'radio_inline',
837 'options' => [
838 'enabled' => __('Enabled', 'give'),
839 'disabled' => __('Disabled', 'give'),
840 ],
841 ],
842 [
843 'id' => 'per_page',
844 'name' => __('Process Rows Per Batch:', 'give'),
845 'type' => 'number',
846 'description' => __('Determine how many rows you would like to import per cycle.', 'give'),
847 'default' => $per_page,
848 'class' => 'give-text-small',
849 ],
850 ];
851
852 $settings = apply_filters('give_import_file_upload_html', $settings);
853
854 if (empty($this->is_csv_valid)) {
855 Give_Admin_Settings::output_fields($settings, 'give_settings');
856 } else {
857 ?>
858 <input type="hidden" name="is_csv_valid" class="is_csv_valid"
859 value="<?php echo esc_attr($this->is_csv_valid); ?>">
860 <?php
861 }
862 }
863
864 /**
865 * Run when user click on the submit button.
866 */
867 public function save()
868 {
869 if (!$this->is_nonce_valid()) {
870 wp_die();
871 }
872
873 $step = $this->get_step();
874
875 if (1 === $step) {
876 $csv_id = absint($_POST['csv_id']);
877
878 if ($this->is_valid_csv($csv_id, esc_url($_POST['csv']))) {
879 $url = give_import_page_url(
880 [
881 'step' => '2',
882 'importer-type' => $this->importer_type,
883 'csv' => $csv_id,
884 'delimiter' => isset($_REQUEST['delimiter']) ? give_clean($_REQUEST['delimiter']) : 'csv',
885 'mode' => empty($_POST['mode']) ? '0' : (give_is_setting_enabled(give_clean($_POST['mode'])) ? '1' : '0'),
886 'create_user' => empty($_POST['create_user']) ? '0' : (give_is_setting_enabled(give_clean($_POST['create_user'])) ? '1' : '0'),
887 'delete_csv' => empty($_POST['delete_csv']) ? '1' : (give_is_setting_enabled(give_clean($_POST['delete_csv'])) ? '1' : '0'),
888 'per_page' => isset($_POST['per_page']) ? absint($_POST['per_page']) : self::$per_page,
889 'dry_run' => isset($_POST['dry_run']) ? absint($_POST['dry_run']) : 0,
890 ]
891 );
892
893 $this->is_csv_valid = wp_nonce_url($url, 'give-save-settings', '_give-save-settings');
894 }
895 }
896 }
897
898 /**
899 * Check if user uploaded csv is valid or not.
900 */
901 private function is_valid_csv($csv = false, $match_url = '')
902 {
903 $is_valid_csv = true;
904
905 if ($csv) {
906 $csv_url = wp_get_attachment_url($csv);
907
908 $delimiter = (!empty($_REQUEST['delimiter']) ? give_clean($_REQUEST['delimiter']) : 'csv');
909
910 if (
911 !$csv_url ||
912 (!empty($match_url) && ($csv_url !== $match_url)) ||
913 (($mime_type = get_post_mime_type($csv)) && !strpos($mime_type, $delimiter))
914 ) {
915 $is_valid_csv = false;
916 Give_Admin_Settings::add_error('give-import-csv', __('Please upload or provide a valid CSV file.', 'give'));
917 }
918 } else {
919 $is_valid_csv = false;
920 Give_Admin_Settings::add_error('give-import-csv', __('Please upload or provide a valid CSV file.', 'give'));
921 }
922
923 return $is_valid_csv;
924 }
925
926 /**
927 * Get if current page import donations page or not
928 */
929 private function is_subscriptions_import_page()
930 {
931 return 'import' === give_get_current_setting_tab() &&
932 isset($_GET['importer-type']) &&
933 $this->importer_type === give_clean($_GET['importer-type']);
934 }
935
936 /**
937 * Nonce validation
938 */
939 private function is_nonce_valid()
940 {
941 return !empty($_REQUEST['_give-save-settings']) && wp_verify_nonce($_REQUEST['_give-save-settings'], 'give-save-settings');
942 }
943
944 /**
945 * Import a single subscription row from CSV
946 *
947 * @param array $raw_key
948 * @param array $row_data
949 * @param array $main_key
950 * @param array $import_setting
951 * @return bool|int|string
952 */
953 public function import_row($raw_key, $row_data, $main_key = [], $import_setting = [])
954 {
955 $report = $this->get_report();
956 $dry_run = isset($import_setting['dry_run']) ? (bool)$import_setting['dry_run'] : false;
957
958 if (
959 empty($row_data) || (is_array($row_data) && 0 === count(array_filter($row_data, function ($v) {
960 return $v !== null && $v !== '';
961 })))
962 ) {
963 return true;
964 }
965
966 if (!is_array($row_data) || count($row_data) !== count($raw_key)) {
967 $report['failed_subscription'] = (!empty($report['failed_subscription']) ? (absint($report['failed_subscription']) + 1) : 1);
968 $this->update_report($report);
969 return false;
970 }
971
972 $data = array_combine($raw_key, $row_data);
973
974 $required = ['form_id', 'period', 'frequency', 'amount', 'status'];
975 foreach ($required as $key) {
976 if (empty($data[$key]) && '0' !== (string)(isset($data[$key]) ? $data[$key] : '')) {
977 $report['failed_subscription'] = (!empty($report['failed_subscription']) ? (absint($report['failed_subscription']) + 1) : 1);
978 $report['errors'][] = sprintf(__('Row %1$d: Missing required field "%2$s"', 'give'), (int)(isset($import_setting['row_key']) ? $import_setting['row_key'] : 0), $key);
979 $this->update_report($report);
980 return 'Missing required field ' . $key;
981 }
982 }
983 if (empty($data['donor_id']) && empty($data['email'])) {
984 $report['failed_subscription'] = (!empty($report['failed_subscription']) ? (absint($report['failed_subscription']) + 1) : 1);
985 $report['errors'][] = sprintf(__('Row %d: Either donor_id or email is required to resolve the donor', 'give'), (int)(isset($import_setting['row_key']) ? $import_setting['row_key'] : 0));
986 $this->update_report($report);
987 return 'Missing donor identifier (donor_id or email)';
988 }
989
990 try {
991 $currency = !empty($data['currency']) && array_key_exists($data['currency'], give_get_currencies_list()) ? $data['currency'] : give_get_currency();
992
993 $attributes = [];
994 $attributes['donationFormId'] = (int)$data['form_id'];
995
996 $resolvedDonorId = 0;
997 if (!empty($data['donor_id'])) {
998 $resolvedDonorId = (int)$data['donor_id'];
999 } else {
1000 try {
1001 $email = (string)$data['email'];
1002 $firstNameCsv = (string)(isset($data['first_name']) ? $data['first_name'] : '');
1003 $lastNameCsv = (string)(isset($data['last_name']) ? $data['last_name'] : '');
1004 $donorModel = give(\Give\DonationForms\Actions\GetOrCreateDonor::class)(null, $email, $firstNameCsv, $lastNameCsv, null, null);
1005 if (!empty($import_setting['create_user']) && (int)$import_setting['create_user'] === 1) {
1006 try {
1007 $donorModel = give(\Give\Donors\Actions\CreateUserFromDonor::class)($donorModel);
1008 } catch (\Throwable $e) {
1009 }
1010 }
1011 $resolvedDonorId = (int)$donorModel->id;
1012 } catch (\Throwable $e) {
1013 $report['failed_subscription'] = (!empty($report['failed_subscription']) ? (absint($report['failed_subscription']) + 1) : 1);
1014 $this->update_report($report);
1015 return false;
1016 }
1017 }
1018 $attributes['donorId'] = $resolvedDonorId;
1019
1020 $rawPeriod = strtolower(trim((string)$data['period']));
1021 $periodAliases = [
1022 'daily' => 'day',
1023 'days' => 'day',
1024 'day' => 'day',
1025 'weekly' => 'week',
1026 'weeks' => 'week',
1027 'week' => 'week',
1028 'monthly' => 'month',
1029 'months' => 'month',
1030 'month' => 'month',
1031 'quarterly' => 'quarter',
1032 'quarters' => 'quarter',
1033 'qtr' => 'quarter',
1034 'qtrs' => 'quarter',
1035 'quarter' => 'quarter',
1036 'yearly' => 'year',
1037 'annually' => 'year',
1038 'annual' => 'year',
1039 'yrs' => 'year',
1040 'yr' => 'year',
1041 'years' => 'year',
1042 'year' => 'year',
1043 ];
1044 $normalizedPeriod = isset($periodAliases[$rawPeriod]) ? $periodAliases[$rawPeriod] : $rawPeriod;
1045 if (!\Give\Subscriptions\ValueObjects\SubscriptionPeriod::isValid($normalizedPeriod)) {
1046 throw new \UnexpectedValueException(sprintf(
1047 __('Invalid subscription period "%1$s". Valid options: %2$s. You can also use: daily, weekly, monthly, quarterly, yearly.', 'give'),
1048 (string)$data['period'],
1049 implode(', ', array_values(\Give\Subscriptions\ValueObjects\SubscriptionPeriod::toArray()))
1050 ));
1051 }
1052 $attributes['period'] = new \Give\Subscriptions\ValueObjects\SubscriptionPeriod($normalizedPeriod);
1053 $attributes['frequency'] = (int)$data['frequency'];
1054 $attributes['installments'] = isset($data['installments']) ? (int)$data['installments'] : 0;
1055 $attributes['transactionId'] = isset($data['transaction_id']) ? (string)$data['transaction_id'] : '';
1056
1057 if (!empty($data['mode'])) {
1058 $mode = strtolower((string)$data['mode']);
1059 } else {
1060 $mode = (isset($import_setting['mode']) && $import_setting['mode']) ? 'test' : (give_is_test_mode() ? 'test' : 'live');
1061 }
1062 $attributes['mode'] = new \Give\Subscriptions\ValueObjects\SubscriptionMode($mode);
1063
1064 $amountDecimal = is_string($data['amount']) ? preg_replace('/[\$,]/', '', $data['amount']) : $data['amount'];
1065 $attributes['amount'] = \Give\Framework\Support\ValueObjects\Money::fromDecimal($amountDecimal, $currency);
1066
1067 if (isset($data['fee_amount_recovered']) && $data['fee_amount_recovered'] !== '') {
1068 $feeDecimal = is_string($data['fee_amount_recovered']) ? preg_replace('/[\$,]/', '', $data['fee_amount_recovered']) : $data['fee_amount_recovered'];
1069 $attributes['feeAmountRecovered'] = \Give\Framework\Support\ValueObjects\Money::fromDecimal($feeDecimal, $currency);
1070 }
1071
1072 $attributes['status'] = new \Give\Subscriptions\ValueObjects\SubscriptionStatus(strtolower(trim((string)$data['status'])));
1073
1074 if (!empty($data['gateway_id'])) {
1075 $attributes['gatewayId'] = (string)$data['gateway_id'];
1076 }
1077 if (!empty($data['gateway_subscription_id'])) {
1078 $attributes['gatewaySubscriptionId'] = (string)$data['gateway_subscription_id'];
1079 }
1080
1081 if (!empty($data['created_at'])) {
1082 $attributes['createdAt'] = new \DateTime((string)$data['created_at']);
1083 }
1084 if (!empty($data['renews_at'])) {
1085 $attributes['renewsAt'] = new \DateTime((string)$data['renews_at']);
1086 }
1087
1088 if ($dry_run) {
1089 $report['create_subscription'] = (!empty($report['create_subscription']) ? (absint($report['create_subscription']) + 1) : 1);
1090 $this->update_report($report);
1091 return true;
1092 }
1093
1094 $subscription = \Give\Subscriptions\Models\Subscription::create($attributes);
1095
1096 if ($subscription && $subscription->id) {
1097 try {
1098 $donorModel = null;
1099 try {
1100 $donorModel = \Give\Donors\Models\Donor::find($subscription->donorId);
1101 } catch (\Throwable $e) {
1102 $donorModel = null;
1103 }
1104
1105 $donorEmail = ($donorModel && isset($donorModel->email)) ? (string)$donorModel->email : '';
1106 $donorName = ($donorModel && isset($donorModel->name)) ? (string)$donorModel->name : '';
1107 $firstName = '';
1108 $lastName = '';
1109 if ($donorName) {
1110 $parts = preg_split('/\s+/', trim($donorName));
1111 if ($parts) {
1112 $firstName = (string)array_shift($parts);
1113 $lastName = (string)trim(implode(' ', $parts));
1114 }
1115 }
1116
1117 $donationAttributes = [
1118 'subscriptionId' => $subscription->id,
1119 'gatewayId' => !empty($attributes['gatewayId']) ? $attributes['gatewayId'] : 'manual',
1120 'amount' => $subscription->amount,
1121 'status' => \Give\Donations\ValueObjects\DonationStatus::COMPLETE(),
1122 'type' => \Give\Donations\ValueObjects\DonationType::SUBSCRIPTION(),
1123 'donorId' => $subscription->donorId,
1124 'formId' => $subscription->donationFormId,
1125 'feeAmountRecovered' => $subscription->feeAmountRecovered,
1126 'mode' => $subscription->mode->isLive() ? \Give\Donations\ValueObjects\DonationMode::LIVE() : \Give\Donations\ValueObjects\DonationMode::TEST(),
1127 'firstName' => $firstName,
1128 'lastName' => $lastName,
1129 'email' => $donorEmail,
1130 ];
1131
1132 if (!empty($data['first_name'])) {
1133 $donationAttributes['firstName'] = (string)$data['first_name'];
1134 }
1135 if (!empty($data['last_name'])) {
1136 $donationAttributes['lastName'] = (string)$data['last_name'];
1137 }
1138 if (!empty($data['email'])) {
1139 $donationAttributes['email'] = (string)$data['email'];
1140 }
1141
1142 if (!empty($attributes['transactionId'])) {
1143 $donationAttributes['gatewayTransactionId'] = (string)$attributes['transactionId'];
1144 }
1145 if (!empty($subscription->createdAt)) {
1146 $donationAttributes['createdAt'] = $subscription->createdAt;
1147 }
1148
1149 $initialDonation = \Give\Donations\Models\Donation::create($donationAttributes);
1150
1151 if ($initialDonation && $initialDonation->id) {
1152 give()->subscriptions->updateLegacyParentPaymentId($subscription->id, $initialDonation->id);
1153 $this->update_legacy_after_initial_donation($initialDonation);
1154 }
1155 } catch (\Throwable $e) {
1156 $report['failed_subscription_initial_donation'] = (!empty($report['failed_subscription_initial_donation']) ? (absint($report['failed_subscription_initial_donation']) + 1) : 1);
1157 $report['errors'][] = sprintf(__('Row %1$d: Initial donation creation failed (%2$s)', 'give'), (int)(isset($import_setting['row_key']) ? $import_setting['row_key'] : 0), $e->getMessage());
1158 }
1159 $report['create_subscription'] = (!empty($report['create_subscription']) ? (absint($report['create_subscription']) + 1) : 1);
1160 $this->update_report($report);
1161 return (int)$subscription->id;
1162 }
1163
1164 $report['failed_subscription'] = (!empty($report['failed_subscription']) ? (absint($report['failed_subscription']) + 1) : 1);
1165 $this->update_report($report);
1166 return false;
1167 } catch (\Throwable $e) {
1168 $report['failed_subscription'] = (!empty($report['failed_subscription']) ? (absint($report['failed_subscription']) + 1) : 1);
1169 $report['errors'][] = sprintf(__('Row %1$d: %2$s', 'give'), (int)(isset($import_setting['row_key']) ? $import_setting['row_key'] : 0), $e->getMessage());
1170 $this->update_report($report);
1171 return $e->getMessage();
1172 }
1173 }
1174
1175 /**
1176 * Get current import report
1177 * @since 4.11.0
1178 */
1179 public function get_report()
1180 {
1181 return get_option('give_import_subscription_report', []);
1182 }
1183
1184 /**
1185 * Update import report
1186 * @since 4.11.0
1187 */
1188 private function update_report($value = [])
1189 {
1190 update_option('give_import_subscription_report', $value, false);
1191 }
1192
1193 /**
1194 * Reset import report
1195 * @since 4.11.0
1196 */
1197 public function reset_report()
1198 {
1199 update_option('give_import_subscription_report', [], false);
1200 }
1201
1202 /**
1203 * Update legacy donor totals and fee meta for newly created initial donation
1204 * @since 4.11.0
1205 */
1206 private function update_legacy_after_initial_donation(Donation $donation)
1207 {
1208 try {
1209 $donor = $donation->donor;
1210 if ($donor && isset($donor->id)) {
1211 give()->donors->updateLegacyColumns(
1212 $donor->id,
1213 [
1214 'purchase_value' => $this->get_donor_total_intended_amount((int)$donor->id),
1215 'purchase_count' => $donor->totalDonations(),
1216 ]
1217 );
1218 }
1219 if (null !== $donation->feeAmountRecovered) {
1220 give()->payment_meta->update_meta(
1221 $donation->id,
1222 '_give_fee_donation_amount',
1223 give_sanitize_amount_for_db(
1224 $donation->intendedAmount()->formatToDecimal(),
1225 ['currency' => $donation->amount->getCurrency()]
1226 )
1227 );
1228 }
1229 } catch (\Throwable $e) {
1230 }
1231 }
1232
1233 /**
1234 * Calculate donor total intended amount across donations
1235 * @since 4.11.0
1236 */
1237 private function get_donor_total_intended_amount($donorId)
1238 {
1239 return (float)DB::table('posts', 'posts')
1240 ->join(function ($join) {
1241 $join->leftJoin('give_donationmeta', 'donor_meta')
1242 ->on('posts.ID', 'donor_meta.donation_id')
1243 ->andOn('donor_meta.meta_key', DonationMetaKeys::DONOR_ID, true);
1244 })
1245 ->join(function ($join) {
1246 $join->leftJoin('give_donationmeta', 'amount_meta')
1247 ->on('posts.ID', 'amount_meta.donation_id')
1248 ->andOn('amount_meta.meta_key', DonationMetaKeys::AMOUNT, true);
1249 })
1250 ->join(function ($join) {
1251 $join->leftJoin('give_donationmeta', 'fee_meta')
1252 ->on('posts.ID', 'fee_meta.donation_id')
1253 ->andOn('fee_meta.meta_key', DonationMetaKeys::FEE_AMOUNT_RECOVERED, true);
1254 })
1255 ->where('posts.post_type', 'give_payment')
1256 ->where('donor_meta.meta_value', (int)$donorId)
1257 ->whereIn('posts.post_status', ['publish', 'give_subscription'])
1258 ->sum('IFNULL(amount_meta.meta_value, 0) - IFNULL(fee_meta.meta_value, 0)');
1259 }
1260
1261 /**
1262 * Subscription mapping options for CSV column selection
1263 * @since 4.11.0
1264 */
1265 public function get_subscription_options()
1266 {
1267 return (array)apply_filters(
1268 'give_import_subscription_options',
1269 [
1270 'form_id' => [__('Donation Form ID', 'give'), __('Form ID', 'give')],
1271 'donor_id' => [__('Donor ID', 'give')],
1272 'first_name' => [__('Donor First Name', 'give'), __('First Name', 'give')],
1273 'last_name' => [__('Donor Last Name', 'give'), __('Last Name', 'give')],
1274 'email' => [__('Donor Email', 'give'), __('Email', 'give')],
1275 'period' => [__('Period', 'give'), __('Subscription Period', 'give')],
1276 'frequency' => [__('Frequency', 'give')],
1277 'installments' => [__('Installments', 'give')],
1278 'amount' => [__('Amount', 'give'), __('Recurring Amount', 'give')],
1279 'fee_amount_recovered' => [__('Recovered Fee Amount', 'give')],
1280 'status' => [__('Status', 'give')],
1281 'mode' => [__('Mode', 'give'), __('Payment Mode', 'give')],
1282 'transaction_id' => [__('Transaction ID', 'give')],
1283 'gateway_id' => [__('Gateway ID', 'give'), __('Gateway', 'give')],
1284 'gateway_subscription_id' => [__('Gateway Subscription ID', 'give')],
1285 'created_at' => [__('Created At', 'give'), __('Start Date', 'give')],
1286 'renews_at' => [__('Renews At', 'give'), __('Next Renewal Date', 'give')],
1287 'currency' => [__('Currency', 'give')],
1288 ]
1289 );
1290 }
1291 }
1292
1293 Give_Import_Subscriptions::get_instance()->setup();
1294 }
1295