* * @see http://wcpos.com * @package WCPOS\WooCommercePOS */ use WCPOS\WooCommercePOS\Admin\Permalink; use WCPOS\WooCommercePOS\Logger; use WCPOS\WooCommercePOS\Services\Settings; use WCPOS\WooCommercePOS\Template_Router; use const WCPOS\WooCommercePOS\PLUGIN_PATH; use const WCPOS\WooCommercePOS\SHORT_NAME; use const WCPOS\WooCommercePOS\VERSION; /* * ============================================================================ * WCPOS Functions * ============================================================================ * * Primary functions using the wcpos_ prefix. */ /* * getallheaders() is an alias of apache_response_headers() * This function provides compatibility for nginx servers */ if ( ! \function_exists( 'getallheaders' ) ) { /** * Polyfill for getallheaders() on nginx servers. * * @return array The request headers. */ function getallheaders(): array { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- polyfill for missing PHP function. $headers = array(); foreach ( $_SERVER as $name => $value ) { // RFC2616 (HTTP/1.1) defines header fields as case-insensitive entities. if ( 'http_' == strtolower( substr( $name, 0, 5 ) ) ) { $headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value; } } return $headers; } } /* * Resolve the URL scheme for POS permalinks. * * @return string|null 'https' when force_ssl is enabled, null for the home scheme. */ if ( ! \function_exists( 'wcpos_url_scheme' ) ) { /** * Resolve the URL scheme for POS permalinks. * * See Settings::url_scheme() for the policy. * * @return string|null 'https' when force_ssl is enabled, null for the home scheme. */ function wcpos_url_scheme(): ?string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. return Settings::instance()->url_scheme(); } } /* * Construct the POS permalink. * * @param string $page Page slug. * @return string POS URL. */ if ( ! \function_exists( 'wcpos_url' ) ) { /** * Construct the POS permalink. * * The trailing slash follows the site's permalink structure, via * user_trailingslashit(). * * @param string $page Page slug. * * @return string POS URL. */ function wcpos_url( $page = '' ): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. $slug = Permalink::get_slug(); return home_url( user_trailingslashit( $slug . '/' . $page ), wcpos_url_scheme() ); } } /* * Construct a POS checkout permalink. * * @param string $path Path relative to the wcpos-checkout endpoint. * @return string POS checkout URL. */ if ( ! \function_exists( 'wcpos_checkout_url' ) ) { /** * Construct a POS checkout permalink. * * Respects the force_ssl setting, like wcpos_url(), so checkout and receipt * links work when the site home URL is http but the POS is served over https, * eg: behind an SSL-terminating proxy. * * Like home_url(), this performs no encoding โ€” pass trusted path segments * only and escape the result on output. * * The trailing slash follows the site's permalink structure, via * user_trailingslashit(). Slash-less URLs can trip origin rewrite rules * that force a trailing slash โ€” some redirect to a hardcoded http:// * target, which the browser then blocks as mixed content. The slash is * appended to the end of the string, so $path must not contain a query * string or fragment; append query args to the returned URL instead. * * @param string $path Path relative to the wcpos-checkout endpoint, eg: 'order-pay/123'. * * @return string POS checkout URL. */ function wcpos_checkout_url( $path = '' ): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. $full_path = Template_Router::CHECKOUT_PATH . '/' . ltrim( $path, '/' ); return home_url( user_trailingslashit( $full_path ), wcpos_url_scheme() ); } } /* * Test for POS requests to the server. * * @param string $type Request type: 'query_var', 'header', or 'all'. * @return bool Whether this is a POS request. */ if ( ! \function_exists( 'wcpos_request' ) ) { /** * Test for POS requests to the server. * * Core's rest_api_loaded() reads this query var, which remains the original * outer route during internal re-dispatches; this behavior is load-bearing. * * @param string $type Request type: 'query_var', 'header', 'rest_route', or 'all'. * * @return bool Whether this is a POS request. */ function wcpos_request( $type = 'all' ): bool { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. // check query_vars, eg: ?wcpos=1 or /pos rewrite rule. if ( 'all' == $type || 'query_var' == $type ) { global $wp; if ( 1 == isset( $wp->query_vars[ SHORT_NAME ] ) && $wp->query_vars[ SHORT_NAME ] ) { return true; } } // check headers, eg: from ajax request. if ( 'all' == $type || 'header' == $type ) { $headers = array_change_key_case( getallheaders() ); // convert headers to lowercase. if ( 1 == isset( $headers[ 'x-' . SHORT_NAME ] ) && $headers[ 'x-' . SHORT_NAME ] ) { return true; } } if ( ( 'all' == $type || 'rest_route' == $type ) && isset( $GLOBALS['wp']->query_vars['rest_route'] ) ) { $route = '/' . ltrim( (string) $GLOBALS['wp']->query_vars['rest_route'], '/' ); return 1 === preg_match( '#^/' . preg_quote( SHORT_NAME, '#' ) . '/v\d+(?:/|$)#', $route ); } return false; } } /* * Check for POS admin requests. * * @return mixed Admin request header value or false. */ if ( ! \function_exists( 'wcpos_admin_request' ) ) { /** * Check for POS admin requests. * * @return mixed Admin request header value or false. */ function wcpos_admin_request() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. if ( \function_exists( 'getallheaders' ) ) { $headers = getallheaders(); if ( $headers && isset( $headers['X-WC-POS-ADMIN'] ) ) { return $headers['X-WC-POS-ADMIN']; } } if ( isset( $_SERVER['HTTP_X_woocommerce_pos_ADMIN'] ) ) { return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_woocommerce_pos_ADMIN'] ) ); } return false; } } /* * Helper function to get WCPOS settings. * * @param string $id Settings ID. * @param string $key Optional settings key. * @return mixed Settings value. */ if ( ! \function_exists( 'wcpos_get_settings' ) ) { /** * Helper function to get WCPOS settings. * * @param string $id Settings ID. * @param string $key Optional settings key. * * @return mixed Settings value. */ function wcpos_get_settings( $id, $key = null ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. $settings_service = Settings::instance(); return $settings_service->get_settings( $id, $key ); } } /* * Get the site UUID (Plugin State), generating and persisting it on first use. * * @return string Site UUID. */ if ( ! \function_exists( 'wcpos_get_site_uuid' ) ) { /** * Get the site UUID, generating and persisting it on first use. * * Single owner for the woocommerce_pos_uuid option โ€” the * generate-if-missing logic previously lived in three places (REST index, * POS frontend, analytics) and could race. * * @return string Site UUID. */ function wcpos_get_site_uuid(): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. $uuid = get_option( 'woocommerce_pos_uuid', '' ); if ( \is_string( $uuid ) && '' !== $uuid ) { return $uuid; } $uuid = \Ramsey\Uuid\Uuid::uuid4()->toString(); // add_option() is a no-op when the option already exists, so a // concurrent request that won the race keeps its value. if ( ! add_option( 'woocommerce_pos_uuid', $uuid ) ) { $existing = get_option( 'woocommerce_pos_uuid', '' ); if ( \is_string( $existing ) && '' !== $existing ) { return $existing; } update_option( 'woocommerce_pos_uuid', $uuid ); } return $uuid; } } /* * Simple wrapper for json_encode. * * Use JSON_FORCE_OBJECT for PHP 5.3 or higher with fallback for * PHP less than 5.3. * * @param mixed $data Data to encode. * @return string|false JSON string or false on failure. */ if ( ! \function_exists( 'wcpos_json_encode' ) ) { /** * Simple wrapper for json_encode with JSON_FORCE_OBJECT. * * @param mixed $data Data to encode. * * @return string|false JSON string or false on failure. */ function wcpos_json_encode( $data ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. $args = array( $data, JSON_FORCE_OBJECT ); return \call_user_func_array( 'json_encode', $args ); } } /* * Return template path for a given template. * * @param string $template Template name. * @return string|null Template path or null if not found. */ if ( ! \function_exists( 'wcpos_locate_template' ) ) { /** * Return template path for a given template. * * @param string $template Template name. * * @return string|null Template path or null if not found. */ function wcpos_locate_template( $template = '' ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. // check theme directory first. $path = locate_template( array( 'woocommerce-pos/' . $template, ) ); // if not, use plugin template. if ( ! $path ) { $path = PLUGIN_PATH . 'templates/' . $template; } /** * Filters the template path. * * @hook woocommerce_pos_locate_template * * @since 1.0.0 * * @param string $path The full path to the template. * @param string $template The template name, eg: 'receipt.php'. * * @return string $path The full path to the template. */ $filtered_path = apply_filters( 'woocommerce_pos_locate_template', $path, $template ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- legacy hook name. // Check if the filtered template file exists. if ( file_exists( $filtered_path ) ) { return $filtered_path; } // Echo a message or handle the error as needed if the file path does not exist. echo "The template file '" . esc_html( $filtered_path ) . "' does not exist."; return null; } } /* * Remove newlines and code spacing. * * @param string $str HTML string to trim. * @return string Trimmed string. */ if ( ! \function_exists( 'wcpos_trim_html_string' ) ) { /** * Remove newlines and code spacing from an HTML string. * * @param string $str HTML string to trim. * * @return string Trimmed string. */ function wcpos_trim_html_string( $str ): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. return preg_replace( '/^\s+|\n|\r|\s+$/m', '', $str ); } } /* * Get documentation URL. * * @param string $page Documentation page. * @return string Documentation URL. */ if ( ! \function_exists( 'wcpos_doc_url' ) ) { /** * Get documentation URL. * * @param string $page Documentation page. * * @return string Documentation URL. */ function wcpos_doc_url( $page ): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. return 'http://docs.wcpos.com/v/' . VERSION . '/en/' . $page; } } /* * Get FAQ URL. * * @param string $page FAQ page. * @return string FAQ URL. */ if ( ! \function_exists( 'wcpos_faq_url' ) ) { /** * Get FAQ URL. * * @param string $page FAQ page. * * @return string FAQ URL. */ function wcpos_faq_url( $page ): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. return 'http://faq.wcpos.com/v/' . VERSION . '/en/' . $page; } } /* * Helper function to check whether an order is a POS order. * * @param \WC_Order|int $order Order object or ID. * @return bool Whether the order is a POS order. */ if ( ! \function_exists( 'wcpos_is_pos_order' ) ) { /** * Helper function to check whether an order is a POS order. * * @param \WC_Order|int $order Order object or ID. * * @return bool Whether the order is a POS order. */ function wcpos_is_pos_order( $order ): bool { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. // Handle various input types and edge cases. if ( ! $order instanceof WC_Order ) { // Sometimes the order is passed as an ID. if ( is_numeric( $order ) ) { $order = wc_get_order( $order ); } // If we still don't have a valid order, return false. if ( ! $order instanceof WC_Order ) { return false; } } $legacy = $order->get_meta( '_pos', true ); $created_via = $order->get_created_via(); return 'woocommerce-pos' === $created_via || '1' === $legacy; } } /* * Get a default WooCommerce template. * * @param string $template_name Template name. * @param array $args Arguments. */ if ( ! \function_exists( 'wcpos_get_woocommerce_template' ) ) { /** * Get a default WooCommerce template. * * @param string $template_name Template name. * @param array $args Arguments. */ function wcpos_get_woocommerce_template( $template_name, $args = array() ): void { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix. $plugin_path = WC()->plugin_path(); $template = trailingslashit( $plugin_path . '/templates' ) . $template_name; /** * Filter the default WooCommerce template path. * * @param string $template Template path. * @param string $template_name Template name. * @param array $args Arguments. */ $template = apply_filters( 'wcpos_locate_woocommerce_template', $template, $template_name, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- uses wcpos_ prefix. if ( ! file_exists( $template ) ) { Logger::log( \sprintf( 'WooCommerce default template not found: %s', $template ) ); return; } if ( $args && \is_array( $args ) ) { extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract } include $template; } } /* * ============================================================================ * Legacy Aliases * ============================================================================ * * These functions use the old woocommerce_pos_ prefix. * They are kept for backwards compatibility but new code should use wcpos_ prefix. * * @deprecated Use wcpos_* functions instead. */ if ( ! \function_exists( 'woocommerce_pos_url' ) ) { /** * Legacy alias for wcpos_url(). * * @deprecated Use wcpos_url() instead. * * @param mixed $page The page slug. */ function woocommerce_pos_url( $page = '' ): string { return wcpos_url( $page ); } } if ( ! \function_exists( 'woocommerce_pos_request' ) ) { /** * Legacy alias for wcpos_request(). * * @deprecated Use wcpos_request() instead. * * @param mixed $type The request type. */ function woocommerce_pos_request( $type = 'all' ): bool { return wcpos_request( $type ); } } if ( ! \function_exists( 'woocommerce_pos_admin_request' ) ) { /** * Legacy alias for wcpos_admin_request(). * * @deprecated Use wcpos_admin_request() instead. */ function woocommerce_pos_admin_request() { return wcpos_admin_request(); } } if ( ! \function_exists( 'woocommerce_pos_get_settings' ) ) { /** * Legacy alias for wcpos_get_settings(). * * @deprecated Use wcpos_get_settings() instead. * * @param mixed $id The settings ID. * @param null|mixed $key The settings key. */ function woocommerce_pos_get_settings( $id, $key = null ) { return wcpos_get_settings( $id, $key ); } } if ( ! \function_exists( 'woocommerce_pos_get_anon_id' ) ) { /** * Returns the anonymous analytics id (wcpos_anon_id), creating it on first use. * * Supported accessor for the Pro plugin's licence-activation request and the * wcpos.com purchase reconciler join (landing-experiments spec ยง5.3c). * * @return string v4 UUID. */ function woocommerce_pos_get_anon_id(): string { return ( new \WCPOS\WooCommercePOS\Services\Anon_ID() )->get(); } } if ( ! \function_exists( 'woocommerce_pos_json_encode' ) ) { /** * Legacy alias for wcpos_json_encode(). * * @deprecated Use wcpos_json_encode() instead. * * @param mixed $data The data to encode. */ function woocommerce_pos_json_encode( $data ) { return wcpos_json_encode( $data ); } } if ( ! \function_exists( 'woocommerce_pos_locate_template' ) ) { /** * Legacy alias for wcpos_locate_template(). * * @deprecated Use wcpos_locate_template() instead. * * @param mixed $template The template name. */ function woocommerce_pos_locate_template( $template = '' ) { return wcpos_locate_template( $template ); } } if ( ! \function_exists( 'woocommerce_pos_trim_html_string' ) ) { /** * Legacy alias for wcpos_trim_html_string(). * * @deprecated Use wcpos_trim_html_string() instead. * * @param mixed $str The HTML string. */ function woocommerce_pos_trim_html_string( $str ): string { return wcpos_trim_html_string( $str ); } } if ( ! \function_exists( 'woocommerce_pos_doc_url' ) ) { /** * Legacy alias for wcpos_doc_url(). * * @deprecated Use wcpos_doc_url() instead. * * @param mixed $page The documentation page. */ function woocommerce_pos_doc_url( $page ): string { return wcpos_doc_url( $page ); } } if ( ! \function_exists( 'woocommerce_pos_faq_url' ) ) { /** * Legacy alias for wcpos_faq_url(). * * @deprecated Use wcpos_faq_url() instead. * * @param mixed $page The FAQ page. */ function woocommerce_pos_faq_url( $page ): string { return wcpos_faq_url( $page ); } } if ( ! \function_exists( 'woocommerce_pos_is_pos_order' ) ) { /** * Legacy alias for wcpos_is_pos_order(). * * @deprecated Use wcpos_is_pos_order() instead. * * @param mixed $order The order object or ID. */ function woocommerce_pos_is_pos_order( $order ): bool { return wcpos_is_pos_order( $order ); } }