*
* @see http://wcpos.com
* @package WCPOS\WooCommercePOS
*/
namespace WCPOS\WooCommercePOS\Templates;
use Ramsey\Uuid\Uuid;
use WCPOS\WooCommercePOS\Services\Auth;
use WCPOS\WooCommercePOS\Template_Router;
use const WCPOS\WooCommercePOS\PLUGIN_URL;
use const WCPOS\WooCommercePOS\SHORT_NAME;
use const WCPOS\WooCommercePOS\VERSION;
/**
* Frontend class.
*/
class Frontend {
/**
* Stores user credentials data for use in footer().
*
* @var array
*/
/** Stores user credentials data for use in footer.
*
* @var array
*/
private $wp_credentials = array();
/**
* Render the frontend template.
*
* @return void
*/
public function get_template(): void {
// force ssl.
if ( ! is_ssl() && woocommerce_pos_get_settings( 'general', 'force_ssl' ) ) {
wp_safe_redirect( woocommerce_pos_url() );
exit;
}
// check auth.
if ( ! is_user_logged_in() ) {
add_filter( 'login_url', array( $this, 'login_url' ) );
auth_redirect();
}
// check privileges.
if ( ! current_user_can( 'access_woocommerce_pos' ) ) {
// translators: Authorization error shown when a logged-in user lacks permission to open the POS page.
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'woocommerce-pos' ) );
}
// disable cache plugins.
$this->no_cache();
// last chance before frontend template is rendered.
do_action( 'woocommerce_pos_frontend_template_redirect' );
/*
* Deprecated action.
*
* @TODO remove in 1.5.0
*/
if ( has_action( 'woocommerce_pos_template_redirect' ) ) {
do_action_deprecated( 'woocommerce_pos_template_redirect', array(), 'Version_1.4.0', 'woocommerce_pos_frontend_template_redirect' );
}
// add head & footer actions.
add_action( 'woocommerce_pos_head', array( $this, 'head' ) );
add_action( 'woocommerce_pos_footer', array( $this, 'footer' ) );
// Generate user credentials BEFORE including template to ensure cookies can be set.
// The set_web_session_cookie() call in Auth::get_user_data() requires headers not yet sent.
$user = wp_get_current_user();
$auth_service = Auth::instance();
$this->wp_credentials = $auth_service->get_user_data( $user, true );
include woocommerce_pos_locate_template( 'pos.php' );
exit;
}
/**
* Add variable to login url to signify POS login.
*
* @param string $login_url The login URL.
*
* @return mixed
*/
public function login_url( $login_url ) {
return add_query_arg( SHORT_NAME, '1', $login_url );
}
/**
* Output the head scripts.
*/
public function head(): void {
}
/**
* Output the footer scripts.
*/
public function footer(): void {
/**
* Filters whether the POS is in development mode.
*
* When true, loads the web bundle from localhost instead of CDN.
* Useful for local development of the web application.
*
* @since 1.8.0
*
* @param bool $development Whether development mode is enabled.
* Defaults to checking WCPOS_DEVELOPMENT constant,
* then $_ENV['DEVELOPMENT'].
*
* @hook woocommerce_pos_development_mode
*/
$development = apply_filters(
'woocommerce_pos_development_mode',
( \defined( 'WCPOS_DEVELOPMENT' ) && WCPOS_DEVELOPMENT ) || ( isset( $_ENV['DEVELOPMENT'] ) && wp_validate_boolean( sanitize_text_field( wp_unslash( $_ENV['DEVELOPMENT'] ) ) ) )
);
$user = wp_get_current_user();
// Explicit web-bundle override (constant or env). Null when unset.
$explicit_bundle_ref = null;
$env_bundle_ref = getenv( 'WCPOS_WEB_BUNDLE_REF' );
if ( \defined( 'WCPOS_WEB_BUNDLE_REF' ) && WCPOS_WEB_BUNDLE_REF ) {
$explicit_bundle_ref = WCPOS_WEB_BUNDLE_REF;
} elseif ( ! empty( $_ENV['WCPOS_WEB_BUNDLE_REF'] ) ) {
$explicit_bundle_ref = sanitize_text_field( wp_unslash( $_ENV['WCPOS_WEB_BUNDLE_REF'] ) );
} elseif ( false !== $env_bundle_ref && '' !== $env_bundle_ref ) {
$explicit_bundle_ref = sanitize_text_field( wp_unslash( $env_bundle_ref ) );
} elseif ( ! empty( $_SERVER['WCPOS_WEB_BUNDLE_REF'] ) ) {
$explicit_bundle_ref = sanitize_text_field( wp_unslash( $_SERVER['WCPOS_WEB_BUNDLE_REF'] ) );
}
// Default to the plugin's own major.minor so the stable lane tracks the
// version automatically: a 1.9.x plugin loads `@1.9`, a 1.10.x plugin loads
// `@1.10`, etc. — no edit needed as versions roll.
$default_bundle_ref = implode( '.', \array_slice( explode( '.', VERSION ), 0, 2 ) );
/**
* The web-bundle ref served from jsDelivr (or a full base URL).
*
* Override via the WCPOS_WEB_BUNDLE_REF constant / env var or this filter to
* point a site at another lane for testing the in-development build locally
* or on staging: a branch (e.g. `next`), a tag, a commit, or a full base URL
* (anything containing `://`, e.g. a local dev server or an EAS preview).
*
* @hook woocommerce_pos_web_bundle_ref
*/
$bundle_ref = (string) apply_filters( 'woocommerce_pos_web_bundle_ref', $explicit_bundle_ref ?? $default_bundle_ref );
$bundle_ref = trim( $bundle_ref );
if ( '' === $bundle_ref ) {
$bundle_ref = $default_bundle_ref;
}
$bundle_overridden = $bundle_ref !== $default_bundle_ref;
// No trailing slash: Metro's runtime concatenates `cdnBaseUrl` with leading-slash paths
// (`/_expo/...`, `/assets/...`); a trailing slash here would produce `//`, which jsDelivr
// 301-redirects with a year-long cache, breaking lazy chunk loads in the browser.
if ( false !== strpos( (string) $bundle_ref, '://' ) ) {
// Full base URL (local dev server, EAS preview, etc.).
$cdn_base_url = rtrim( $bundle_ref, '/' );
} elseif ( $development && ! $bundle_overridden ) {
// Development default: the local web build server.
$cdn_base_url = 'http://localhost:4567/build';
} else {
// jsDelivr web-bundle lane (e.g. `1.9`, `1.10`, `next`, a tag or commit).
$cdn_base_url = 'https://cdn.jsdelivr.net/gh/wcpos/web-bundle@' . rawurlencode( $bundle_ref ) . '/build';
}
$wcpos_base_path = rtrim( wp_parse_url( woocommerce_pos_url(), PHP_URL_PATH ), '/' );
$stores = array_map(
function ( $store ) {
return $store->get_data();
},
wcpos_get_stores()
);
$site_uuid = get_option( 'woocommerce_pos_uuid' );
if ( ! $site_uuid ) {
$site_uuid = Uuid::uuid4()->toString();
update_option( 'woocommerce_pos_uuid', $site_uuid );
}
$user_uuid = get_user_meta( $user->ID, '_woocommerce_pos_uuid', true );
if ( ! $user_uuid ) {
$user_uuid = Uuid::uuid4()->toString();
update_user_meta( $user->ID, '_woocommerce_pos_uuid', $user_uuid );
}
$vars = array(
'version' => VERSION,
'manifest' => $cdn_base_url . '/metadata.json?v=' . VERSION,
'homepage' => woocommerce_pos_url(),
'logout_url' => $this->pos_logout_url(),
'site' => array(
'uuid' => $site_uuid,
'url' => get_option( 'siteurl' ),
'name' => get_option( 'blogname' ),
'description' => get_option( 'blogdescription' ),
'home' => home_url(),
'gmt_offset' => get_option( 'gmt_offset' ),
'timezone_string' => get_option( 'timezone_string' ),
'wp_version' => get_bloginfo( 'version' ),
'wc_version' => WC()->version,
'wcpos_version' => VERSION,
'wp_api_url' => get_rest_url(),
'wc_api_url' => trailingslashit( get_rest_url( null, 'wc/v3' ) ),
'wcpos_api_url' => trailingslashit( get_rest_url( null, 'wcpos/v1' ) ),
'wcpos_login_url' => Template_Router::get_auth_url(),
'locale' => get_locale(),
),
'wp_credentials' => $this->wp_credentials,
'stores' => $stores,
);
/**
* Filters the javascript variables passed to the POS.
*
* @param array $vars
*
* @returns array $vars
*
* @since 1.0.0
*
* @hook woocommerce_pos_inline_vars
*/
$vars = apply_filters( 'woocommerce_pos_inline_vars', $vars );
$initial_props = wp_json_encode( $vars );
$cdn_base_url = wp_json_encode( $cdn_base_url );
/**
* Add path to worker scripts.
*/
$idb_worker = PLUGIN_URL . 'assets/js/indexeddb.worker.js';
$opfs_worker = PLUGIN_URL . 'assets/js/opfs.worker.js';
// getScript helper and initialProps.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Inline JavaScript for POS frontend
echo "" . "\n";
echo "" . "\n";
}
/**
* Get the POS logout URL.
*
* @return string
*/
private function pos_logout_url() {
/**
* Get the login URL, allow other plugins to customise the URL. eg: WPS Hide Login.
*/
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core hook
$login_url = apply_filters( 'login_url', site_url( '/wp-login.php' ), 'logout', false );
$redirect_to = urlencode( woocommerce_pos_url() );
$reauth = 1;
$wcpos = 1;
$logout_nonce = wp_create_nonce( 'log-out' );
return "{$login_url}?action=logout&_wpnonce={$logout_nonce}&redirect_to={$redirect_to}&reauth={$reauth}&wcpos={$wcpos}";
}
/**
* Disable caching conflicts.
*/
private function no_cache(): void {
// disable W3 Total Cache minify.
if ( ! \defined( 'DONOTMINIFY' ) ) {
\define( 'DONOTMINIFY', 'true' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Third-party constant
}
// disable WP Super Cache.
if ( ! \defined( 'DONOTCACHEPAGE' ) ) {
\define( 'DONOTCACHEPAGE', 'true' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Third-party constant
}
// disable Lite Speed Cache.
do_action( 'litespeed_control_set_nocache', 'nocache WoCommerce POS web application' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Third-party hook
}
}