Files

327 lines
12 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* Adds filters that modify woocommerce functionality on GraphQL requests.
*
2019-10-25 19:13:36 -04:00
* @package \WPGraphQL\WooCommerce
* @since 0.2.0
*/
2019-10-25 19:13:36 -04:00
namespace WPGraphQL\WooCommerce;
use WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce as WooGraphQL;
/**
* Class WooCommerce
*/
class WooCommerce {
/**
* Stores instance session header name.
*
* @var string
*/
private static $session_header;
/**
2019-11-13 17:43:37 -05:00
* Initializes hooks for WooCommerce-related utilities.
2023-06-13 23:17:02 +03:00
*
* @return void
*/
public static function init() {
self::$session_header = apply_filters( 'graphql_woocommerce_cart_session_http_header', 'woocommerce-session' );
2019-11-18 16:18:13 -05:00
// Check if request is a GraphQL POST request.
2023-04-04 12:02:25 -04:00
if ( ! self::is_session_handler_disabled() ) {
add_filter( 'woocommerce_session_handler', [ self::class, 'woocommerce_session_handler' ] );
add_filter( 'graphql_response_headers_to_send', [ self::class, 'add_session_header_to_expose_headers' ] );
add_filter( 'graphql_access_control_allow_headers', [ self::class, 'add_session_header_to_allow_headers' ] );
// Initialize cart/session after JWT authentication has had a chance to run.
add_action( 'init_graphql_request', [ self::class, 'initialize_session_and_cart' ] );
}
2020-04-03 21:40:17 -04:00
// Authenticate pre-auth download URLs before WooCommerce's download handler.
if ( 'on' === woographql_setting( 'enable_pre_auth_download_urls', 'off' ) ) {
add_action( 'init', [ self::class, 'authenticate_pre_auth_download' ], 1 );
}
// WPGraphQL Reset password -> Use woocommerce email password template when requested.
add_filter( 'retrieve_password_message', [ self::class, 'get_reset_password_message' ], 10, 3 );
add_filter( 'retrieve_password_title', [ self::class, 'get_reset_password_title' ] );
// Brand the WooCommerce Order Attribution "Origin" for orders created through WPGraphQL.
add_filter( 'wc_order_attribution_origin_label', [ self::class, 'order_attribution_origin_label' ], 10, 4 );
}
/**
* Returns the WooCommerce Order Attribution source type used to mark orders created through WPGraphQL.
*
* Matches the default `created_via` value so GraphQL-created orders are attributable out of the box.
*
* @return string
*/
public static function get_order_attribution_source_type() {
return apply_filters( 'graphql_woocommerce_order_attribution_source_type', 'graphql-api' );
}
/**
* Provides the WooCommerce Order Attribution "Origin" label for orders created through WPGraphQL.
*
* Connected to WooCommerce's order origin label filter so orders tagged with our attribution
* source type surface a recognizable origin instead of "Unknown".
*
* @param string $label Origin label. May contain a "%s" placeholder for the source.
* @param string $source_type Attribution source type.
* @param string $source Attribution source.
* @param string $formatted_source Formatted attribution source.
*
* @return string
*/
public static function order_attribution_origin_label( $label, $source_type, $source, $formatted_source ) {
if ( self::get_order_attribution_source_type() !== $source_type ) {
return $label;
}
return apply_filters( 'graphql_woocommerce_order_attribution_origin_label', __( 'GraphQL', 'graphql-for-ecommerce' ), $source, $formatted_source );
}
2023-04-04 12:02:25 -04:00
/**
* Returns true if the "Disable QL Session Handler" option is checked on the settings page.
*
* @return boolean
*/
public static function is_session_handler_disabled() {
return \defined( 'NO_QL_SESSION_HANDLER' ) || 'on' === woographql_setting( 'disable_ql_session_handler', 'off' );
}
/**
* Initialize WooCommerce session and cart for GraphQL requests.
*
* This is hooked to 'graphql_before_execute' to ensure JWT authentication has
* had a chance to set the current user before the session is initialized.
* This fixes an issue where guest sessions weren't being updated when a user
* provides both a Cart-Token (session) and Authorization (JWT) header.
*
* @return void
*/
public static function initialize_session_and_cart() {
if ( ! \WPGraphQL\Router::is_graphql_http_request() ) {
return;
}
// Clear any existing WooCommerce objects to ensure fresh initialization
// with the correct user context after JWT authentication.
// @phpstan-ignore-next-line
\WC()->customer = null;
// @phpstan-ignore-next-line
\WC()->cart = null;
// @phpstan-ignore-next-line
\WC()->session = null;
wc_load_cart();
// Ensure cart contents are restored from the session after re-initialization.
\WC()->cart->get_cart_from_session(); // @phpstan-ignore-line
}
/**
* Returns array of enabled authorizing URL field slugs.
*
* @return array
*/
public static function enabled_authorizing_url_fields() {
if ( defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' ) ) {
return \WPGraphQL\WooCommerce\Admin\General::enabled_authorizing_url_fields_value();
}
return woographql_setting( 'enable_authorizing_url_fields', [] );
}
/**
* Return the nonce query parameter name for the provided field.
*
* @param string $field URL field slug.
*
* @return string|null
*/
public static function get_authorizing_url_nonce_param_name( $field ) {
$flag_name = strtoupper( $field );
$hardcoded_name = defined( "{$flag_name}_NONCE_PARAM" ) ? constant( "{$flag_name}_NONCE_PARAM" ) : false;
if ( ! empty( $hardcoded_name ) ) {
return $hardcoded_name;
}
return woographql_setting( "{$field}_nonce_param", null );
2023-04-04 12:02:25 -04:00
}
/**
* Returns true if the session handler should be loaded.
*
* @return boolean
*/
public static function should_load_session_handler() {
// Any request carrying either the Store-API Cart-Token header or the
// legacy `woocommerce-session` (filterable) header is a headless
// caller driving session state through the token, regardless of
// which WP endpoint it lands on (admin-ajax, REST, the front-end,
// etc.). We need QL_Session_Handler here too so the session is
// bootstrapped from the token instead of the (absent) WC session
// cookie.
$legacy_header_key = 'HTTP_' . strtoupper(
preg_replace(
'#[^A-z0-9]#',
'_',
apply_filters( 'graphql_woocommerce_cart_session_http_header', 'woocommerce-session' )
)
);
$has_session_header = ! empty( $_SERVER['HTTP_CART_TOKEN'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|| ! empty( $_SERVER[ $legacy_header_key ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
switch ( true ) {
case \WPGraphQL\Router::is_graphql_http_request():
//phpcs:disable
case 'on' === woographql_setting( 'enable_ql_session_handler_on_ajax', 'off' )
&& (
! empty( $_GET['wc-ajax'] )
|| defined( 'WC_DOING_AJAX' )
|| wp_doing_ajax()
|| $has_session_header
):
//phpcs:enable
case 'on' === woographql_setting( 'enable_ql_session_handler_on_rest', 'off' )
&& ( ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || $has_session_header ):
return true;
default:
return false;
}
}
2019-11-24 23:54:50 -05:00
/**
* WooCommerce Session Handler callback
*
* @param string $session_class Class name of WooCommerce Session Handler.
* @return string
*/
public static function woocommerce_session_handler( $session_class ) {
if ( self::should_load_session_handler() ) {
2019-11-24 23:54:50 -05:00
$session_class = '\WPGraphQL\WooCommerce\Utils\QL_Session_Handler';
} elseif ( WooGraphQL::auth_router_is_enabled() ) {
require_once get_includes_directory() . 'utils/class-protected-router.php';
require_once get_includes_directory() . 'utils/class-transfer-session-handler.php';
$session_class = Utils\Protected_Router::is_auth_request() ? '\WPGraphQL\WooCommerce\Utils\Transfer_Session_Handler' : $session_class;
2019-11-24 23:54:50 -05:00
}
return $session_class;
}
/**
* Append session header to the exposed headers in GraphQL responses
*
* @param array $headers GraphQL responser headers.
* @return array
*/
public static function add_session_header_to_expose_headers( array $headers ) {
if ( empty( $headers['Access-Control-Expose-Headers'] ) ) {
2019-11-18 16:18:13 -05:00
$headers['Access-Control-Expose-Headers'] = self::$session_header;
} else {
2019-11-18 16:18:13 -05:00
$headers['Access-Control-Expose-Headers'] .= ', ' . self::$session_header;
}
return $headers;
}
/**
* Append the session header to the allowed headers in GraphQL responses
*
* @param array $allowed_headers The existing allowed headers.
* @return array
*/
public static function add_session_header_to_allow_headers( array $allowed_headers ) {
$allowed_headers[] = self::$session_header;
return $allowed_headers;
}
2020-04-03 21:40:17 -04:00
/**
* Customizes the password reset message for ResetPassword Mutation.
*
* This function modifies the password reset message to use WooCommerce's email template
* if the `WC_Email_Customer_Reset_Password` email is enabled. It sets the email subject
* and content type based on WooCommerce settings and returns the styled email content.
*
* @param string $message The original password reset message.
* @param string $key The password reset key.
* @param string $user_login The username or email of the user requesting the password reset.
*
* @return string The customized password reset message. Returns the original message if
* the `WC_Email_Customer_Reset_Password` email is not enabled.
*/
public static function get_reset_password_message( $message, $key, $user_login ) {
/** @var \WC_Email_Customer_Reset_Password $wc_reset_email */
$wc_reset_email = \WC()->mailer()->emails['WC_Email_Customer_Reset_Password'];
if ( $wc_reset_email && $wc_reset_email->is_enabled() ) {
add_filter( 'wp_mail_content_type', [ $wc_reset_email, 'get_content_type' ] );
$wc_reset_email->user_login = $user_login;
$wc_reset_email->reset_key = $key;
$message = $wc_reset_email->style_inline( $wc_reset_email->get_content() );
return $message;
}
return $message;
}
/**
* Customizes the password reset title for ResetPassword Mutation.
*
* This function modifies the password reset email title to use WooCommerce's email subject
* if the `WC_Email_Customer_Reset_Password` email is enabled.
*
* @param string $title The original password reset email title.
*
* @return string The customized password reset email title. Returns the original title if
* the `WC_Email_Customer_Reset_Password` email is not enabled.
*/
public static function get_reset_password_title( $title ) {
/** @var \WC_Email_Customer_Reset_Password $wc_reset_email */
$wc_reset_email = \WC()->mailer()->emails['WC_Email_Customer_Reset_Password'];
if ( $wc_reset_email && $wc_reset_email->is_enabled() ) {
return $wc_reset_email->get_subject();
}
return $title;
}
/**
* Authenticates pre-auth download requests before WooCommerce's download handler.
*
* Validates the token and sets the current user so WooCommerce's
* is_user_logged_in() check passes during download processing.
*
* @return void
*/
public static function authenticate_pre_auth_download() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( empty( $_GET['download_file'] ) || empty( $_GET['token'] ) || empty( $_GET['uid'] ) || empty( $_GET['expires'] ) ) {
return;
}
$customer_id = absint( $_GET['uid'] );
$expires = absint( $_GET['expires'] );
$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
$download_id = isset( $_GET['key'] ) ? sanitize_text_field( wp_unslash( $_GET['key'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( empty( $download_id ) ) {
return;
}
if ( ! Type\WPObject\Downloadable_Item_Type::validate_download_token( $customer_id, $download_id, $expires, $token ) ) {
return;
}
wp_set_current_user( $customer_id );
}
2021-01-21 22:41:43 -05:00
}