| 1 |
<?php |
| 2 |
|
| 3 |
namespace Give\DonationForms\Actions; |
| 4 |
|
| 5 |
use Give\DonationForms\Models\DonationForm; |
| 6 |
use Give\Framework\Routes\Route; |
| 7 |
|
| 8 |
/** |
| 9 |
* Data the external embed script reads from window.givewpDonationFormEmbed. |
| 10 |
* Runs when the script is requested, so the strings are in the site's locale |
| 11 |
* and the URLs reflect the site's current home URL. |
| 12 |
* |
| 13 |
* Everything the script needs to know about the WordPress site travels here |
| 14 |
* rather than being hardcoded in the bundle: the home URL, the routes the |
| 15 |
* iframe loads, the form's own page, and the parameters an offsite gateway |
| 16 |
* return carries. The script only appends per-embed values such as the form |
| 17 |
* id. |
| 18 |
* |
| 19 |
* When the script URL names a form (`?form-id=42`, or the `42` path segment), |
| 20 |
* the response also carries that form's server-rendered skeleton, keyed by |
| 21 |
* id, so the embed can draw the form's shape before the form page answers. |
| 22 |
* Only published forms are included, and the id never selects which form the |
| 23 |
* embed loads: the element looks up its own `form-id` attribute in the map. |
| 24 |
* |
| 25 |
* @since 4.17.0 |
| 26 |
*/ |
| 27 |
class GetExternalEmbedScriptData |
| 28 |
{ |
| 29 |
/** |
| 30 |
* Upper bound on form ids honored per request, so a long list cannot turn |
| 31 |
* one script request into many form loads. |
| 32 |
* |
| 33 |
* @since 4.17.0 |
| 34 |
*/ |
| 35 |
private const MAX_FORMS = 10; |
| 36 |
|
| 37 |
/** |
| 38 |
* @since 4.17.0 |
| 39 |
* |
| 40 |
* @param array $request The query arguments and path id the router matched. |
| 41 |
*/ |
| 42 |
public function __invoke(array $request = []): array |
| 43 |
{ |
| 44 |
return [ |
| 45 |
'homeUrl' => home_url('/'), |
| 46 |
'formViewUrl' => esc_url_raw(Route::url('donation-form-view')), |
| 47 |
'receiptViewUrl' => esc_url_raw(Route::url('donation-confirmation-receipt-view')), |
| 48 |
'formPageUrl' => (new GenerateDonationFormPageUrl())(), |
| 49 |
/* |
| 50 |
* The offsite gateway return flow, as GenerateDonationConfirmationReceiptUrl |
| 51 |
* builds it: the listener params that must match, and the params carrying |
| 52 |
* the embed and receipt ids. |
| 53 |
*/ |
| 54 |
'receiptReturn' => [ |
| 55 |
'match' => [ |
| 56 |
'givewp-event' => 'donation-completed', |
| 57 |
'givewp-listener' => 'show-donation-confirmation-receipt', |
| 58 |
], |
| 59 |
'embedIdParam' => 'givewp-embed-id', |
| 60 |
'receiptIdParam' => 'givewp-receipt-id', |
| 61 |
], |
| 62 |
'i18n' => [ |
| 63 |
'donate' => __('Donate', 'give'), |
| 64 |
'loading' => __('Loading', 'give'), |
| 65 |
'formTitle' => __('Donation Form', 'give'), |
| 66 |
'openForm' => __('Open donation form', 'give'), |
| 67 |
'close' => __('Close', 'give'), |
| 68 |
], |
| 69 |
// An object even when empty, so the script always sees a map. |
| 70 |
'skeletons' => (object)$this->skeletons($this->formIds($request)), |
| 71 |
]; |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* The form ids the request names: a comma list in `form-id` and the path |
| 76 |
* segment the router returns as `id`. Anything but a plain positive integer |
| 77 |
* is dropped, then deduplicated and capped. |
| 78 |
* |
| 79 |
* @since 4.17.0 |
| 80 |
* |
| 81 |
* @return int[] |
| 82 |
*/ |
| 83 |
private function formIds(array $request): array |
| 84 |
{ |
| 85 |
$ids = []; |
| 86 |
|
| 87 |
if (isset($request['form-id'])) { |
| 88 |
$ids = is_array($request['form-id']) ? $request['form-id'] : explode(',', (string)$request['form-id']); |
| 89 |
} |
| 90 |
|
| 91 |
if (!empty($request['id'])) { |
| 92 |
$ids[] = $request['id']; |
| 93 |
} |
| 94 |
|
| 95 |
// Only plain positive integers: absint() would read "-42" and "42abc" as 42. |
| 96 |
$ids = array_filter(array_map('trim', array_map('strval', $ids)), 'ctype_digit'); |
| 97 |
$ids = array_values(array_unique(array_filter(array_map('intval', $ids)))); |
| 98 |
|
| 99 |
return array_slice($ids, 0, self::MAX_FORMS); |
| 100 |
} |
| 101 |
|
| 102 |
/** |
| 103 |
* Skeleton markup by form id for the published forms among the ids. A form |
| 104 |
* whose design the skeleton cannot sketch gets no entry, and the embed |
| 105 |
* shows a spinner for it. |
| 106 |
* |
| 107 |
* @since 4.17.0 |
| 108 |
* |
| 109 |
* @param int[] $ids |
| 110 |
* |
| 111 |
* @return array<int, string> |
| 112 |
*/ |
| 113 |
private function skeletons(array $ids): array |
| 114 |
{ |
| 115 |
$renderer = new RenderFormSkeleton(); |
| 116 |
$skeletonData = new GetFormSkeletonData(); |
| 117 |
$skeletons = []; |
| 118 |
|
| 119 |
foreach ($ids as $id) { |
| 120 |
/** @var DonationForm|null $form */ |
| 121 |
$form = DonationForm::find($id); |
| 122 |
|
| 123 |
if (!$form || !$form->status->isPublished()) { |
| 124 |
continue; |
| 125 |
} |
| 126 |
|
| 127 |
$markup = $renderer($skeletonData($form)); |
| 128 |
|
| 129 |
if ($markup) { |
| 130 |
$skeletons[$id] = $markup; |
| 131 |
} |
| 132 |
} |
| 133 |
|
| 134 |
return $skeletons; |
| 135 |
} |
| 136 |
} |
| 137 |
|