PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.16.8.1
GiveWP – Donation Plugin and Fundraising Platform v4.16.8.1
4.16.9 4.16.8.1 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 All 255 releases
give / src / DonationForms / ViewModels / DonationFormViewModel.php

DonationFormViewModel.php in GiveWP – Donation Plugin and Fundraising Platform 4.16.8.1, at src/DonationForms/ViewModels/DonationFormViewModel.php

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