donationFormId = $donationFormId; $this->formBlocks = $formBlocks; $this->formSettings = $formSettings; $this->donationFormRepository = give(DonationFormRepository::class); $this->previewMode = $previewMode; $this->donationFormGoalData = new DonationFormGoalData($donationFormId, $formSettings); } /** * @since 3.0.0 */ public function designId(): string { return $this->formSettings->designId ?? ''; } /** * @since 4.14.6 Sanitize output; campaign colors bypass FormSettings::fromArray. * @since 4.1.0 Add support for campaign colors * @since 3.0.0 */ public function primaryColor(): string { $color = $this->formSettings->primaryColor ?? ''; if ($this->formSettings->inheritCampaignColors) { $campaignColors = $this->getCampaignColors($this->donationFormId); if ($campaignColors['primaryColor']) { $color = $campaignColors['primaryColor']; } } return sanitize_hex_color($color) ?? ''; } /** * @since 4.14.6 Sanitize output; campaign colors bypass FormSettings::fromArray. * @since 4.1.0 Add support for campaign colors * @since 3.0.0 */ public function secondaryColor(): string { $color = $this->formSettings->secondaryColor ?? ''; if ($this->formSettings->inheritCampaignColors) { $campaignColors = $this->getCampaignColors($this->donationFormId); if ($campaignColors['secondaryColor']) { $color = $campaignColors['secondaryColor']; } } return sanitize_hex_color($color) ?? ''; } /** * @since 4.1.0 Added custom form styles * @since 3.0.0 */ public function enqueueGlobalStyles() { (new RegisterDesignSystemStyles())(); wp_enqueue_style('givewp-design-system-foundation'); wp_register_style( 'givewp-base-form-styles', GIVE_PLUGIN_URL . 'build/baseFormDesignCss.css' ); wp_add_inline_style( 'givewp-base-form-styles', ":root { --givewp-primary-color:{$this->primaryColor()}; --givewp-secondary-color:{$this->secondaryColor()}; }" ); wp_add_inline_style( 'givewp-base-form-styles', wp_strip_all_tags(give_get_option('custom_form_styles', '')) ); wp_enqueue_style('givewp-base-form-styles'); // The skeleton prints in the body before the app bundles load, so its styles go in the head with the rest. if ($this->skeleton) { wp_register_style('givewp-form-skeleton-styles', false); wp_add_inline_style('givewp-form-skeleton-styles', (new RenderFormSkeleton())->css()); wp_enqueue_style('givewp-form-skeleton-styles'); } } /** * @since 3.0.0 * * @return GoalType|CampaignGoalType */ private function goalType() { if ($this->formSettings->goalSource->isCampaign()) { $campaign = Campaign::findByFormId($this->donationFormId); if ($campaign) { return $campaign->goalType; } } return $this->formSettings->goalType; } /** * @since 4.2.0 always return count value * @since 4.1.0 use DonationFormGoalData * @since 3.0.0 */ private function getTotalCountValue(): ?int { switch ($this->goalType()->getValue()): case 'donors': case 'donorsFromSubscriptions': return $this->donationFormGoalData->getQuery()->countDonors(); case 'donations': return $this->formSettings->goalSource->isCampaign() ? $this->donationFormGoalData->getQuery()->countDonations() : $this->donationFormGoalData->getQuery()->count(); default: return $this->donationFormGoalData->getQuery()->count(); endswitch; } /** * @since 3.0.0 */ public function getCountLabel(): ?string { if ($this->goalType()->isDonors() || $this->goalType()->isDonorsFromSubscriptions()) { return __('Donors', 'give'); } if ($this->goalType()->isDonations() || $this->goalType()->isAmount()) { return __('Donations', 'give'); } if ($this->goalType()->isSubscriptions() || $this->goalType()->isAmountFromSubscriptions()) { return __('Recurring Donations', 'give'); } return __('Counted', 'give'); } /** * @since 4.3.0 ensure totalRevenue always reflects revenue * @since 4.1.0 use DonationFormGoalData instead of repository * @since 3.0.0 */ private function formStatsData(): array { $query = $this->donationFormGoalData->getQuery(); // Only form goal has range if ($this->formSettings->goalSource->isForm() && $this->formSettings->goalProgressType->isCustom()) { $query->between($this->formSettings->goalStartDate, $this->formSettings->goalEndDate); } return [ 'totalRevenue' => $this->donationFormGoalData->getTotalDonationRevenue(), 'totalCountValue' => $this->goalType()->isDonations() || $this->goalType()->isAmount() ? $query->count() : $this->getTotalCountValue(), 'totalCountLabel' => $this->getCountLabel(), ]; } /** * @since 3.6.0 added includeHeaderInMultiStep to form design export * @since 3.0.0 */ public function exports(): array { $donateUrl = (new GenerateDonateRouteUrl())(); $validateUrl = (new GenerateDonationFormValidationRouteUrl())(); $authUrl = (new GenerateAuthUrl())(); $formDataGateways = $this->donationFormRepository->getFormDataGateways($this->donationFormId); $formApi = $this->donationFormRepository->getFormSchemaFromBlocks( $this->donationFormId, $this->formBlocks ); $formDesign = $this->getFormDesign($this->designId()); return [ 'donateUrl' => $donateUrl, 'validateUrl' => $validateUrl, 'authUrl' => $authUrl, 'inlineRedirectRoutes' => [ 'donation-confirmation-receipt-view', ], 'registeredGateways' => $formDataGateways, 'form' => array_merge($formApi->jsonSerialize(), [ 'settings' => $this->formSettings, 'currency' => $formApi->getDefaultCurrency(), 'goal' => $this->previewMode || ($this->formSettings->showHeader && $this->formSettings->enableDonationGoal) ? $this->donationFormGoalData->toArray() : [], 'stats' => $this->previewMode || ($this->formSettings->showHeader && $this->formSettings->enableDonationGoal) ? $this->formStatsData() : [], 'design' => $formDesign ? [ 'id' => $formDesign::id(), 'name' => $formDesign::name(), 'isMultiStep' => $formDesign->isMultiStep(), 'includeHeaderInMultiStep' => $formDesign->shouldIncludeHeaderInMultiStep(), ] : null, ]), 'previewMode' => $this->previewMode, ]; } /** * @since 4.17.0 print the form's skeleton inside the root and announce the shell to the embedding page. * This is the order of loading: * 1. Enqueue global styles from WP. * - 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. * 2. Enqueue our donation form specific scripts & styles. * - We will let WP handle the actual printing depending on how they were enqueued. * 3. Pin the script and style queues to what we enqueued. * - The print functions below fire actions that other plugins enqueue from, so the queues * have to be constrained before printing rather than trusted. * 4. Call the specific WP functions wp_print_styles() and wp_print_head_scripts() * - 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. * 5. Manually echo our window data and root div for our React app to consume * 6. Finally, call the specific WP function wp_print_footer_scripts() * - This will only print the footer scripts that are enqueued within our route. * * @since 4.16.7 Isolate the printed assets from anything enqueued after the form has been prepared * @since 4.14.3 Escape HTML attributes for classNames property * @since 3.20.0 Adds class for form design * @since 3.11.0 Sanitize customCSS property * @since 3.0.0 */ public function render(): string { $this->skeleton = $this->previewMode ? '' : $this->renderSkeleton(); $this->enqueueGlobalStyles(); $this->enqueueFormScripts( $this->donationFormId, $this->designId() ); (new IsolateEnqueuedFormViewAssets())(); ob_start(); wp_print_styles(); wp_print_head_scripts(); ?> previewMode || $this->formSettings->customCss): ?> designId()}"]; if ($this->previewMode) { $classNames[] = 'givewp-donation-form--preview'; } ?>