PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / trunk
GiveWP – Donation Plugin and Fundraising Platform vtrunk
4.16.3 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 / src / Campaigns / Migrations / MigrateFormsToCampaignForms.php
give / src / Campaigns / Migrations Last commit date
Donations 11 months ago P2P 1 year ago RevenueTable 1 year ago Tables 1 year ago CacheCampaignsData.php 8 months ago MigrateFormsToCampaignForms.php 1 year ago
MigrateFormsToCampaignForms.php
386 lines
1 <?php
2
3 namespace Give\Campaigns\Migrations;
4
5 use Give\Campaigns\ValueObjects\CampaignType;
6 use Give\Framework\Database\DB;
7 use Give\Framework\Database\Exceptions\DatabaseQueryException;
8 use Give\Framework\Migrations\Contracts\Migration;
9 use Give\Framework\Migrations\Contracts\ReversibleMigration;
10 use Give\Framework\Migrations\Exceptions\DatabaseMigrationException;
11 use Give\Framework\QueryBuilder\JoinQueryBuilder;
12 use Give\Framework\QueryBuilder\QueryBuilder;
13 use stdClass;
14
15 /**
16 * @since 4.0.0
17 */
18 class MigrateFormsToCampaignForms extends Migration implements ReversibleMigration
19 {
20 /**
21 * @inheritDoc
22 */
23 public static function id(): string
24 {
25 return 'migrate_forms_to_campaign_forms';
26 }
27
28 /**
29 * @inheritDoc
30 */
31 public static function title(): string
32 {
33 return 'Migrate Forms to Campaigns';
34 }
35
36 /**
37 * @inheritDoc
38 */
39 public static function timestamp(): int
40 {
41 return strtotime('2024-08-26 00:00:02');
42 }
43
44 /**
45 * @since 4.0.0
46 * @inheritDoc
47 * @throws \Exception
48 */
49 public function run()
50 {
51 DB::transaction(function() {
52 try {
53 array_map([$this, 'createCampaignForForm'], $this->getAllFormsData());
54 array_map([$this, 'addUpgradedV2FormToCampaign'], $this->getUpgradedV2FormsData());
55 } catch (DatabaseQueryException $exception) {
56 DB::rollback();
57 throw new DatabaseMigrationException('An error occurred while creating initial campaigns', 0, $exception);
58 }
59 });
60 }
61
62 /**
63 * @inheritDoc
64 */
65 public function reverse(): void
66 {
67 // Delete core campaigns
68 DB::table('give_campaigns')
69 ->where('campaign_type', CampaignType::CORE)
70 ->delete();
71
72 // Truncate form relationships
73 DB::table('give_campaign_forms')->truncate();
74 }
75
76 /**
77 * @since 4.0.0
78 */
79 protected function getAllFormsData(): array
80 {
81 $query = DB::table('posts', 'forms')->distinct()
82 ->select(
83 ['forms.ID', 'id'],
84 ['forms.post_title', 'title'],
85 ['forms.post_name', 'name'], // unique slug
86 ['forms.post_status', 'status'],
87 ['forms.post_date', 'createdAt']
88 )
89 ->where('forms.post_type', 'give_forms');
90
91 $query->select(['formmeta.meta_value', 'settings'])
92 ->join(function (JoinQueryBuilder $builder) {
93 $builder
94 ->leftJoin('give_formmeta', 'formmeta')
95 ->on('formmeta.form_id', 'forms.ID')->joinRaw("AND formmeta.meta_key = 'formBuilderSettings'");
96 });
97
98 // Exclude forms already associated with a campaign (ie Peer-to-peer).
99 $query->join(function (JoinQueryBuilder $builder) {
100 $builder
101 ->leftJoin('give_campaigns', 'campaigns')
102 ->on('campaigns.form_id', 'forms.ID');
103 })
104 ->whereIsNull('campaigns.id');
105
106 /**
107 * Exclude forms with an "auto-draft" status, which are WP revisions.
108 *
109 * @see https://wordpress.org/documentation/article/post-status/#auto-draft
110 */
111 $query->where('forms.post_status', 'auto-draft', '!=');
112
113 /**
114 * Excluded upgraded V2 forms as their corresponding V3 version will be used to create the campaign - later the V2 form will be added to the proper campaign as a non-default form through the addUpgradedV2FormToCampaign() method.
115 */
116 $query->whereNotIn('forms.ID', function (QueryBuilder $builder) {
117 $builder
118 ->select('meta_value')
119 ->from('give_formmeta')
120 ->where('meta_key', 'migratedFormId');
121 });
122
123 // Ensure campaigns will be displayed in the same order on the list table
124 $query->orderBy('forms.ID');
125
126 $results = $query->getAll();
127
128 if (!$results) {
129 return [];
130 }
131
132 return $results;
133 }
134
135 /**
136 * @since 4.3.0 Add DISTINCT, whereNotIn and new JOIN to retrieve only the migratedFormId associated with the highest form_id
137 * @since 4.0.0
138 * @return array [{formId, campaignId, migratedFormId}]
139 */
140 protected function getUpgradedV2FormsData(): array
141 {
142 $query = DB::table('posts', 'forms')->distinct()
143 ->select(['forms.ID', 'formId'], ['campaign_forms.campaign_id', 'campaignId'],
144 ['give_formmeta.migratedFormId', 'migratedFormId'])
145 ->join(function (JoinQueryBuilder $builder) {
146 $builder
147 ->rightJoin('give_campaign_forms', 'campaign_forms')
148 ->on('campaign_forms.form_id', 'forms.ID');
149 })
150 ->where('forms.post_type', 'give_forms');
151
152 /**
153 * Sometimes the user starts upgrading a form but gives up and puts the migrated form in the trash. However,
154 * the migratedFormId keeps on DB, which can make this query return the same migratedFormId for multiple
155 * campaigns, so we need to use this join statement to ensure we are NOT adding the same migratedFormId
156 * for multiple campaigns, since it forces the query to retrieve only the migratedFormId associated with
157 * the highest form_id which is the last upgrade attempt.
158 *
159 * @see https://github.com/impress-org/givewp/pull/7901#issuecomment-2854905488
160 */
161 $table = DB::prefix('give_formmeta');
162 $query->joinRaw("INNER JOIN (
163 SELECT
164 meta_value AS migratedFormId,
165 MAX(form_id) AS max_form_id
166 FROM {$table}
167 WHERE meta_key = 'migratedFormId'
168 GROUP BY meta_value
169 ) AS give_formmeta ON forms.ID = give_formmeta.max_form_id");
170
171 /**
172 * When someone re-runs the migration, it can return a duplicated entry error if the upgraded forms were
173 * already added previously in the first time the migration was run, so this whereNotIn prevents these
174 * errors by excluding upgraded forms already added to the give_campaign_forms table previously.
175 *
176 * @see https://github.com/impress-org/givewp/pull/7901#discussion_r2073600045
177 */
178 $query->whereNotIn('give_formmeta.migratedFormId', function (QueryBuilder $builder) {
179 $builder
180 ->select('form_id')
181 ->from('give_campaign_forms')
182 ->whereRaw('WHERE form_id = give_formmeta.migratedFormId');
183 });
184
185
186 $results = $query->getAll();
187
188 if (!$results) {
189 return [];
190 }
191
192 return $results;
193 }
194
195 /**
196 * @since 4.0.0
197 */
198 public function createCampaignForForm($formData): void
199 {
200 $formId = $formData->id;
201 $formStatus = $formData->status;
202 $formName = $formData->name;
203 $formTitle = $formData->title;
204 $formCreatedAt = $formData->createdAt;
205 $isV3Form = ! is_null($formData->settings);
206 $formSettings = $isV3Form ? json_decode($formData->settings) : $this->getV2FormSettings($formId);
207
208 DB::table('give_campaigns')
209 ->insert([
210 'form_id' => $formId,
211 'campaign_type' => 'core',
212 'campaign_title' => $formTitle,
213 'status' => $this->mapFormToCampaignStatus($formStatus),
214 'short_desc' => $formSettings->formExcerpt,
215 'long_desc' => $formSettings->description,
216 'campaign_logo' => $formSettings->designSettingsLogoUrl,
217 'campaign_image' => $formSettings->designSettingsImageUrl,
218 'primary_color' => $formSettings->primaryColor,
219 'secondary_color' => $formSettings->secondaryColor,
220 'campaign_goal' => $formSettings->goalAmount,
221 'goal_type' => $formSettings->goalType,
222 'start_date' => $formCreatedAt,
223 'end_date' => null,
224 'date_created' => $formCreatedAt,
225 ]);
226
227 $campaignId = DB::last_insert_id();
228
229 $this->addCampaignFormRelationship($formId, $campaignId);
230 }
231
232 /**
233 * @param $data
234 */
235 protected function addUpgradedV2FormToCampaign($data): void
236 {
237 $this->addCampaignFormRelationship($data->migratedFormId, $data->campaignId);
238 }
239
240 /**
241 * @since 4.0.0
242 */
243 protected function addCampaignFormRelationship($formId, $campaignId)
244 {
245 DB::table('give_campaign_forms')
246 ->insert([
247 'form_id' => $formId,
248 'campaign_id' => $campaignId,
249 ]);
250 }
251
252 /**
253 * @since 4.0.0
254 */
255 protected function mapFormToCampaignStatus(string $status): string
256 {
257 switch ($status) {
258
259 case 'pending':
260 return 'pending';
261
262 case 'draft':
263 case 'upgraded': // Some V3 forms can have the 'upgraded' status after being migrated from a V2 form
264 return 'draft';
265
266 case 'trash':
267 return 'archived';
268
269 case 'publish':
270 case 'private':
271 return 'active';
272
273 default: // TODO: How do we handle an unknown form status?
274 return 'inactive';
275 }
276 }
277
278 /**
279 * @since 4.0.0
280 */
281 protected function getV2FormSettings(int $formId): stdClass
282 {
283 $template = give_get_meta($formId, '_give_form_template', true);
284 $templateSettings = give_get_meta($formId, "_give_{$template}_form_template_settings", true);
285 $templateSettings = is_array($templateSettings) ? $templateSettings : [];
286
287 return (object)[
288 'formExcerpt' => get_the_excerpt($formId),
289 'description' => $this->getV2FormDescription($templateSettings),
290 'designSettingsLogoUrl' => '',
291 'designSettingsImageUrl' => $this->getV2FormFeaturedImage($templateSettings, $formId),
292 'primaryColor' => $this->getV2FormPrimaryColor($templateSettings),
293 'secondaryColor' => '',
294 'goalAmount' => $this->getV2FormGoalAmount($formId),
295 'goalType' => $this->getV2FormGoalType($formId),
296 'goalStartDate' => '',
297 'goalEndDate' => '',
298 ];
299 }
300
301 /**
302 * @since 4.0.0
303 */
304 protected function getV2FormFeaturedImage(array $templateSettings, int $formId): string
305 {
306 if ( ! empty($templateSettings['introduction']['image'])) {
307 // Sequoia Template (Multi-Step)
308 $featuredImage = $templateSettings['introduction']['image'];
309 } elseif ( ! empty($templateSettings['visual_appearance']['header_background_image'])) {
310 // Classic Template - it doesn't use the featured image from the WP default setting as a fallback
311 $featuredImage = $templateSettings['visual_appearance']['header_background_image'];
312 } elseif ( ! isset($templateSettings['visual_appearance']['header_background_image'])) {
313 // Legacy Template or Sequoia Template without the ['introduction']['image'] setting
314 $featuredImage = get_the_post_thumbnail_url($formId, 'full');
315 } else {
316 $featuredImage = '';
317 }
318
319 return $featuredImage;
320 }
321
322 /**
323 * @since 4.0.0
324 */
325 protected function getV2FormDescription(array $templateSettings): string
326 {
327 if ( ! empty($templateSettings['introduction']['description'])) {
328 // Sequoia Template (Multi-Step)
329 $description = $templateSettings['introduction']['description'];
330 } elseif ( ! empty($templateSettings['visual_appearance']['description'])) {
331 // Classic Template
332 $description = $templateSettings['visual_appearance']['description'];
333 } else {
334 $description = '';
335 }
336
337 return $description;
338 }
339
340 /**
341 * @since 4.0.0
342 */
343 protected function getV2FormPrimaryColor(array $templateSettings): string
344 {
345 if ( ! empty($templateSettings['introduction']['primary_color'])) {
346 // Sequoia Template (Multi-Step)
347 $primaryColor = $templateSettings['introduction']['primary_color'];
348 } elseif ( ! empty($templateSettings['visual_appearance']['primary_color'])) {
349 // Classic Template
350 $primaryColor = $templateSettings['visual_appearance']['primary_color'];
351 } else {
352 $primaryColor = '';
353 }
354
355 return $primaryColor;
356 }
357
358 /**
359 * @since 4.0.0
360 */
361 protected function getV2FormGoalAmount(int $formId)
362 {
363 return give_get_form_goal($formId);
364 }
365
366 /**
367 * @since 4.0.0
368 */
369 protected function getV2FormGoalType(int $formId): string
370 {
371 $onlyRecurringEnabled = filter_var(give_get_meta($formId, '_give_recurring_goal_format', true),
372 FILTER_VALIDATE_BOOLEAN);
373
374 switch (give_get_form_goal_format($formId)) {
375 case 'donors':
376 return $onlyRecurringEnabled ? 'donorsFromSubscriptions' : 'donors';
377 case 'donation':
378 return $onlyRecurringEnabled ? 'subscriptions' : 'donations';
379 case 'amount':
380 case 'percentage':
381 default:
382 return $onlyRecurringEnabled ? 'amountFromSubscriptions' : 'amount';
383 }
384 }
385 }
386