Skip to content

Commit 0d6cf25

Browse files
authored
Merge pull request #88 from kidunot89/feature/ql-session-handler
QL Session Handler
2 parents 1b1bb68 + baac48a commit 0d6cf25

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

includes/class-filters.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use WPGraphQL\Extensions\WooCommerce\Data\Factory;
1616
use WPGraphQL\Extensions\WooCommerce\Data\Loader\WC_Customer_Loader;
1717
use WPGraphQL\Extensions\WooCommerce\Data\Loader\WC_Post_Crud_Loader;
18+
use WPGraphQL\Extensions\WooCommerce\Utils\QL_Session_Handler;
1819

1920
/**
2021
* Class Filters
@@ -34,12 +35,24 @@ class Filters {
3435
*/
3536
private static $post_crud_loader;
3637

38+
/**
39+
* Stores instance session header name.
40+
*
41+
* @var string
42+
*/
43+
private static $session_header;
44+
3745
/**
3846
* Register filters
3947
*/
4048
public static function load() {
49+
// Registers WooCommerce taxonomies.
4150
add_filter( 'register_taxonomy_args', array( __CLASS__, 'register_taxonomy_args' ), 10, 2 );
51+
52+
// Add data-loaders to AppContext.
4253
add_filter( 'graphql_data_loaders', array( __CLASS__, 'graphql_data_loaders' ), 10, 2 );
54+
55+
// Filter core connection resolutions.
4356
add_filter(
4457
'graphql_post_object_connection_query_args',
4558
array( __CLASS__, 'graphql_post_object_connection_query_args' ),
@@ -52,6 +65,13 @@ public static function load() {
5265
10,
5366
5
5467
);
68+
69+
// Setup QL session handler.
70+
self::$session_header = apply_filters( 'woocommerce_session_header_name', 'woocommerce-session' );
71+
add_filter( 'woocommerce_cookie', array( __CLASS__, 'woocommerce_cookie' ) );
72+
add_filter( 'woocommerce_session_handler', array( __CLASS__, 'init_ql_session_handler' ) );
73+
add_filter( 'graphql_response_headers_to_send', array( __CLASS__, 'add_session_header_to_expose_headers' ) );
74+
add_filter( 'graphql_access_control_allow_headers', array( __CLASS__, 'add_session_header_to_allow_headers' ) );
5575
}
5676

5777
/**
@@ -182,4 +202,55 @@ public static function graphql_post_object_connection_query_args( $query_args, $
182202
public static function graphql_term_object_connection_query_args( $query_args, $source, $args, $context, $info ) {
183203
return WC_Terms_Connection_Resolver::get_query_args( $query_args, $source, $args, $context, $info );
184204
}
205+
206+
/**
207+
* Filters WooCommerce cookie key to be used as a HTTP Header on GraphQL HTTP requests
208+
*
209+
* @param string $cookie WooCommerce cookie key.
210+
*
211+
* @return string
212+
*/
213+
public static function woocommerce_cookie( $cookie ) {
214+
return self::$session_header;
215+
}
216+
217+
/**
218+
* Filters WooCommerce session handler class on GraphQL HTTP requests
219+
*
220+
* @param string $session_class Classname of the current session handler class.
221+
*
222+
* @return string
223+
*/
224+
public static function init_ql_session_handler( $session_class ) {
225+
return QL_Session_Handler::class;
226+
}
227+
228+
/**
229+
* Append session header to the exposed headers in GraphQL responses
230+
*
231+
* @param array $headers GraphQL responser headers.
232+
*
233+
* @return array
234+
*/
235+
public static function add_session_header_to_expose_headers( $headers ) {
236+
if ( empty( $headers['Access-Control-Expose-Headers'] ) ) {
237+
$headers['Access-Control-Expose-Headers'] = apply_filters( 'woocommerce_cookie', self::$session_header );
238+
} else {
239+
$headers['Access-Control-Expose-Headers'] .= ', ' . apply_filters( 'woocommerce_cookie', self::$session_header );
240+
}
241+
242+
return $headers;
243+
}
244+
245+
/**
246+
* Append the session header to the allowed headers in GraphQL responses
247+
*
248+
* @param array $allowed_headers The existing allowed headers.
249+
*
250+
* @return array
251+
*/
252+
public static function add_session_header_to_allow_headers( array $allowed_headers ) {
253+
$allowed_headers[] = self::$session_header;
254+
return $allowed_headers;
255+
}
185256
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
/**
3+
* Handles data for the current customers session.
4+
*
5+
* @package WPGraphQL\Extensions\WooCommerce\Utils
6+
* @since 0.1.2
7+
*/
8+
9+
namespace WPGraphQL\Extensions\WooCommerce\Utils;
10+
11+
/**
12+
* Class - QL_Session_Handler
13+
*/
14+
class QL_Session_Handler extends \WC_Session_Handler {
15+
/**
16+
* Encrypt and decrypt
17+
*
18+
* @author Nazmul Ahsan <n.mukto@gmail.com>
19+
* @author Geoff Taylor <kidunot89@gmail.com>
20+
* @link http://nazmulahsan.me/simple-two-way-function-encrypt-decrypt-string/
21+
*
22+
* @param string $string string to be encrypted/decrypted.
23+
* @param string $action what to do with this? e for encrypt, d for decrypt.
24+
*
25+
* @return string
26+
*/
27+
private function crypt( $string, $action = 'e' ) {
28+
// you may change these values to your own.
29+
$secret_key = apply_filters( 'woographql_session_header_secret_key', 'my_simple_secret_key' );
30+
$secret_iv = apply_filters( 'woographql_session_header_secret_iv', 'my_simple_secret_iv' );
31+
32+
$output = false;
33+
$encrypt_method = 'AES-256-CBC';
34+
$key = hash( 'sha256', $secret_key );
35+
$iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );
36+
37+
if ( 'e' === $action ) {
38+
$output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
39+
} elseif ( 'd' === $action ) {
40+
$output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
41+
}
42+
43+
return $output;
44+
}
45+
46+
/**
47+
* Returns formatted $_SERVER index from provided string.
48+
*
49+
* @param string $string String to be formatted.
50+
*
51+
* @return string
52+
*/
53+
private function get_server_key( $string ) {
54+
return 'HTTP_' . strtoupper( preg_replace( '#[^A-z0-9]#', '_', $string ) );
55+
}
56+
57+
/**
58+
* Encrypts and sets the session header on-demand (usually after adding an item to the cart).
59+
*
60+
* Warning: Headers will only be set if this is called before the headers are sent.
61+
*
62+
* @param bool $set Should the session cookie be set.
63+
*/
64+
public function set_customer_session_cookie( $set ) {
65+
if ( $set ) {
66+
$to_hash = $this->_customer_id . '|' . $this->_session_expiration;
67+
$cookie_hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
68+
$cookie_value = $this->_customer_id . '||' . $this->_session_expiration . '||' . $this->_session_expiring . '||' . $cookie_hash;
69+
$this->_has_cookie = true;
70+
if ( ! isset( $_SERVER[ $this->_cookie ] ) || $_SERVER[ $this->_cookie ] !== $cookie_value ) {
71+
add_filter(
72+
'graphql_response_headers_to_send',
73+
function( $headers ) use ( $cookie_value ) {
74+
$headers[ $this->_cookie ] = $this->crypt( $cookie_value, 'e' );
75+
return $headers;
76+
}
77+
);
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Return true if the current user has an active session, i.e. a cookie to retrieve values.
84+
*
85+
* @return bool
86+
*/
87+
public function has_session() {
88+
// @codingStandardsIgnoreLine.
89+
return isset( $_SERVER[ $this->get_server_key( $this->_cookie ) ] ) || $this->_has_cookie || is_user_logged_in();
90+
}
91+
92+
/**
93+
* Retrieve and decrypt the session data from session, if set. Otherwise return false.
94+
*
95+
* Session cookies without a customer ID are invalid.
96+
*
97+
* @return bool|array
98+
*/
99+
public function get_session_cookie() {
100+
// @codingStandardsIgnoreStart.
101+
$cookie_value = isset( $_SERVER[ $this->get_server_key( $this->_cookie ) ] )
102+
? $this->crypt( $_SERVER[ $this->get_server_key( $this->_cookie ) ], 'd' )
103+
: false;
104+
// @codingStandardsIgnoreEnd.
105+
if ( empty( $cookie_value ) || ! is_string( $cookie_value ) ) {
106+
return false;
107+
}
108+
list( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) = explode( '||', $cookie_value );
109+
if ( empty( $customer_id ) ) {
110+
return false;
111+
}
112+
// Validate hash.
113+
$to_hash = $customer_id . '|' . $session_expiration;
114+
$hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
115+
if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) {
116+
return false;
117+
}
118+
return array( $customer_id, $session_expiration, $session_expiring, $cookie_hash );
119+
}
120+
121+
/**
122+
* Forget all session data without destroying it.
123+
*/
124+
public function forget_session() {
125+
add_filter(
126+
'graphql_response_headers_to_send',
127+
function( $headers ) {
128+
$headers[ $this->_cookie ] = 'false';
129+
return $headers;
130+
}
131+
);
132+
wc_empty_cart();
133+
$this->_data = array();
134+
$this->_dirty = false;
135+
$this->_customer_id = $this->generate_customer_id();
136+
}
137+
}

vendor/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,6 @@
9494
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Shipping_Method_Type' => $baseDir . '/includes/type/object/class-shipping-method-type.php',
9595
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Tax_Rate_Type' => $baseDir . '/includes/type/object/class-tax-rate-type.php',
9696
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Variation_Attribute_Type' => $baseDir . '/includes/type/object/class-variation-attribute-type.php',
97+
'WPGraphQL\\Extensions\\WooCommerce\\Utils\\QL_Session_Handler' => $baseDir . '/includes/utils/class-ql-session-handler.php',
9798
'WP_GraphQL_WooCommerce' => $baseDir . '/includes/class-wp-graphql-woocommerce.php',
9899
);

vendor/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class ComposerStaticInitee0d17af17b841ed3a93c4a0e5cc5e5f
109109
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Shipping_Method_Type' => __DIR__ . '/../..' . '/includes/type/object/class-shipping-method-type.php',
110110
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Tax_Rate_Type' => __DIR__ . '/../..' . '/includes/type/object/class-tax-rate-type.php',
111111
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Variation_Attribute_Type' => __DIR__ . '/../..' . '/includes/type/object/class-variation-attribute-type.php',
112+
'WPGraphQL\\Extensions\\WooCommerce\\Utils\\QL_Session_Handler' => __DIR__ . '/../..' . '/includes/utils/class-ql-session-handler.php',
112113
'WP_GraphQL_WooCommerce' => __DIR__ . '/../..' . '/includes/class-wp-graphql-woocommerce.php',
113114
);
114115

0 commit comments

Comments
 (0)