PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.15.2
GiveWP – Donation Plugin and Fundraising Platform v4.15.2
4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 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 All 253 releases
give / src / DonationForms / ViewModels / DonationFormViewModel.php
DonationFormViewModel.php
520 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\DonationForms\ViewModels;
4
5 use Give\Campaigns\Models\Campaign;
6 use Give\Campaigns\ValueObjects\CampaignGoalType;
7 use Give\DonationForms\Actions\GenerateAuthUrl;
8 use Give\DonationForms\Actions\GenerateDonateRouteUrl;
9 use Give\DonationForms\Actions\GenerateDonationFormValidationRouteUrl;
10 use Give\DonationForms\DataTransferObjects\DonationFormGoalData;
11 use Give\DonationForms\Properties\FormSettings;
12 use Give\DonationForms\Repositories\DonationFormRepository;
13 use Give\DonationForms\ValueObjects\GoalType;
14 use Give\Framework\Blocks\BlockCollection;
15 use Give\Framework\DesignSystem\Actions\RegisterDesignSystemStyles;
16 use Give\Framework\FormDesigns\FormDesign;
17 use Give\Framework\FormDesigns\Registrars\FormDesignRegistrar;
18 use Give\Framework\Support\Scripts\Concerns\HasScriptAssetFile;
19 use Give\Helpers\Hooks;
20 use Give\Helpers\Language;
21
22 /**
23 * @since 3.0.0
24 */
25 class DonationFormViewModel
26 {
27 use HasScriptAssetFile;
28
29 /**
30 * @var int
31 */
32 private $donationFormId;
33 /**
34 * @var BlockCollection
35 */
36 private $formBlocks;
37 /**
38 *
39 * @var FormSettings
40 */
41 private $formSettings;
42 /**
43 * @var DonationFormRepository
44 */
45 private $donationFormRepository;
46 /**
47 * @var bool
48 */
49 private $previewMode;
50 /**
51 * @since 4.1.0
52 */
53 private DonationFormGoalData $donationFormGoalData;
54
55 /**
56 * @since 3.0.0
57 */
58 public function __construct(
59 int $donationFormId,
60 BlockCollection $formBlocks,
61 FormSettings $formSettings,
62 bool $previewMode = false
63 ) {
64 $this->donationFormId = $donationFormId;
65 $this->formBlocks = $formBlocks;
66 $this->formSettings = $formSettings;
67 $this->donationFormRepository = give(DonationFormRepository::class);
68 $this->previewMode = $previewMode;
69 $this->donationFormGoalData = new DonationFormGoalData($donationFormId, $formSettings);
70 }
71
72 /**
73 * @since 3.0.0
74 */
75 public function designId(): string
76 {
77 return $this->formSettings->designId ?? '';
78 }
79
80 /**
81 * @since 4.14.6 Sanitize output; campaign colors bypass FormSettings::fromArray.
82 * @since 4.1.0 Add support for campaign colors
83 * @since 3.0.0
84 */
85 public function primaryColor(): string
86 {
87 $color = $this->formSettings->primaryColor ?? '';
88
89 if ($this->formSettings->inheritCampaignColors) {
90 $campaignColors = $this->getCampaignColors($this->donationFormId);
91
92 if ($campaignColors['primaryColor']) {
93 $color = $campaignColors['primaryColor'];
94 }
95 }
96
97 return sanitize_hex_color($color) ?? '';
98 }
99
100 /**
101 * @since 4.14.6 Sanitize output; campaign colors bypass FormSettings::fromArray.
102 * @since 4.1.0 Add support for campaign colors
103 * @since 3.0.0
104 */
105 public function secondaryColor(): string
106 {
107 $color = $this->formSettings->secondaryColor ?? '';
108
109 if ($this->formSettings->inheritCampaignColors) {
110 $campaignColors = $this->getCampaignColors($this->donationFormId);
111
112 if ($campaignColors['secondaryColor']) {
113 $color = $campaignColors['secondaryColor'];
114 }
115 }
116
117 return sanitize_hex_color($color) ?? '';
118 }
119
120 /**
121 * @since 4.1.0 Added custom form styles
122 * @since 3.0.0
123 */
124 public function enqueueGlobalStyles()
125 {
126 (new RegisterDesignSystemStyles())();
127 wp_enqueue_style('givewp-design-system-foundation');
128
129 wp_register_style(
130 'givewp-base-form-styles',
131 GIVE_PLUGIN_URL . 'build/baseFormDesignCss.css'
132 );
133
134 wp_add_inline_style(
135 'givewp-base-form-styles',
136 ":root {
137 --givewp-primary-color:{$this->primaryColor()};
138 --givewp-secondary-color:{$this->secondaryColor()};
139 }"
140 );
141
142 wp_add_inline_style(
143 'givewp-base-form-styles',
144 wp_strip_all_tags(give_get_option('custom_form_styles', ''))
145 );
146
147 wp_enqueue_style('givewp-base-form-styles');
148 }
149
150 /**
151 * @since 3.0.0
152 *
153 * @return GoalType|CampaignGoalType
154 */
155 private function goalType()
156 {
157 if ($this->formSettings->goalSource->isCampaign()) {
158 $campaign = Campaign::findByFormId($this->donationFormId);
159
160 if ($campaign) {
161 return $campaign->goalType;
162 }
163 }
164
165 return $this->formSettings->goalType;
166 }
167
168 /**
169 * @since 4.2.0 always return count value
170 * @since 4.1.0 use DonationFormGoalData
171 * @since 3.0.0
172 */
173 private function getTotalCountValue(): ?int
174 {
175 switch ($this->goalType()->getValue()):
176 case 'donors':
177 case 'donorsFromSubscriptions':
178 return $this->donationFormGoalData->getQuery()->countDonors();
179 case 'donations':
180 return $this->formSettings->goalSource->isCampaign()
181 ? $this->donationFormGoalData->getQuery()->countDonations()
182 : $this->donationFormGoalData->getQuery()->count();
183 default:
184 return $this->donationFormGoalData->getQuery()->count();
185 endswitch;
186 }
187
188 /**
189 * @since 3.0.0
190 */
191 public function getCountLabel(): ?string
192 {
193 if ($this->goalType()->isDonors() || $this->goalType()->isDonorsFromSubscriptions()) {
194 return __('Donors', 'give');
195 }
196
197 if ($this->goalType()->isDonations() || $this->goalType()->isAmount()) {
198 return __('Donations', 'give');
199 }
200
201 if ($this->goalType()->isSubscriptions() || $this->goalType()->isAmountFromSubscriptions()) {
202 return __('Recurring Donations', 'give');
203 }
204
205 return __('Counted', 'give');
206 }
207
208 /**
209 * @since 4.3.0 ensure totalRevenue always reflects revenue
210 * @since 4.1.0 use DonationFormGoalData instead of repository
211 * @since 3.0.0
212 */
213 private function formStatsData(): array
214 {
215 $query = $this->donationFormGoalData->getQuery();
216
217 // Only form goal has range
218 if ($this->formSettings->goalSource->isForm() && $this->formSettings->goalProgressType->isCustom()) {
219 $query->between($this->formSettings->goalStartDate, $this->formSettings->goalEndDate);
220 }
221
222 return [
223 'totalRevenue' => $this->donationFormGoalData->getTotalDonationRevenue(),
224 'totalCountValue' => $this->goalType()->isDonations() || $this->goalType()->isAmount()
225 ? $query->count()
226 : $this->getTotalCountValue(),
227 'totalCountLabel' => $this->getCountLabel(),
228 ];
229 }
230
231 /**
232 * @since 3.6.0 added includeHeaderInMultiStep to form design export
233 * @since 3.0.0
234 */
235 public function exports(): array
236 {
237 $donateUrl = (new GenerateDonateRouteUrl())();
238 $validateUrl = (new GenerateDonationFormValidationRouteUrl())();
239 $authUrl = (new GenerateAuthUrl())();
240
241 $formDataGateways = $this->donationFormRepository->getFormDataGateways($this->donationFormId);
242 $formApi = $this->donationFormRepository->getFormSchemaFromBlocks(
243 $this->donationFormId,
244 $this->formBlocks
245 );
246
247 $formDesign = $this->getFormDesign($this->designId());
248
249 return [
250 'donateUrl' => $donateUrl,
251 'validateUrl' => $validateUrl,
252 'authUrl' => $authUrl,
253 'inlineRedirectRoutes' => [
254 'donation-confirmation-receipt-view',
255 ],
256 'registeredGateways' => $formDataGateways,
257 'form' => array_merge($formApi->jsonSerialize(), [
258 'settings' => $this->formSettings,
259 'currency' => $formApi->getDefaultCurrency(),
260 'goal' => $this->previewMode || ($this->formSettings->showHeader && $this->formSettings->enableDonationGoal)
261 ? $this->donationFormGoalData->toArray()
262 : [],
263 'stats' => $this->previewMode || ($this->formSettings->showHeader && $this->formSettings->enableDonationGoal)
264 ? $this->formStatsData()
265 : [],
266 'design' => $formDesign ? [
267 'id' => $formDesign::id(),
268 'name' => $formDesign::name(),
269 'isMultiStep' => $formDesign->isMultiStep(),
270 'includeHeaderInMultiStep' => $formDesign->shouldIncludeHeaderInMultiStep(),
271 ] : null,
272 ]),
273 'previewMode' => $this->previewMode,
274 ];
275 }
276
277 /**
278 * This is the order of loading:
279 * 1. Enqueue global styles from WP.
280 * - This ensures template compatability with global WP css variables as needed. Loads before our templates, so they can use things like global font-family, etc.
281 * 2. Enqueue our donation form specific scripts & styles.
282 * - We will let WP handle the actual printing depending on how they were enqueued.
283 * 3. Call the specific WP functions wp_print_styles() and wp_print_head_scripts()
284 * - This will only print the styles and scripts that are enqueued within our route - so we don't have to dequeue a bunch of stuff.
285 * 4. Manually echo our window data and root div for our React app to consume
286 * 5. Finally, call the specific WP function wp_print_footer_scripts()
287 * - This will only print the footer scripts that are enqueued within our route.
288 *
289 * @since 4.14.3 Escape HTML attributes for classNames property
290 * @since 3.20.0 Adds class for form design
291 * @since 3.11.0 Sanitize customCSS property
292 * @since 3.0.0
293 */
294 public function render(): string
295 {
296 $this->enqueueGlobalStyles();
297
298 $this->enqueueFormScripts(
299 $this->donationFormId,
300 $this->designId()
301 );
302
303 ob_start();
304 wp_print_styles();
305 wp_print_head_scripts();
306 ?>
307
308 <?php
309 if ($this->previewMode || $this->formSettings->customCss): ?>
310 <style id="root-givewp-donation-form-style"><?php
311 echo wp_strip_all_tags($this->formSettings->customCss); ?></style>
312 <?php
313 endif; ?>
314
315 <?php
316 $classNames = ['givewp-donation-form', "givewp-donation-form-design--{$this->designId()}"];
317
318 if ($this->previewMode) {
319 $classNames[] = 'givewp-donation-form--preview';
320 }
321 ?>
322
323 <div data-theme="light" id="root-givewp-donation-form"
324 data-iframe-height
325 class="<?= esc_attr(implode(' ', $classNames)) ?>"></div>
326
327 <?php
328 wp_print_footer_scripts();
329
330 echo ob_get_clean();
331
332 exit();
333 }
334
335 /**
336 * Loads scripts in order: [Registrars, Designs, Gateways, Block]
337 *
338 * @since 3.0.0
339 *
340 * @return void
341 */
342 private function enqueueFormScripts(int $formId, string $formDesignId)
343 {
344 $this->enqueueRegistrars();
345 $this->enqueueDesign($formDesignId);
346 $this->enqueueGateways($formId);
347 $this->enqueueFormApp();
348 }
349
350 /**
351 * @since 3.0.0
352 *
353 * @return FormDesign|null
354 */
355 protected function getFormDesign(string $designId)
356 {
357 /** @var FormDesignRegistrar $formDesignRegistrar */
358 $formDesignRegistrar = give(FormDesignRegistrar::class);
359
360 return $formDesignRegistrar->hasDesign($this->designId()) ? $formDesignRegistrar->getDesign($designId) : null;
361 }
362
363 /**
364 * @since 3.0.0
365 */
366 private function enqueueRegistrars()
367 {
368 wp_enqueue_style(
369 'givewp-donation-form-registrars',
370 GIVE_PLUGIN_URL . 'build/donationFormRegistrars.css',
371 [],
372 GIVE_VERSION
373 );
374
375 $handle = 'givewp-donation-form-registrars';
376 wp_enqueue_script(
377 $handle,
378 GIVE_PLUGIN_URL . 'build/donationFormRegistrars.js',
379 $this->getScriptAssetDependencies(GIVE_PLUGIN_DIR . 'build/donationFormRegistrars.asset.php'),
380 GIVE_VERSION,
381 true
382 );
383
384 Language::setScriptTranslations($handle);
385
386 wp_add_inline_script(
387 'givewp-donation-form-registrars',
388 'window.givewpDonationFormExports = ' . wp_json_encode($this->exports()) . ';',
389 'before'
390 );
391
392 Hooks::doAction('givewp_donation_form_enqueue_scripts');
393 }
394
395 /**
396 * @since 3.0.0
397 */
398 private function enqueueGateways(int $formId)
399 {
400 /** @var DonationFormRepository $donationFormRepository */
401 $donationFormRepository = give(DonationFormRepository::class);
402
403 // load gateway scripts
404 foreach ($donationFormRepository->getEnabledPaymentGateways($formId) as $gateway) {
405 if (method_exists($gateway, 'enqueueScript')) {
406 $gateway->enqueueScript($formId);
407 }
408 }
409 }
410
411 /**
412 * @since 3.0.0 Set script translations
413 * @since 3.0.0
414 */
415 private function enqueueDesign(string $formDesignId)
416 {
417 $design = $this->getFormDesign($formDesignId);
418
419 // silently fail if design is missing for some reason
420 if ($design) {
421 if ($design->css()) {
422 wp_enqueue_style('givewp-form-design-' . $design::id(), $design->css());
423 }
424
425 if ($design->js()) {
426 $handle = 'givewp-form-design-' . $design::id();
427 wp_enqueue_script(
428 $handle,
429 $design->js(),
430 array_merge(
431 $design->dependencies(),
432 ['givewp-donation-form-registrars']
433 ),
434 true
435 );
436
437 Language::setScriptTranslations($handle);
438 }
439 }
440 }
441
442 /**
443 * @since 3.0.0
444 */
445 private function enqueueFormApp()
446 {
447 // load block - since this is using render_callback viewScript in blocks.json will not work.
448 $handle = 'givewp-donation-form-app';
449 wp_enqueue_script(
450 $handle,
451 GIVE_PLUGIN_URL . 'build/donationFormApp.js',
452 array_merge(
453 $this->getScriptAssetDependencies(GIVE_PLUGIN_DIR . 'build/donationFormApp.asset.php'),
454 ['givewp-donation-form-registrars']
455 ),
456 GIVE_VERSION,
457 true
458 );
459
460 Language::setScriptTranslations($handle);
461
462 /**
463 * Load iframeResizer.contentWindow.min.js inside iframe
464 *
465 * @see https://github.com/davidjbradshaw/iframe-resizer
466 */
467 $handle = 'givewp-donation-form-embed-inside';
468 wp_enqueue_script(
469 $handle,
470 GIVE_PLUGIN_URL . 'build/donationFormEmbedInside.js',
471 [],
472 GIVE_VERSION,
473 true
474 );
475
476 Language::setScriptTranslations($handle);
477 }
478
479 /**
480 * @since 3.4.0
481 */
482 private function updateDesignSettingsClassNames(array &$classNames)
483 {
484 if ($this->formSettings->designSettingsImageUrl) {
485 $classNames[] = 'givewp-design-settings--image';
486 $classNames[] = 'givewp-design-settings--image-style__' . $this->formSettings->designSettingsImageStyle;
487 }
488
489 if ($this->formSettings->designSettingsLogoUrl) {
490 $classNames[] = 'givewp-design-settings--logo';
491 $classNames[] = 'givewp-design-settings--logo-position__' . $this->formSettings->designSettingsLogoPosition;
492 }
493
494 $classNames[] = 'givewp-design-settings--section-style__' . $this->formSettings->designSettingsSectionStyle;
495
496 $classNames[] = 'givewp-design-settings--textField-style__' . $this->formSettings->designSettingsTextFieldStyle;
497 }
498
499 /**
500 * @since 4.1.0
501 */
502 private function getCampaignColors(int $formId): array
503 {
504 /** @var Campaign $campaign */
505 $campaign = give()->campaigns->getByFormId($formId);
506
507 if ($campaign) {
508 return [
509 'primaryColor' => $campaign->primaryColor,
510 'secondaryColor' => $campaign->secondaryColor,
511 ];
512 }
513
514 return [
515 'primaryColor' => '',
516 'secondaryColor' => '',
517 ];
518 }
519 }
520