PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.9.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.9.16
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / includes / Templates / Frontend.php

Frontend.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.9.16, at includes/Templates/Frontend.php

368 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Frontend template.
4 *
5 * @author Paul Kilmurray <paul@kilbot.com>
6 *
7 * @see http://wcpos.com
8 * @package WCPOS\WooCommercePOS
9 */
10
11 namespace WCPOS\WooCommercePOS\Templates;
12
13 use Ramsey\Uuid\Uuid;
14 use WCPOS\WooCommercePOS\Services\Auth;
15 use WCPOS\WooCommercePOS\Template_Router;
16 use const WCPOS\WooCommercePOS\PLUGIN_URL;
17 use const WCPOS\WooCommercePOS\SHORT_NAME;
18 use const WCPOS\WooCommercePOS\VERSION;
19
20 /**
21 * Frontend class.
22 */
23 class Frontend {
24 /**
25 * Stores user credentials data for use in footer().
26 *
27 * @var array
28 */
29 /** Stores user credentials data for use in footer.
30 *
31 * @var array
32 */
33 private $wp_credentials = array();
34
35 /**
36 * Render the frontend template.
37 *
38 * @return void
39 */
40 public function get_template(): void {
41 // force ssl.
42 if ( ! is_ssl() && woocommerce_pos_get_settings( 'general', 'force_ssl' ) ) {
43 wp_safe_redirect( woocommerce_pos_url() );
44 exit;
45 }
46
47 // check auth.
48 if ( ! is_user_logged_in() ) {
49 add_filter( 'login_url', array( $this, 'login_url' ) );
50 auth_redirect();
51 }
52
53 // check privileges.
54 if ( ! current_user_can( 'access_woocommerce_pos' ) ) {
55 // translators: Authorization error shown when a logged-in user lacks permission to open the POS page.
56 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'woocommerce-pos' ) );
57 }
58
59 // disable cache plugins.
60 $this->no_cache();
61
62 // last chance before frontend template is rendered.
63 do_action( 'woocommerce_pos_frontend_template_redirect' );
64
65 /*
66 * Deprecated action.
67 *
68 * @TODO remove in 1.5.0
69 */
70 if ( has_action( 'woocommerce_pos_template_redirect' ) ) {
71 do_action_deprecated( 'woocommerce_pos_template_redirect', array(), 'Version_1.4.0', 'woocommerce_pos_frontend_template_redirect' );
72 }
73
74 // add head & footer actions.
75 add_action( 'woocommerce_pos_head', array( $this, 'head' ) );
76 add_action( 'woocommerce_pos_footer', array( $this, 'footer' ) );
77
78 // Generate user credentials BEFORE including template to ensure cookies can be set.
79 // The set_web_session_cookie() call in Auth::get_user_data() requires headers not yet sent.
80 $user = wp_get_current_user();
81 $auth_service = Auth::instance();
82 $this->wp_credentials = $auth_service->get_user_data( $user, true );
83
84 include woocommerce_pos_locate_template( 'pos.php' );
85 exit;
86 }
87
88 /**
89 * Add variable to login url to signify POS login.
90 *
91 * @param string $login_url The login URL.
92 *
93 * @return mixed
94 */
95 public function login_url( $login_url ) {
96 return add_query_arg( SHORT_NAME, '1', $login_url );
97 }
98
99 /**
100 * Output the head scripts.
101 */
102 public function head(): void {
103 }
104
105 /**
106 * Output the footer scripts.
107 */
108 public function footer(): void {
109 /**
110 * Filters whether the POS is in development mode.
111 *
112 * When true, loads the web bundle from localhost instead of CDN.
113 * Useful for local development of the web application.
114 *
115 * @since 1.8.0
116 *
117 * @param bool $development Whether development mode is enabled.
118 * Defaults to checking WCPOS_DEVELOPMENT constant,
119 * then $_ENV['DEVELOPMENT'].
120 *
121 * @hook woocommerce_pos_development_mode
122 */
123 $development = apply_filters(
124 'woocommerce_pos_development_mode',
125 ( \defined( 'WCPOS_DEVELOPMENT' ) && WCPOS_DEVELOPMENT ) || ( isset( $_ENV['DEVELOPMENT'] ) && wp_validate_boolean( sanitize_text_field( wp_unslash( $_ENV['DEVELOPMENT'] ) ) ) )
126 );
127
128 $user = wp_get_current_user();
129
130 // Explicit web-bundle override (constant or env). Null when unset.
131 $explicit_bundle_ref = null;
132 $env_bundle_ref = getenv( 'WCPOS_WEB_BUNDLE_REF' );
133 if ( \defined( 'WCPOS_WEB_BUNDLE_REF' ) && WCPOS_WEB_BUNDLE_REF ) {
134 $explicit_bundle_ref = WCPOS_WEB_BUNDLE_REF;
135 } elseif ( ! empty( $_ENV['WCPOS_WEB_BUNDLE_REF'] ) ) {
136 $explicit_bundle_ref = sanitize_text_field( wp_unslash( $_ENV['WCPOS_WEB_BUNDLE_REF'] ) );
137 } elseif ( false !== $env_bundle_ref && '' !== $env_bundle_ref ) {
138 $explicit_bundle_ref = sanitize_text_field( wp_unslash( $env_bundle_ref ) );
139 } elseif ( ! empty( $_SERVER['WCPOS_WEB_BUNDLE_REF'] ) ) {
140 $explicit_bundle_ref = sanitize_text_field( wp_unslash( $_SERVER['WCPOS_WEB_BUNDLE_REF'] ) );
141 }
142
143 // Default to the plugin's own major.minor so the stable lane tracks the
144 // version automatically: a 1.9.x plugin loads `@1.9`, a 1.10.x plugin loads
145 // `@1.10`, etc. — no edit needed as versions roll.
146 $default_bundle_ref = implode( '.', \array_slice( explode( '.', VERSION ), 0, 2 ) );
147
148 /**
149 * The web-bundle ref served from jsDelivr (or a full base URL).
150 *
151 * Override via the WCPOS_WEB_BUNDLE_REF constant / env var or this filter to
152 * point a site at another lane for testing the in-development build locally
153 * or on staging: a branch (e.g. `next`), a tag, a commit, or a full base URL
154 * (anything containing `://`, e.g. a local dev server or an EAS preview).
155 *
156 * @hook woocommerce_pos_web_bundle_ref
157 */
158 $bundle_ref = (string) apply_filters( 'woocommerce_pos_web_bundle_ref', $explicit_bundle_ref ?? $default_bundle_ref );
159 $bundle_ref = trim( $bundle_ref );
160 if ( '' === $bundle_ref ) {
161 $bundle_ref = $default_bundle_ref;
162 }
163 $bundle_overridden = $bundle_ref !== $default_bundle_ref;
164
165 // No trailing slash: Metro's runtime concatenates `cdnBaseUrl` with leading-slash paths
166 // (`/_expo/...`, `/assets/...`); a trailing slash here would produce `//`, which jsDelivr
167 // 301-redirects with a year-long cache, breaking lazy chunk loads in the browser.
168 if ( false !== strpos( (string) $bundle_ref, '://' ) ) {
169 // Full base URL (local dev server, EAS preview, etc.).
170 $cdn_base_url = rtrim( $bundle_ref, '/' );
171 } elseif ( $development && ! $bundle_overridden ) {
172 // Development default: the local web build server.
173 $cdn_base_url = 'http://localhost:4567/build';
174 } else {
175 // jsDelivr web-bundle lane (e.g. `1.9`, `1.10`, `next`, a tag or commit).
176 $cdn_base_url = 'https://cdn.jsdelivr.net/gh/wcpos/web-bundle@' . rawurlencode( $bundle_ref ) . '/build';
177 }
178 $wcpos_base_path = rtrim( wp_parse_url( woocommerce_pos_url(), PHP_URL_PATH ), '/' );
179 $stores = array_map(
180 function ( $store ) {
181 return $store->get_data();
182 },
183 wcpos_get_stores()
184 );
185
186 $site_uuid = get_option( 'woocommerce_pos_uuid' );
187 if ( ! $site_uuid ) {
188 $site_uuid = Uuid::uuid4()->toString();
189 update_option( 'woocommerce_pos_uuid', $site_uuid );
190 }
191
192 $user_uuid = get_user_meta( $user->ID, '_woocommerce_pos_uuid', true );
193 if ( ! $user_uuid ) {
194 $user_uuid = Uuid::uuid4()->toString();
195 update_user_meta( $user->ID, '_woocommerce_pos_uuid', $user_uuid );
196 }
197
198 $vars = array(
199 'version' => VERSION,
200 'manifest' => $cdn_base_url . '/metadata.json?v=' . VERSION,
201 'homepage' => woocommerce_pos_url(),
202 'logout_url' => $this->pos_logout_url(),
203 'site' => array(
204 'uuid' => $site_uuid,
205 'url' => get_option( 'siteurl' ),
206 'name' => get_option( 'blogname' ),
207 'description' => get_option( 'blogdescription' ),
208 'home' => home_url(),
209 'gmt_offset' => get_option( 'gmt_offset' ),
210 'timezone_string' => get_option( 'timezone_string' ),
211 'wp_version' => get_bloginfo( 'version' ),
212 'wc_version' => WC()->version,
213 'wcpos_version' => VERSION,
214 'wp_api_url' => get_rest_url(),
215 'wc_api_url' => trailingslashit( get_rest_url( null, 'wc/v3' ) ),
216 'wcpos_api_url' => trailingslashit( get_rest_url( null, 'wcpos/v1' ) ),
217 'wcpos_login_url' => Template_Router::get_auth_url(),
218 'locale' => get_locale(),
219 ),
220 'wp_credentials' => $this->wp_credentials,
221 'stores' => $stores,
222 );
223
224 /**
225 * Filters the javascript variables passed to the POS.
226 *
227 * @param array $vars
228 *
229 * @returns array $vars
230 *
231 * @since 1.0.0
232 *
233 * @hook woocommerce_pos_inline_vars
234 */
235 $vars = apply_filters( 'woocommerce_pos_inline_vars', $vars );
236 $initial_props = wp_json_encode( $vars );
237 $cdn_base_url = wp_json_encode( $cdn_base_url );
238
239 /**
240 * Add path to worker scripts.
241 */
242 $idb_worker = PLUGIN_URL . 'assets/js/indexeddb.worker.js';
243 $opfs_worker = PLUGIN_URL . 'assets/js/opfs.worker.js';
244
245 // getScript helper and initialProps.
246 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Inline JavaScript for POS frontend
247 echo "<script>
248 function getScript(source, callback, onError) {
249 var script = document.createElement('script');
250 script.async = true;
251 script.onload = script.onreadystatechange = function(_, isAbort) {
252 if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
253 script.onload = script.onreadystatechange = null;
254 script = undefined;
255 if (!isAbort && callback) setTimeout(callback, 0);
256 }
257 };
258 script.onerror = function() {
259 script.onload = script.onreadystatechange = null;
260 if (onError) onError(new Error('Failed to load script: ' + source));
261 };
262 script.src = source;
263 document.head.appendChild(script);
264 }
265
266 function loadCSS(source, callback) {
267 var link = document.createElement('link');
268 link.rel = 'stylesheet';
269 link.href = source;
270 link.onload = function() {
271 if (callback) callback();
272 };
273 link.onerror = function() {
274 console.error('Failed to load CSS file:', source);
275 };
276 document.head.appendChild(link);
277 }
278
279 var idbWorker = '{$idb_worker}';
280 var opfsWorker = '{$opfs_worker}';
281 var initialProps = {$initial_props};
282 var cdnBaseUrl = {$cdn_base_url};
283 var baseUrl = '{$wcpos_base_path}';
284 </script>" . "\n";
285
286 echo "<script>
287 var request = new Request(initialProps.manifest);
288
289 window.fetch(request)
290 .then(function(response) { return response.json(); })
291 .then(function(data) {
292 // v1 metadata uses 'bundles' array (metro runtime, common, entry)
293 // v0 fallback uses single 'bundle' string
294 var webMeta = (data && data.fileMetadata && data.fileMetadata.web) || {};
295 var bundles = Array.isArray(webMeta.bundles)
296 ? webMeta.bundles.filter(Boolean)
297 : (webMeta.bundle ? [webMeta.bundle] : []);
298
299 if (!bundles.length) {
300 throw new Error('No JavaScript bundles declared in metadata.json');
301 }
302
303 function loadBundles(index) {
304 if (index >= bundles.length) return;
305 var source = cdnBaseUrl + '/' + bundles[index];
306 getScript(source, function() {
307 loadBundles(index + 1);
308 }, function(error) {
309 console.error(error.message);
310 });
311 }
312
313 if (data.fileMetadata.web.css) {
314 loadCSS(cdnBaseUrl + '/' + data.fileMetadata.web.css, function() {
315 loadBundles(0);
316 });
317 } else {
318 loadBundles(0);
319 }
320 })
321 .catch(function(error) {
322 console.error('Error fetching manifest:', error);
323 });
324 </script>" . "\n";
325 }
326
327 /**
328 * Get the POS logout URL.
329 *
330 * @return string
331 */
332 private function pos_logout_url() {
333 /**
334 * Get the login URL, allow other plugins to customise the URL. eg: WPS Hide Login.
335 */
336 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core hook
337 $login_url = apply_filters( 'login_url', site_url( '/wp-login.php' ), 'logout', false );
338
339 $redirect_to = urlencode( woocommerce_pos_url() );
340 $reauth = 1;
341 $wcpos = 1;
342 $logout_nonce = wp_create_nonce( 'log-out' );
343
344 return "{$login_url}?action=logout&_wpnonce={$logout_nonce}&redirect_to={$redirect_to}&reauth={$reauth}&wcpos={$wcpos}";
345 }
346
347
348
349
350 /**
351 * Disable caching conflicts.
352 */
353 private function no_cache(): void {
354 // disable W3 Total Cache minify.
355 if ( ! \defined( 'DONOTMINIFY' ) ) {
356 \define( 'DONOTMINIFY', 'true' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Third-party constant
357 }
358
359 // disable WP Super Cache.
360 if ( ! \defined( 'DONOTCACHEPAGE' ) ) {
361 \define( 'DONOTCACHEPAGE', 'true' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Third-party constant
362 }
363
364 // disable Lite Speed Cache.
365 do_action( 'litespeed_control_set_nocache', 'nocache WoCommerce POS web application' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Third-party hook
366 }
367 }
368