PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.0
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
suredonation / inc / import / charitable / session.php

session.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.6.0, at inc/import/charitable/session.php

249 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Migration session store.
4 *
5 * Wraps a transient with typed accessors so the rest of the importer never
6 * touches transient keys directly. Each session is keyed by a UUID that the
7 * REST layer hands back to the client and expects back on subsequent /batch
8 * calls.
9 *
10 * @package SureDonation
11 */
12
13 namespace SureDonation\Inc\Import\Charitable;
14
15 use SureDonation\Inc\Traits\Get_Instance;
16
17 // Exit if accessed directly.
18 defined( 'ABSPATH' ) || exit;
19
20 /**
21 * Session class.
22 *
23 * @phpstan-type PhaseResults array{imported: int, skipped: int, errors: int, error_log: array<int, array{source_id: int, message: string}>, gateway_breakdown?: array<string, int>, imported_live?: int, imported_historical?: int}
24 * @phpstan-type ImportProgress array{import_id: string, started_at: string, started_by: int, options: array{campaign_ids: array<int, int>, include_standalone_donors: bool}, phases: array<int, string>, current_phase: int, offset: int, campaign_map: array<int, int>, donor_map: array<string, int>, status: string, results: array<string, PhaseResults>, error?: string, completed_at?: string}
25 *
26 * @since 1.0.0
27 */
28 class Session {
29 use Get_Instance;
30
31 /**
32 * Transient key prefix.
33 */
34 public const TRANSIENT_PREFIX = 'suredonation_charitable_import_';
35
36 /**
37 * Session TTL.
38 *
39 * Long enough to survive batch gaps and brief page reloads, short enough
40 * that abandoned sessions don't pile up indefinitely.
41 */
42 public const TTL = HOUR_IN_SECONDS;
43
44 /**
45 * Create a new import session and return its progress payload.
46 *
47 * Options shape:
48 * - campaign_ids: int[] — Charitable form IDs selected in the preview UI.
49 * - include_standalone_donors: bool — whether to also import donors
50 * that have no associated payments (Pro phase, off by default).
51 *
52 * Campaigns + donations phases are always registered (the user picked
53 * at least one campaign on the preview step). Subscriptions is injected
54 * by the Pro plugin via the phases filter when recurring_donation plans
55 * exist. Standalone donors is opt-in via the option above.
56 *
57 * @param array<string, mixed> $options Session options.
58 * @return array<string, mixed> Newly created progress payload (including generated import_id) or empty array if no phases are selected.
59 * @since 1.0.0
60 */
61 public function create( $options ) {
62 $options = is_array( $options ) ? $options : [];
63
64 $defaults = [
65 'campaign_ids' => [],
66 'include_standalone_donors' => false,
67 ];
68
69 $options = wp_parse_args( $options, $defaults );
70
71 $options['campaign_ids'] = is_array( $options['campaign_ids'] )
72 ? array_values( array_unique( array_filter( array_map( 'absint', $options['campaign_ids'] ) ) ) )
73 : [];
74 $options['include_standalone_donors'] = (bool) $options['include_standalone_donors'];
75
76 if ( empty( $options['campaign_ids'] ) ) {
77 return [];
78 }
79
80 // Campaigns + donations always run; the user opted in to those by
81 // picking campaigns on the preview step. Pro injects subscriptions
82 // and (conditionally) standalone_donors via the filter below.
83 $phases = [ 'campaigns', 'donations' ];
84
85 /**
86 * Filter the phases that a Charitable migration session will run.
87 *
88 * Pro reads $options['include_standalone_donors'] when deciding
89 * whether to inject the standalone_donors phase. The subscriptions
90 * phase is injected unconditionally when recurring_donation plans
91 * exist — it's still campaign-scoped because the mapper
92 * filters by $progress['options']['campaign_ids'].
93 *
94 * @param array $phases Default phases (campaigns + donations).
95 * @param array $options Resolved options for this session.
96 * @since 1.0.0
97 */
98 $phases = apply_filters( 'suredonation_import_charitable_phases', $phases, $options );
99 $phases = is_array( $phases ) ? array_values( array_unique( array_filter( array_map( 'strval', $phases ) ) ) ) : [];
100
101 if ( empty( $phases ) ) {
102 return [];
103 }
104
105 $progress = [
106 'import_id' => wp_generate_uuid4(),
107 'started_at' => current_time( 'mysql', true ),
108 'started_by' => get_current_user_id(),
109 'options' => $options,
110 'phases' => $phases,
111 'current_phase' => 0,
112 'offset' => 0,
113 'campaign_map' => [],
114 'donor_map' => [],
115 'status' => 'running',
116 'results' => $this->empty_results( $phases ),
117 ];
118
119 $this->put( $progress['import_id'], $progress );
120
121 /**
122 * Fires after a new Charitable migration session has been created.
123 *
124 * @param string $import_id Session UUID.
125 * @param array $progress Full progress payload.
126 * @since 1.0.0
127 */
128 do_action( 'suredonation_import_charitable_session_created', $progress['import_id'], $progress );
129
130 return $progress;
131 }
132
133 /**
134 * Load progress for an existing import session.
135 *
136 * @param string $import_id Session UUID.
137 * @return array<string, mixed>|false Progress payload, or false if the transient is gone.
138 * @since 1.0.0
139 */
140 public function get( $import_id ) {
141 $import_id = $this->sanitize_id( $import_id );
142 if ( '' === $import_id ) {
143 return false;
144 }
145
146 $progress = get_transient( self::TRANSIENT_PREFIX . $import_id );
147 return is_array( $progress ) ? $progress : false;
148 }
149
150 /**
151 * Persist updated progress to its transient.
152 *
153 * @param string $import_id Session UUID.
154 * @param array<string, mixed> $progress Progress payload.
155 * @return bool
156 * @since 1.0.0
157 */
158 public function put( $import_id, $progress ) {
159 $import_id = $this->sanitize_id( $import_id );
160 if ( '' === $import_id || ! is_array( $progress ) ) {
161 return false;
162 }
163
164 return (bool) set_transient( self::TRANSIENT_PREFIX . $import_id, $progress, self::TTL );
165 }
166
167 /**
168 * Delete a session.
169 *
170 * @param string $import_id Session UUID.
171 * @return bool
172 * @since 1.0.0
173 */
174 public function delete( $import_id ) {
175 $import_id = $this->sanitize_id( $import_id );
176 if ( '' === $import_id ) {
177 return false;
178 }
179
180 return (bool) delete_transient( self::TRANSIENT_PREFIX . $import_id );
181 }
182
183 /**
184 * Build a zeroed results structure for the given phases.
185 *
186 * @param array<int, string> $phases Phase names.
187 * @return array<string, array<string, mixed>>
188 * @since 1.0.0
189 */
190 private function empty_results( $phases ) {
191 $schema = [
192 'campaigns' => [
193 'imported' => 0,
194 'skipped' => 0,
195 'errors' => 0,
196 'error_log' => [],
197 ],
198 'donations' => [
199 'imported' => 0,
200 'skipped' => 0,
201 'errors' => 0,
202 'error_log' => [],
203 'gateway_breakdown' => [],
204 ],
205 'subscriptions' => [
206 'imported_live' => 0,
207 'imported_historical' => 0,
208 'skipped' => 0,
209 'errors' => 0,
210 'error_log' => [],
211 ],
212 'standalone_donors' => [
213 'imported' => 0,
214 'skipped' => 0,
215 'errors' => 0,
216 'error_log' => [],
217 ],
218 ];
219
220 $out = [];
221 foreach ( $phases as $phase ) {
222 if ( isset( $schema[ $phase ] ) ) {
223 $out[ $phase ] = $schema[ $phase ];
224 }
225 }
226 return $out;
227 }
228
229 /**
230 * Sanitize an incoming import-id string.
231 *
232 * @param mixed $import_id Raw input.
233 * @return string Empty string if not a plausible UUID.
234 * @since 1.0.0
235 */
236 private function sanitize_id( $import_id ) {
237 if ( ! is_string( $import_id ) ) {
238 return '';
239 }
240 $import_id = sanitize_text_field( $import_id );
241
242 if ( ! preg_match( '/^[a-f0-9-]{8,64}$/i', $import_id ) ) {
243 return '';
244 }
245
246 return $import_id;
247 }
248 }
249