Backoffice
2 months ago
build_widget_data_attributes.php
2 months ago
enqueue_widget_sdk.php
2 weeks ago
get_widget_settings.php
2 months ago
load_script_file.php
2 weeks ago
load_script_template_file.php
2 weeks ago
load_translations.php
2 years ago
register_consent_api.php
2 weeks ago
register_shortcode.php
2 weeks ago
sanitize_restaurant_id.php
2 months ago
sanitize_widget_settings.php
2 months ago
enqueue_widget_sdk.php
169 lines
| 1 | <?php |
| 2 | |
| 3 | namespace Zenchef\Widget\Widget; |
| 4 | |
| 5 | use function apply_filters; |
| 6 | use function array_values; |
| 7 | use function esc_attr; |
| 8 | use function strpos; |
| 9 | use function wp_enqueue_script; |
| 10 | use function wp_register_script; |
| 11 | |
| 12 | /** Script handle under which the Zenchef SDK is registered with WordPress. */ |
| 13 | const SDK_SCRIPT_HANDLE = 'zenchef-sdk'; |
| 14 | |
| 15 | /** |
| 16 | * Handle used up to 1.2.0, when the plugin enqueued a local loader script that |
| 17 | * injected the SDK at runtime. Kept as an alias so a theme that declared it as a |
| 18 | * dependency still resolves to the SDK. |
| 19 | * |
| 20 | * @deprecated 1.3.0 Use SDK_SCRIPT_HANDLE. |
| 21 | */ |
| 22 | const LEGACY_SCRIPT_HANDLE = 'zenchef-widget-integration'; |
| 23 | |
| 24 | /** The SDK is enqueued by its real URL so consent plugins can see and block it. */ |
| 25 | const SDK_SCRIPT_URL = 'https://sdk.zenchef.com/v1/sdk.min.js'; |
| 26 | |
| 27 | /** |
| 28 | * The id attribute the SDK requires on its own script element. |
| 29 | * |
| 30 | * The SDK locates itself with document.getElementById() to derive its stylesheet |
| 31 | * URL from its own src, and throws if it cannot. WordPress would otherwise emit the |
| 32 | * handle suffixed with "-js", so the id is set explicitly. |
| 33 | */ |
| 34 | const SDK_SCRIPT_ELEMENT_ID = 'zenchef-sdk'; |
| 35 | |
| 36 | /** |
| 37 | * Enqueues the Zenchef SDK. |
| 38 | * |
| 39 | * The SDK is enqueued by its public URL rather than through a local loader script, |
| 40 | * so that consent management platforms which block third-party scripts by URL can |
| 41 | * detect it in the page HTML. A site owner can suppress it entirely through the |
| 42 | * zenchef_widget_should_load filter. |
| 43 | * |
| 44 | * @return bool whether the SDK was enqueued |
| 45 | */ |
| 46 | function enqueue_widget_sdk() |
| 47 | { |
| 48 | if (!widget_should_load()) { |
| 49 | return false; |
| 50 | } |
| 51 | |
| 52 | // No version is appended: a ?ver= query string on a third-party URL is |
| 53 | // meaningless for cache busting and defeats exact-URL script blockers. |
| 54 | wp_enqueue_script(SDK_SCRIPT_HANDLE, SDK_SCRIPT_URL, [], null, true); |
| 55 | |
| 56 | // A dependency-only alias, so it costs no extra request. |
| 57 | wp_register_script(LEGACY_SCRIPT_HANDLE, false, [SDK_SCRIPT_HANDLE], null, true); |
| 58 | |
| 59 | return true; |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Whether the widget should be loaded on the current request at all. |
| 64 | * |
| 65 | * Lets a site owner or a consent plugin suppress both the SDK and the widget |
| 66 | * configuration element, for instance until the visitor has given consent. |
| 67 | * |
| 68 | * @return bool |
| 69 | */ |
| 70 | function widget_should_load() |
| 71 | { |
| 72 | return (bool) apply_filters('zenchef_widget_should_load', true); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Rebuilds the SDK script tag from a filterable map of attributes. |
| 77 | * |
| 78 | * This carries the async loading the SDK needs, and gives site owners a way to add |
| 79 | * whatever attributes their consent banner blocks on (commonly type="text/plain" |
| 80 | * plus a vendor-specific data-* attribute) without us writing per-vendor code. |
| 81 | * |
| 82 | * The tag is rebuilt rather than appended to: WordPress below 6.4 emits |
| 83 | * type="text/javascript", so appending a caller-supplied type would produce a |
| 84 | * duplicate attribute in exactly the case this filter exists to support. |
| 85 | * |
| 86 | * @param string $tag the complete script tag built by WordPress |
| 87 | * @param string $handle the script handle |
| 88 | * @param string $src the script source URL |
| 89 | * @return string |
| 90 | */ |
| 91 | function filter_widget_sdk_script_tag($tag, $handle, $src) |
| 92 | { |
| 93 | if ($handle !== SDK_SCRIPT_HANDLE) { |
| 94 | return $tag; |
| 95 | } |
| 96 | |
| 97 | $attributes = apply_filters( |
| 98 | 'zenchef_widget_script_attributes', |
| 99 | [ |
| 100 | 'src' => $src, |
| 101 | 'id' => SDK_SCRIPT_ELEMENT_ID, |
| 102 | 'async' => true, |
| 103 | ], |
| 104 | $handle, |
| 105 | $src |
| 106 | ); |
| 107 | |
| 108 | // Re-asserted after the filter: the SDK locates itself by this id and throws if |
| 109 | // it cannot, so a filter that returns a fresh map rather than merging into the |
| 110 | // one it was given would otherwise stop the widget rendering at all. src stays |
| 111 | // filterable, because moving it to data-src is how several banners block a script. |
| 112 | $attributes['id'] = SDK_SCRIPT_ELEMENT_ID; |
| 113 | |
| 114 | return build_script_tag($attributes); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Drops the SDK host from WordPress's resource hints when the widget is suppressed. |
| 119 | * |
| 120 | * WordPress adds a dns-prefetch hint for the host of every external script it |
| 121 | * enqueues. That hint is a separate <link> element, so it survives the script being |
| 122 | * dequeued and would have the visitor's browser resolve a Zenchef domain on a page |
| 123 | * where the site owner asked for no widget at all. |
| 124 | * |
| 125 | * @param array $urls the URLs WordPress is about to hint at |
| 126 | * @param string $relation_type the hint type being assembled |
| 127 | * @return array |
| 128 | */ |
| 129 | function filter_widget_sdk_resource_hints($urls, $relation_type) |
| 130 | { |
| 131 | if ($relation_type !== 'dns-prefetch' || widget_should_load()) { |
| 132 | return $urls; |
| 133 | } |
| 134 | |
| 135 | foreach ($urls as $index => $url) { |
| 136 | if (strpos((string) $url, 'sdk.zenchef.com') !== false) { |
| 137 | unset($urls[$index]); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | return array_values($urls); |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Renders a script tag from an attribute map. |
| 146 | * |
| 147 | * True renders a boolean attribute, false and null omit it entirely, anything else |
| 148 | * is escaped and rendered as a quoted value. |
| 149 | * |
| 150 | * @param array<string, string|bool|null> $attributes |
| 151 | * @return string |
| 152 | */ |
| 153 | function build_script_tag(array $attributes) |
| 154 | { |
| 155 | $rendered = ''; |
| 156 | |
| 157 | foreach ($attributes as $name => $value) { |
| 158 | if ($value === false || $value === null) { |
| 159 | continue; |
| 160 | } |
| 161 | |
| 162 | $rendered .= $value === true |
| 163 | ? ' ' . esc_attr($name) |
| 164 | : ' ' . esc_attr($name) . '="' . esc_attr($value) . '"'; |
| 165 | } |
| 166 | |
| 167 | return '<script' . $rendered . '></script>' . "\n"; |
| 168 | } |
| 169 |