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'); } /** * @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, ]; } /** * 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->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'; } ?>
enqueueRegistrars(); $this->enqueueDesign($formDesignId); $this->enqueueGateways($formId); $this->enqueueFormApp(); } /** * @since 3.0.0 * * @return FormDesign|null */ protected function getFormDesign(string $designId) { /** @var FormDesignRegistrar $formDesignRegistrar */ $formDesignRegistrar = give(FormDesignRegistrar::class); return $formDesignRegistrar->hasDesign($this->designId()) ? $formDesignRegistrar->getDesign($designId) : null; } /** * @since 3.0.0 */ private function enqueueRegistrars() { wp_enqueue_style( 'givewp-donation-form-registrars', GIVE_PLUGIN_URL . 'build/donationFormRegistrars.css', [], GIVE_VERSION ); $handle = 'givewp-donation-form-registrars'; wp_enqueue_script( $handle, GIVE_PLUGIN_URL . 'build/donationFormRegistrars.js', $this->getScriptAssetDependencies(GIVE_PLUGIN_DIR . 'build/donationFormRegistrars.asset.php'), GIVE_VERSION, true ); Language::setScriptTranslations($handle); wp_add_inline_script( 'givewp-donation-form-registrars', 'window.givewpDonationFormExports = ' . wp_json_encode($this->exports()) . ';', 'before' ); Hooks::doAction('givewp_donation_form_enqueue_scripts'); } /** * @since 3.0.0 */ private function enqueueGateways(int $formId) { /** @var DonationFormRepository $donationFormRepository */ $donationFormRepository = give(DonationFormRepository::class); // load gateway scripts foreach ($donationFormRepository->getEnabledPaymentGateways($formId) as $gateway) { if (method_exists($gateway, 'enqueueScript')) { $gateway->enqueueScript($formId); } } } /** * @since 3.0.0 Set script translations * @since 3.0.0 */ private function enqueueDesign(string $formDesignId) { $design = $this->getFormDesign($formDesignId); // silently fail if design is missing for some reason if ($design) { if ($design->css()) { wp_enqueue_style('givewp-form-design-' . $design::id(), $design->css()); } if ($design->js()) { $handle = 'givewp-form-design-' . $design::id(); wp_enqueue_script( $handle, $design->js(), array_merge( $design->dependencies(), ['givewp-donation-form-registrars'] ), true ); Language::setScriptTranslations($handle); } } } /** * @since 3.0.0 */ private function enqueueFormApp() { // load block - since this is using render_callback viewScript in blocks.json will not work. $handle = 'givewp-donation-form-app'; wp_enqueue_script( $handle, GIVE_PLUGIN_URL . 'build/donationFormApp.js', array_merge( $this->getScriptAssetDependencies(GIVE_PLUGIN_DIR . 'build/donationFormApp.asset.php'), ['givewp-donation-form-registrars'] ), GIVE_VERSION, true ); Language::setScriptTranslations($handle); /** * Load iframeResizer.contentWindow.min.js inside iframe * * @see https://github.com/davidjbradshaw/iframe-resizer */ $handle = 'givewp-donation-form-embed-inside'; wp_enqueue_script( $handle, GIVE_PLUGIN_URL . 'build/donationFormEmbedInside.js', [], GIVE_VERSION, true ); Language::setScriptTranslations($handle); } /** * @since 3.4.0 */ private function updateDesignSettingsClassNames(array &$classNames) { if ($this->formSettings->designSettingsImageUrl) { $classNames[] = 'givewp-design-settings--image'; $classNames[] = 'givewp-design-settings--image-style__' . $this->formSettings->designSettingsImageStyle; } if ($this->formSettings->designSettingsLogoUrl) { $classNames[] = 'givewp-design-settings--logo'; $classNames[] = 'givewp-design-settings--logo-position__' . $this->formSettings->designSettingsLogoPosition; } $classNames[] = 'givewp-design-settings--section-style__' . $this->formSettings->designSettingsSectionStyle; $classNames[] = 'givewp-design-settings--textField-style__' . $this->formSettings->designSettingsTextFieldStyle; } /** * @since 4.1.0 */ private function getCampaignColors(int $formId): array { /** @var Campaign $campaign */ $campaign = give()->campaigns->getByFormId($formId); if ($campaign) { return [ 'primaryColor' => $campaign->primaryColor, 'secondaryColor' => $campaign->secondaryColor, ]; } return [ 'primaryColor' => '', 'secondaryColor' => '', ]; } }