Skip to content

Commit

Permalink
feat: QL Session Handler functionality expanded to support cookies on…
Browse files Browse the repository at this point in the history
… non-GraphQL requests
  • Loading branch information
kidunot89 committed Jun 19, 2024
1 parent 61b99ea commit a4becdc
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 78 deletions.
4 changes: 4 additions & 0 deletions codeception.dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ modules:
uploads: '/wp-content/uploads'
WPLoader:
wpRootFolder: '%WP_CORE_DIR%'
dbHost: '%DB_HOST%'
dbName: '%DB_NAME%'
dbUser: '%DB_USER%'
dbPassword: '%DB_PASSWORD%'
dbUrl: 'mysql://%DB_USER%:%DB_PASSWORD%@%DB_HOST%:%DB_PORT%/%DB_NAME%'
tablePrefix: '%WP_TABLE_PREFIX%'
domain: '%WORDPRESS_DOMAIN%'
Expand Down
12 changes: 6 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions includes/admin/class-general.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ public static function get_fields() {
'value' => defined( 'NO_QL_SESSION_HANDLER' ) ? 'on' : woographql_setting( 'disable_ql_session_handler', 'off' ),
'disabled' => defined( 'NO_QL_SESSION_HANDLER' ),
],
[
'name' => 'enable_ql_session_handler_on_ajax',
'label' => __( 'Enable QL Session Handler on WC AJAX requests.', 'wp-graphql-woocommerce' ),
'desc' => __( 'Enabling this will enable JSON Web Tokens usage on WC AJAX requests.', 'wp-graphql-woocommerce' )
. ( defined( 'NO_QL_SESSION_HANDLER' ) ? __( ' This setting is disabled. The "NO_QL_SESSION_HANDLER" flag has been triggered with code', 'wp-graphql-woocommerce' ) : '' ),
'type' => 'checkbox',
'value' => defined( 'NO_QL_SESSION_HANDLER' ) ? 'off' : woographql_setting( 'enable_ql_session_handler_on_ajax', 'off' ),
'disabled' => defined( 'NO_QL_SESSION_HANDLER' ),
],
[
'name' => 'enable_ql_session_handler_on_rest',
'label' => __( 'Enable QL Session Handler on WP REST requests.', 'wp-graphql-woocommerce' ),
'desc' => __( 'Enabling this will enable JSON Web Tokens usage on WP REST requests.', 'wp-graphql-woocommerce' )
. ( defined( 'NO_QL_SESSION_HANDLER' ) ? __( ' This setting is disabled. The "NO_QL_SESSION_HANDLER" flag has been triggered with code', 'wp-graphql-woocommerce' ) : '' ),
'type' => 'checkbox',
'value' => defined( 'NO_QL_SESSION_HANDLER' ) ? 'off' : woographql_setting( 'enable_ql_session_handler_on_rest', 'off' ),
'disabled' => defined( 'NO_QL_SESSION_HANDLER' ),
],
[
'name' => 'enable_unsupported_product_type',
'label' => __( 'Enable Unsupported types', 'wp-graphql-woocommerce' ),
Expand Down
17 changes: 16 additions & 1 deletion includes/class-woocommerce-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,29 @@ public static function get_authorizing_url_nonce_param_name( $field ) {
return woographql_setting( "{$field}_nonce_param", null );
}

public static function should_load_session_handler() {

Check failure on line 81 in includes/class-woocommerce-filters.php

View workflow job for this annotation

GitHub Actions / Testing WooGraphQL code quality w/ PHPStan

Method WPGraphQL\WooCommerce\WooCommerce_Filters::should_load_session_handler() has no return type specified.
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' ) ):
//phpcs:enable
case 'on' === woographql_setting( 'enable_ql_session_handler_on_rest', 'off' )
&& ( defined( 'REST_REQUEST' ) && REST_REQUEST ):
return true;
default:
return false;
}
}

/**
* 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 ( \WPGraphQL\Router::is_graphql_http_request() ) {
if ( self::should_load_session_handler() ) {
$session_class = '\WPGraphQL\WooCommerce\Utils\QL_Session_Handler';
} elseif ( WooGraphQL::auth_router_is_enabled() ) {
require_once get_includes_directory() . 'utils/class-protected-router.php';
Expand Down
121 changes: 53 additions & 68 deletions includes/utils/class-ql-session-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use WC_Session_Handler;
use WPGraphQL\WooCommerce\Vendor\Firebase\JWT\JWT;
use WPGraphQL\WooCommerce\Vendor\Firebase\JWT\Key;
use WPGraphQL\Router as Router;

/**
* Class - QL_Session_Handler
Expand Down Expand Up @@ -52,8 +53,8 @@ class QL_Session_Handler extends WC_Session_Handler {
* Constructor for the session class.
*/
public function __construct() {
parent::__construct();
$this->_token = apply_filters( 'graphql_woocommerce_cart_session_http_header', 'woocommerce-session' );
$this->_table = $GLOBALS['wpdb']->prefix . 'woocommerce_sessions';
}

/**
Expand Down Expand Up @@ -100,18 +101,17 @@ public function init() {
$this->init_session_token();
Session_Transaction_Manager::get( $this );

/**
* Necessary since Session_Transaction_Manager applies to the reference.
*
* @var self $this
*/
add_action( 'woocommerce_set_cart_cookies', [ $this, 'set_customer_session_token' ], 10 );
add_action( 'woographql_update_session', [ $this, 'set_customer_session_token' ], 10 );
add_action( 'shutdown', [ $this, 'save_data' ] );
add_action( 'wp_logout', [ $this, 'destroy_session' ] );

