PluginProbe ʕ •ᴥ•ʔ
Zenchef widget integration / 1.3.0
Zenchef widget integration v1.3.0
1.3.0 1.0.1 1.2.0 trunk 1.0.0
zenchef-widget-integration / src / Widget / enqueue_widget_sdk.php
zenchef-widget-integration / src / Widget Last commit date
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