if ( ! is_user_logged_in() ) {
add_filter( 'nonce_user_logged_out', [ $this, 'maybe_update_nonce_user_logged_out' ], 10, 2 );
if ( Router::is_graphql_http_request() ) {
add_action( 'woocommerce_set_cart_cookies', [ $this, 'set_customer_session_token' ], 10 );
add_action( 'woographql_update_session', [ $this, 'set_customer_session_token' ], 10 );
add_action( 'shutdown', [ $this, 'save_data' ] );
} else {
add_action( 'woocommerce_set_cart_cookies', [ $this, 'set_customer_session_cookie' ], 10 );
add_action( 'shutdown', [ $this, 'save_data' ], 20 );
add_action( 'wp_logout', [ $this, 'destroy_session' ] );
if ( ! is_user_logged_in() ) {
add_filter( 'nonce_user_logged_out', [ $this, 'maybe_update_nonce_user_logged_out' ], 10, 2 );
}
}
}

Expand All @@ -123,6 +123,10 @@ public function init() {
* @return void
*/
public function init_session_token() {

/**
* @var object{ iat: int, exp: int, data: object{ customer_id: string } }|false|\WP_Error $token
*/
$token = $this->get_session_token();

// Process existing session if not expired or invalid.
Expand All @@ -147,35 +151,43 @@ public function init_session_token() {

// @phpstan-ignore-next-line
$this->save_data( $guest_session_id );
$this->set_customer_session_token( true );
Router::is_graphql_http_request()
? $this->set_customer_session_token( true )
: $this->set_customer_session_cookie( true );
}

// Update session expiration on each action.
$this->set_session_expiration();
if ( $token->exp < $this->_session_expiration ) {
$this->update_session_timestamp( (string) $this->_customer_id, $this->_session_expiration );
}
} else {
} else if ( is_wp_error( $token ) ) {
add_filter(
'graphql_woocommerce_session_token_errors',
static function ( $errors ) use ( $token ) {
$errors = $token->get_error_message();
return $errors;
}
);
}

// If token invalid throw warning.
if ( is_wp_error( $token ) ) {
add_filter(
'graphql_woocommerce_session_token_errors',
static function ( $errors ) use ( $token ) {
$errors = $token->get_error_message();
return $errors;
}
);
}
$start_new_session = ! $token || is_wp_error( $token );
if ( ! $start_new_session ) {
return;
}

if ( Router::is_graphql_http_request() ) {
// Start new session.
$this->set_session_expiration();

// Get Customer ID.
$this->_customer_id = is_user_logged_in() ? get_current_user_id() : $this->generate_customer_id();
$this->_data = $this->get_session_data();
$this->set_customer_session_token( true );
}//end if

} else {
return $this->init_session_cookie();

Check failure on line 189 in includes/utils/class-ql-session-handler.php

View workflow job for this annotation

GitHub Actions / Testing WooGraphQL code quality w/ PHPStan

Method WPGraphQL\WooCommerce\Utils\QL_Session_Handler::init_session_token() with return type void returns mixed but should not return anything.
}
}

/**
Expand Down Expand Up @@ -259,13 +271,22 @@ public function get_session_header() {
return apply_filters( 'graphql_woocommerce_cart_session_header', $session_header );
}

/**
* Determine if a JWT is being sent in the page response.
*
* @return bool
*/
private function sending_token() {
return $this->_has_token || $this->_issuing_new_token;
}

/**
* Creates JSON Web Token for customer session.
*
* @return false|string
*/
public function build_token() {
if ( empty( $this->_session_issued ) ) {
if ( empty( $this->_session_issued ) || ! $this->sending_token() ) {
return false;
}

Expand Down Expand Up @@ -368,8 +389,9 @@ function ( $headers ) {
* @return bool
*/
public function has_session() {

// @codingStandardsIgnoreLine.
return $this->_issuing_new_token || $this->_has_token || is_user_logged_in();
return $this->_issuing_new_token || $this->_has_token || parent::has_session();
}

/**
Expand All @@ -378,35 +400,9 @@ public function has_session() {
* @return void
*/
public function set_session_expiration() {
$this->_session_issued = time();
// 14 Days.
$this->_session_expiration = apply_filters(
'graphql_woocommerce_cart_session_expire',
// Seconds * Minutes * Hours * Days.
$this->_session_issued + ( 60 * 60 * 24 * 14 )
);
// 13 Days.
$this->_session_expiring = $this->_session_expiration - ( 60 * 60 * 24 );
}

/**
* Forget all session data without destroying it.
*
* @return void
*/
public function forget_session() {
if ( isset( $this->_token_to_be_sent ) ) {
unset( $this->_token_to_be_sent );
}
wc_empty_cart();
$this->_data = [];
$this->_dirty = false;

// Start new session.
$this->set_session_expiration();

// Get Customer ID.
$this->_customer_id = is_user_logged_in() ? get_current_user_id() : $this->generate_customer_id();
$this->_session_issued = time();
$this->_session_expiring = apply_filters( 'wc_session_expiring', $this->_session_issued + ( 60 * 60 * 47 ) ); // 47 Hours.
$this->_session_expiration = apply_filters( 'wc_session_expiration', $this->_session_issued + ( 60 * 60 * 48 ) ); // 48 Hours.
}

/**
Expand Down Expand Up @@ -444,17 +440,6 @@ public function reload_data() {
}
}

/**
* Noop for \WC_Session_Handler method.
*
* Prevents potential crticial errors when calling this method.
*
* @param bool $set Should the session cookie be set.
*
* @return void
*/
public function set_customer_session_cookie( $set ) {}

/**
* Returns "client_session_id". "client_session_id_expiration" is used
* to keep "client_session_id" as fresh as possible.
Expand Down
Loading

0 comments on commit a4becdc

Please sign in to comment.