2023-01-21 01:31:34 +01:00
< ? php
2023-02-17 16:07:40 +01:00
// =============================================================================
// CONSTANTS
// =============================================================================
define (
2023-10-23 00:11:32 +02:00
'FCN_OAUTH2_API_ENDPOINTS' ,
2023-02-17 16:07:40 +01:00
array (
'discord' => array (
'login' => 'https://discord.com/api/oauth2/authorize' ,
'token' => 'https://discord.com/api/oauth2/token' ,
'user' => 'https://discord.com/api/users/@me' ,
'revoke' => 'https://discord.com/api/oauth2/token/revoke' ,
'scope' => 'identify email'
),
'twitch' => array (
'login' => 'https://id.twitch.tv/oauth2/authorize' ,
'token' => 'https://id.twitch.tv/oauth2/token' ,
'user' => 'https://api.twitch.tv/helix/users' ,
'revoke' => 'https://id.twitch.tv/oauth2/revoke' ,
'scope' => 'user:read:email'
),
'google' => array (
'login' => 'https://accounts.google.com/o/oauth2/auth' ,
'token' => 'https://oauth2.googleapis.com/token' ,
'user' => 'https://www.googleapis.com/oauth2/v1/userinfo' ,
'revoke' => 'https://oauth2.googleapis.com/revoke' ,
'scope' => 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
),
'patreon' => array (
'login' => 'https://www.patreon.com/oauth2/authorize' ,
'token' => 'https://www.patreon.com/api/oauth2/token' ,
'user' => 'https://www.patreon.com/api/oauth2/v2/identity' ,
'revoke' => '' ,
2024-08-16 13:31:17 +02:00
'scope' => urlencode ( 'identity identity[email]' )
2024-05-24 21:18:22 +02:00
),
'subscribestar' => array (
'login' => 'https://www.subscribestar.com/oauth2/authorize' ,
'token' => 'https://www.subscribestar.com/oauth2/token' ,
'api' => 'https://www.subscribestar.com/api/graphql/v1' ,
'revoke' => '' ,
'scope' => 'subscriber.read+subscriber.payments.read+user.read+user.email.read'
2023-02-17 16:07:40 +01:00
)
)
);
2023-01-21 01:31:34 +01:00
// =============================================================================
// PROCESS
// =============================================================================
2024-05-23 23:01:14 +02:00
/**
* Handle OAuth 2.0 requests to get campaign tiers
*
* @ since 5.15 . 0
* @ since 5.19 . 0 - Refactored
*/
function fictioneer_oauth2_get_campaign_tiers () {
// Abort if not on the /oauth2 route...
if ( is_null ( get_query_var ( FICTIONEER_OAUTH_ENDPOINT , null ) ) ) {
return ;
}
// Setup
$cookie = fictioneer_oauth2_get_cookie ();
$state = sanitize_key ( $_GET [ 'state' ] ? ? '' );
$code = sanitize_text_field ( $_GET [ 'code' ] ? ? '' ); // Do not use sanitize_key()
// Validate!
if (
! current_user_can ( 'manage_options' ) ||
( $cookie [ 'action' ] ? ? 0 ) !== 'fictioneer_connection_get_patreon_tiers' ||
( $cookie [ 'state' ] ? ? 0 ) !== $state
) {
return ;
}
// Delete cookie
fictioneer_oauth2_delete_cookie ();
// Get access token
$token_response = fictioneer_oauth2_get_token (
FCN_OAUTH2_API_ENDPOINTS [ 'patreon' ][ 'token' ],
array (
'grant_type' => 'authorization_code' ,
'client_id' => get_option ( 'fictioneer_patreon_client_id' ),
'client_secret' => get_option ( 'fictioneer_patreon_client_secret' ),
'redirect_uri' => get_site_url ( null , FICTIONEER_OAUTH_ENDPOINT ),
'code' => $code ,
'scope' => urlencode ( 'campaigns' )
)
);
// Token successfully retrieved?
if ( is_wp_error ( $token_response ) ) {
fictioneer_oauth_die ( $token_response -> get_error_message () );
}
// Extract token
$access_token = isset ( $token_response -> access_token ) ? $token_response -> access_token : null ;
if ( ! $access_token ) {
fictioneer_oauth2_terminate (
admin_url ( 'admin.php?page=fictioneer_connections' ),
array ( 'failure' => 'fictioneer-patreon-token-missing' )
);
}
// Build params
2024-08-16 13:31:17 +02:00
$params = '?fields' . urlencode ( '[campaign]' ) . '=created_at' ;
$params .= '&fields' . urlencode ( '[tier]' ) . '=title,amount_cents,published,description' ;
2024-05-23 23:01:14 +02:00
$params .= '&include=tiers' ;
// Retrieve campaign data from Patreon
$campaign_response = wp_remote_get (
'https://www.patreon.com/api/oauth2/v2/campaigns' . $params ,
array (
'headers' => array (
'Authorization' => 'Bearer ' . $access_token
)
)
);
// Request successful?
if ( is_wp_error ( $campaign_response ) ) {
fictioneer_oauth_die ( $campaign_response -> get_error_message () );
} else {
$body = json_decode ( wp_remote_retrieve_body ( $campaign_response ) );
}
// Data successfully retrieved?
if ( empty ( $body ) || json_last_error () !== JSON_ERROR_NONE ) {
fictioneer_oauth_die ( wp_remote_retrieve_body ( $campaign_response ) );
}
// Data?
if ( ! isset ( $body -> data ) ) {
fictioneer_oauth_die ( 'Data node not found.' );
}
// Includes?
if ( ! isset ( $body -> included ) ) {
fictioneer_oauth_die ( 'Includes node not found.' );
}
// Extract tiers
$tiers = [];
foreach ( $body -> included as $item ) {
if ( $item -> type !== 'tier' || ! isset ( $item -> attributes ) ) {
continue ;
}
$tiers [ $item -> id ] = array (
'id' => absint ( $item -> id ),
'title' => sanitize_text_field ( $item -> attributes -> title ? ? '' ),
'description' => wp_kses_post ( $item -> attributes -> description ? ? '' ),
'amount_cents' => absint ( $item -> attributes -> amount_cents ? ? 0 ),
'published' => filter_var ( $item -> attributes -> published ? ? 0 , FILTER_VALIDATE_BOOLEAN , FILTER_NULL_ON_FAILURE ),
);
}
// Save as autoload option
update_option ( 'fictioneer_connection_patreon_tiers' , $tiers );
// Finish
wp_safe_redirect (
add_query_arg (
array ( 'success' => 'fictioneer-patreon-tiers-pulled' ),
admin_url ( 'admin.php?page=fictioneer_connections' )
)
);
exit ;
}
add_action ( 'template_redirect' , 'fictioneer_oauth2_get_campaign_tiers' , 5 );
2023-08-08 22:45:55 +02:00
/**
2024-05-21 16:44:05 +02:00
* Process any OAuth 2.0 request
2023-08-08 22:45:55 +02:00
*
2024-05-21 16:44:05 +02:00
* @ since 5.19 . 0
2023-08-08 22:45:55 +02:00
* @ link https :// dev . twitch . tv / docs / authentication
* @ link https :// discord . com / developers / docs / topics / oauth2
* @ link https :// docs . patreon . com / #step-1-registering-your-client
* @ link https :// developers . google . com / identity / protocols / oauth2
*/
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_process () {
// Abort if not on the /oauth2 route...
2023-08-28 10:43:04 +02:00
if ( is_null ( get_query_var ( FICTIONEER_OAUTH_ENDPOINT , null ) ) ) {
return ;
}
2023-08-08 22:45:55 +02:00
// Setup
2024-05-21 16:44:05 +02:00
$cookie = fictioneer_oauth2_get_cookie ();
$action = sanitize_key ( $_GET [ 'action' ] ? ? '' );
$state = sanitize_key ( $_GET [ 'state' ] ? ? '' );
$code = sanitize_text_field ( $_GET [ 'code' ] ? ? '' ); // Do not use sanitize_key()
$channel = sanitize_key ( $_GET [ 'channel' ] ? ? '' );
$merge = ( $_GET [ 'merge' ] ? ? $cookie [ 'merge' ] ? ? 0 ) ? 1 : 0 ;
$anchor = sanitize_key ( $_GET [ 'anchor' ] ? ? $cookie [ 'anchor' ] ? ? '' );
$return_url = wp_validate_redirect ( $_GET [ 'return_url' ] ? ? $cookie [ 'return_url' ] ? ? home_url (), home_url () );
$result = '' ;
// Verify!
if ( ! $state && ! wp_verify_nonce ( $_GET [ 'oauth_nonce' ] ? ? '' , 'authenticate' ) ) {
fictioneer_oauth2_terminate (
$return_url ,
array ( 'failure' => 'oauth_invalid_nonce' ),
$anchor
);
2023-08-08 22:45:55 +02:00
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Merge?
if ( ( ! is_user_logged_in () && $merge ) || ( is_user_logged_in () && ! $merge ) ) {
fictioneer_oauth2_terminate (
$return_url ,
array ( 'failure' => 'oauth_invalid_merge_request' ),
$anchor
);
2023-08-08 22:45:55 +02:00
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Login: Get code!
if ( $action === 'login' && ! $state ) {
fictioneer_oauth2_get_code (
array (
'channel' => $channel ,
'anchor' => $anchor ,
'return_url' => $return_url ,
'merge' => $merge ,
'merge_id' => get_current_user_id ()
)
);
2023-10-23 00:11:32 +02:00
}
2023-08-08 22:45:55 +02:00
2024-05-21 16:44:05 +02:00
// Cookie?
if ( ! $cookie || ! ( $cookie [ 'state' ] ? ? 0 ) || ! ( $cookie [ 'channel' ] ? ? 0 ) || ! ( $cookie [ 'return_url' ] ? ? 0 ) ) {
fictioneer_oauth2_terminate (
$cookie [ 'return_url' ],
array ( 'failure' => 'oauth_invalid_cookie' ),
$anchor
);
2023-10-23 00:11:32 +02:00
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// State?
if ( $cookie [ 'state' ] !== $state ) {
fictioneer_oauth2_terminate (
$cookie [ 'return_url' ],
array ( 'failure' => 'oauth_invalid_state' ),
$anchor
);
2023-01-21 01:31:34 +01:00
}
2023-10-23 00:11:32 +02:00
2024-05-21 16:44:05 +02:00
// Code?
if ( ! $code ) {
fictioneer_oauth2_terminate (
$cookie [ 'return_url' ],
array ( 'failure' => 'oauth_invalid_code' ),
$anchor
);
2023-10-26 21:21:36 +02:00
}
2024-05-21 16:44:05 +02:00
// Code: Get access token!
$token_response = fictioneer_oauth2_get_token (
FCN_OAUTH2_API_ENDPOINTS [ $cookie [ 'channel' ] ][ 'token' ],
2023-10-23 00:11:32 +02:00
array (
'grant_type' => 'authorization_code' ,
2024-05-21 16:44:05 +02:00
'client_id' => get_option ( " fictioneer_ { $cookie [ 'channel' ] } _client_id " ),
'client_secret' => get_option ( " fictioneer_ { $cookie [ 'channel' ] } _client_secret " ),
'redirect_uri' => get_site_url ( null , FICTIONEER_OAUTH_ENDPOINT ),
'code' => $code ,
'scope' => FCN_OAUTH2_API_ENDPOINTS [ $cookie [ 'channel' ] ][ 'scope' ]
2023-10-23 00:11:32 +02:00
)
);
2024-05-21 16:44:05 +02:00
// Error?
if ( is_wp_error ( $token_response ) ) {
fictioneer_oauth2_terminate (
$cookie [ 'return_url' ],
array ( 'failure' => $token_response -> get_error_code () ),
$anchor
);
2023-10-23 00:11:32 +02:00
}
2024-05-21 16:44:05 +02:00
// Token?
$access_token = isset ( $token_response -> access_token ) ? $token_response -> access_token : null ;
2023-10-23 00:11:32 +02:00
if ( ! $access_token ) {
2024-05-21 16:44:05 +02:00
fictioneer_oauth2_terminate (
$cookie [ 'return_url' ],
array ( 'failure' => 'oauth_token_missing' ),
$anchor
);
2023-10-23 00:11:32 +02:00
}
2024-05-21 16:44:05 +02:00
// Delegate to respective channel
switch ( $cookie [ 'channel' ] ) {
2023-10-23 00:11:32 +02:00
case 'discord' :
2024-05-24 21:16:15 +02:00
$result = fictioneer_oauth2_discord ( $token_response , $cookie );
2023-10-23 00:11:32 +02:00
break ;
2024-05-21 16:44:05 +02:00
case 'patreon' :
2024-05-24 21:16:15 +02:00
$result = fictioneer_oauth2_patreon ( $token_response , $cookie );
2023-10-23 00:11:32 +02:00
break ;
case 'google' :
2024-05-24 21:16:15 +02:00
$result = fictioneer_oauth2_google ( $token_response , $cookie );
2023-10-23 00:11:32 +02:00
break ;
2024-05-21 16:44:05 +02:00
case 'twitch' :
2024-05-24 21:16:15 +02:00
$result = fictioneer_oauth2_twitch ( $token_response , $cookie );
2024-05-21 16:44:05 +02:00
break ;
2024-05-25 10:28:19 +02:00
// case 'subscribestar':
// $result = fictioneer_oauth2_subscribestar( $token_response, $cookie );
// break;
2024-05-21 16:44:05 +02:00
default :
$result = 'oauth_invalid_channel' ;
2023-10-23 00:11:32 +02:00
}
2024-05-21 16:44:05 +02:00
// Error: Any
if ( ! in_array ( $result , [ 'merged_user' , 'new_user' , 'known_user' ] ) ) {
$return_url = add_query_arg ( 'failure' , $result , $return_url );
2023-10-23 00:11:32 +02:00
}
// Success: Merged
2024-05-21 16:44:05 +02:00
if ( $result === 'merged_user' ) {
$return_url = add_query_arg ( 'success' , 'oauth_merged_' . $cookie [ 'channel' ], $return_url );
2023-10-23 00:11:32 +02:00
}
// Success: New
2024-05-21 16:44:05 +02:00
if ( $result === 'new_user' ) {
2023-10-23 00:11:32 +02:00
$return_url = add_query_arg ( 'success' , 'oauth_new' , $return_url );
}
2024-05-21 16:44:05 +02:00
// Success: Known
if ( $result === 'known_user' ) {
$return_url = add_query_arg ( 'success' , 'oauth_known' , $return_url );
}
2023-10-23 00:11:32 +02:00
// Finish
2024-05-21 16:44:05 +02:00
fictioneer_oauth2_delete_cookie ();
wp_safe_redirect ( $return_url . ( $anchor ? '#' . $anchor : '' ) );
exit ;
2023-01-21 01:31:34 +01:00
}
2024-10-05 22:11:21 +02:00
add_action ( 'template_redirect' , 'fictioneer_oauth2_process' );
2023-01-21 01:31:34 +01:00
2024-04-26 17:29:01 +02:00
/**
2024-05-21 16:44:05 +02:00
* Log in or register user
*
* @ since 5.19 . 0
2024-04-26 17:29:01 +02:00
*
2024-05-21 16:44:05 +02:00
* @ param array $user_data Array of user data .
* @ param array $cookie The decrypted cookie data .
2024-04-26 17:29:01 +02:00
*/
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_make_user ( $user_data , $cookie ) {
2024-04-26 17:29:01 +02:00
// Setup
2024-05-21 16:44:05 +02:00
$channel = $user_data [ 'channel' ];
$meta_key = " fictioneer_ { $channel } _id_hash " ;
$channel_id = 'fcn_' . hash ( 'sha256' , sanitize_key ( $user_data [ 'uid' ] ) );
$username = sanitize_user ( $user_data [ 'username' ] );
$nickname = sanitize_user ( $user_data [ 'nickname' ] );
$email = sanitize_email ( $user_data [ 'email' ] );
$avatar = fictioneer_url_exists ( $user_data [ 'avatar' ] ) ? $user_data [ 'avatar' ] : null ;
$merge_id = absint ( $cookie [ 'merge_id' ] ? ? 0 );
$new = false ;
$merged = false ;
2024-05-23 20:45:03 +02:00
// Randomize username?
if ( get_option ( 'fictioneer_randomize_oauth_usernames' ) ) {
$username = fictioneer_get_random_username ();
$nickname = $username ;
}
2024-05-21 16:44:05 +02:00
// Look for existing user...
$wp_user = get_users ( array ( 'meta_key' => $meta_key , 'meta_value' => $channel_id , 'number' => 1 ) );
$wp_user = reset ( $wp_user );
// Check for faulty merge request
if ( ! is_user_logged_in () && $cookie [ 'merge' ] ) {
return 'oauth_invalid_merge_request' ;
2024-04-26 17:29:01 +02:00
}
2024-05-21 16:44:05 +02:00
if ( is_user_logged_in () && ! $cookie [ 'merge' ] ) {
return 'oauth_invalid_merge_request' ;
2024-04-26 17:29:01 +02:00
}
2024-05-21 16:44:05 +02:00
// If a valid merge has been requested...
if ( is_user_logged_in () && ! $wp_user && $cookie [ 'merge' ] ) {
if ( ! $wp_user && get_current_user_id () === $merge_id ) {
$wp_user = get_user_by ( 'id' , $merge_id );
$merged = true ;
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Cannot merge with non-existent user...
if ( is_user_logged_in () && ! $wp_user ) {
return 'oauth_invalid_merge_user' ;
}
2024-04-26 17:29:01 +02:00
}
2024-05-21 16:44:05 +02:00
// Cannot merged if already linked
if ( is_user_logged_in () && $wp_user && $merge_id !== $wp_user -> ID ) {
return 'oauth_already_linked' ;
2024-04-26 17:29:01 +02:00
}
2024-05-21 16:44:05 +02:00
// If still no user has been found, create a new one...
if ( ! is_a ( $wp_user , 'WP_User' ) ) {
// Find free username
if ( username_exists ( $username ) ) {
$alt_username = $username ;
$discriminator = 1 ;
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
while ( username_exists ( $alt_username ) ) {
$alt_username = $username . $discriminator ;
$discriminator ++ ;
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
$username = $alt_username ;
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Create user
$wp_user_id = wp_create_user ( $username , wp_generate_password ( 32 , true , true ), $email );
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Failure or success?
if ( is_wp_error ( $wp_user_id ) ) {
$error_code = $wp_user_id -> get_error_code ();
return $error_code === 'existing_user_email' ? 'oauth_email_taken' : $error_code ;
} else {
$wp_user = get_user_by ( 'id' , $wp_user_id );
$new = true ;
}
2024-04-26 17:29:01 +02:00
}
2024-05-21 16:44:05 +02:00
// Update user data
if ( is_a ( $wp_user , 'WP_User' ) ) {
// Current avatar
$current_avatar = $wp_user -> fictioneer_external_avatar_url ? : '' ;
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Set nickname and display name
if ( $new ) {
fictioneer_update_user_meta ( $wp_user -> ID , 'nickname' , $nickname );
wp_update_user ( array ( 'ID' => $wp_user -> ID , 'display_name' => $nickname ) );
2024-04-26 17:29:01 +02:00
}
2024-05-21 16:44:05 +02:00
// Set channel ID
if ( $new || $merged ) {
fictioneer_update_user_meta ( $wp_user -> ID , " fictioneer_ { $channel } _id_hash " , $channel_id );
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Update avatar
if ( $avatar !== $current_avatar && ! get_the_author_meta ( 'fictioneer_lock_avatar' , $wp_user -> ID ) ) {
fictioneer_update_user_meta ( $wp_user -> ID , 'fictioneer_external_avatar_url' , $avatar );
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Handle Patreon
if ( $channel === 'patreon' ) {
if ( isset ( $user_data [ 'tiers' ] ) && count ( $user_data [ 'tiers' ] ) > 0 ) {
fictioneer_update_user_meta ( $wp_user -> ID , 'fictioneer_patreon_tiers' , $user_data [ 'tiers' ] );
} else {
delete_user_meta ( $wp_user -> ID , 'fictioneer_patreon_tiers' );
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
if ( isset ( $user_data [ 'membership' ] ) && ! empty ( $user_data [ 'membership' ] ) ) {
fictioneer_update_user_meta ( $wp_user -> ID , 'fictioneer_patreon_membership' , $user_data [ 'membership' ] );
} else {
delete_user_meta ( $wp_user -> ID , 'fictioneer_patreon_membership' );
}
}
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Login
if ( ! is_user_logged_in () ) {
wp_clear_auth_cookie ();
wp_set_current_user ( $wp_user -> ID );
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Allow login to last three days
add_filter ( 'auth_cookie_expiration' , function ( $length ) {
2024-11-16 14:06:59 +01:00
return FICTIONEER_OAUTH_COOKIE_EXPIRATION ;
2024-05-21 16:44:05 +02:00
});
2024-04-26 17:29:01 +02:00
2024-05-21 16:44:05 +02:00
// Set authentication cookie
wp_set_auth_cookie ( $wp_user -> ID , true );
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Action
do_action (
'fictioneer_after_oauth_user' ,
$wp_user ,
2023-08-05 22:53:20 +02:00
array (
2024-05-21 16:44:05 +02:00
'channel' => $user_data [ 'channel' ],
'uid' => $user_data [ 'uid' ],
'username' => $user_data [ 'username' ],
'nickname' => $user_data [ 'nickname' ],
'email' => $user_data [ 'email' ],
'avatar_url' => $user_data [ 'avatar' ],
2025-01-17 13:03:32 +01:00
'patreon_tiers' => $user_data [ 'tiers' ] ? ? [],
2025-01-17 13:05:40 +01:00
'patreon_membership' => $user_data [ 'membership' ] ? ? [],
2024-05-21 16:44:05 +02:00
'new' => $new ,
'merged' => $merged
2023-01-21 01:31:34 +01:00
)
);
2024-05-21 16:44:05 +02:00
// Return result
if ( is_user_logged_in () ) {
if ( $new ) {
return 'new_user' ;
}
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
if ( $merged ) {
return 'merged_user' ;
}
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
return 'known_user' ;
2023-08-05 22:53:20 +02:00
}
2023-01-21 01:31:34 +01:00
}
2024-05-21 16:44:05 +02:00
// Something went very wrong
return 'no_user_found_or_created' ;
2023-01-21 01:31:34 +01:00
}
// =============================================================================
2024-05-21 16:44:05 +02:00
// HELPERS
2023-01-21 01:31:34 +01:00
// =============================================================================
2024-05-21 16:44:05 +02:00
/**
* Terminate the OAuth 2.0 script and redirect back
*
* @ since 5.19 . 0
*
* @ param string $return_url Optional . URL to return to .
* @ param array $query_args Optional . Additional query arguments for the redirect .
* @ param string $anchor Optional . Anchor for the return URL .
*/
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_terminate ( $return_url = null , $query_args = [], $anchor = null ) {
// Delete cookie
fictioneer_oauth2_delete_cookie ();
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
// Redirect and terminate
wp_safe_redirect ( add_query_arg ( $query_args , $return_url ? ? home_url () ) . ( $anchor ? '#' . $anchor : '' ) );
exit ;
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
/**
* Outputs a formatted error message and stops the script
*
* @ since 5.5 . 2
* @ since 5.7 . 5 - Refactored .
* @ since 5.19 . 0 - Refactored again .
*
* @ param string $message The error message .
* @ param string $title Optional . Title of the error page . Defaults to 'Error' .
*/
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth_die ( $message , $title = 'Error' ) {
// Delete cookie
fictioneer_oauth2_delete_cookie ();
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
wp_die (
'<h1 style="margin-top: 0;">' . $title . '</h1>' .
'<p><pre>' . print_r ( $message , true ) . '</pre></p>' .
'<p>The good news is, nothing has happened to your account. The bad new is, something is not working. Please try again later or contact an administrator for help. <a href="' . home_url () . '">Back to site</a></p>' ,
$title
);
2023-01-21 01:31:34 +01:00
}
2024-05-21 16:44:05 +02:00
/**
* Get the OAuth 2.0 cookie contents
*
* @ since 5.19 . 0
*
* @ return array | null The cookie as array or null on failure .
*/
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_get_cookie () {
if ( isset ( $_COOKIE [ 'fictioneer_oauth' ] ) ) {
return fictioneer_decrypt ( $_COOKIE [ 'fictioneer_oauth' ] ) ? : null ;
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
return null ;
}
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
/**
* Deleted the OAuth 2.0 cookie
*
* @ since 5.19 . 0
*/
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_delete_cookie () {
setcookie (
'fictioneer_oauth' ,
'' ,
array (
'expires' => time () - 3600 ,
'path' => '/' ,
'domain' => '' ,
'secure' => is_ssl (),
'httponly' => true ,
'samesite' => 'Lax' // Necessary because of redirects
)
);
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
/**
* Get code from OAuth 2.0 provider
*
* @ since 5.19 . 0
*
* @ param array $args {
* Array of arguments .
*
* @ type string $channel The targeted provider .
* @ type string $return_url The return URL .
* @ type string $anchor Optional . Anchor for the return URL .
* @ type bool $merge Optional . Whether this is a merge request .
* @ type int $merge_id ID of the current user or 0.
* }
*/
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_get_code ( $args ) {
// Setup
$client_id = get_option ( " fictioneer_ { $args [ 'channel' ] } _client_id " );
$client_secret = get_option ( " fictioneer_ { $args [ 'channel' ] } _client_secret " );
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Abort if...
if ( ! $client_id || ! $client_secret ) {
fictioneer_oauth2_terminate ( $args [ 'return_url' ] );
2023-01-21 01:31:34 +01:00
}
2024-05-21 16:44:05 +02:00
// Prepare request
$params = array (
'response_type' => 'code' ,
'client_id' => $client_id ,
'state' => hash ( 'sha256' , microtime ( TRUE ) . random_bytes ( 15 ) . $_SERVER [ 'REMOTE_ADDR' ] ),
'scope' => FCN_OAUTH2_API_ENDPOINTS [ $args [ 'channel' ] ][ 'scope' ],
'redirect_uri' => get_site_url ( null , FICTIONEER_OAUTH_ENDPOINT ),
'force_verify' => 'true' ,
'prompt' => 'consent' ,
'access_type' => 'offline'
);
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Set cookie
$value = fictioneer_encrypt (
array (
'state' => $params [ 'state' ],
'channel' => $args [ 'channel' ],
'return_url' => $args [ 'return_url' ],
'anchor' => $args [ 'anchor' ],
'merge' => $args [ 'merge' ],
'merge_id' => $args [ 'merge_id' ]
)
);
if ( $value ) {
setcookie (
'fictioneer_oauth' ,
$value ,
2023-08-05 22:53:20 +02:00
array (
2024-05-21 16:44:05 +02:00
'expires' => time () + 300 ,
'path' => '/' ,
'domain' => '' ,
'secure' => is_ssl (),
'httponly' => true ,
'samesite' => 'Lax' // Necessary because of redirects
2023-01-21 01:31:34 +01:00
)
);
2024-05-21 16:44:05 +02:00
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Request code
wp_redirect ( add_query_arg ( $params , FCN_OAUTH2_API_ENDPOINTS [ $args [ 'channel' ] ][ 'login' ] ) );
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
// Terminate
exit ();
}
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
/**
* Get the OAuth 2.0 access token
*
* @ since 5.19 . 0
*
* @ param string $url URL to make the API request to .
* @ param array $body Post body .
* @ param array $headers Optional . Array of additional header arguments .
*
2024-05-23 22:27:32 +02:00
* @ return object | WP_Error Decoded JSON result or WP_Error on failure .
2024-05-21 16:44:05 +02:00
*/
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_get_token ( $url , $body , $headers = [] ) {
// Params
$args = array (
'headers' => array_merge ( array ( 'Accept' => 'application/json' ), $headers ),
'body' => $body ,
'timeout' => 10 ,
'blocking' => true ,
'reject_unsafe_urls' => true ,
'data_format' => 'body' ,
'limit_response_size' => 4096
);
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
// Request
$response = wp_remote_post ( $url , $args );
2023-08-05 22:53:20 +02:00
2024-05-21 16:44:05 +02:00
// Error?
if ( is_wp_error ( $response ) ) {
return $response ;
}
2024-04-29 00:29:11 +02:00
2024-05-21 16:44:05 +02:00
// Return decoded body
return json_decode ( wp_remote_retrieve_body ( $response ) );
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
/**
* Revoke an OAuth 2.0 access token
*
* @ since 4.0 . 0
* @ since 5.7 . 5 - Refactored .
* @ since 5.19 . 0 - Refactored again .
*
* @ param string $url URL to make the API request to .
* @ param array $post Post body .
*
* @ return string HTTP response code .
*/
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
function fictioneer_oauth2_revoke_token ( $url , $body ) {
// Params
$args = array (
'headers' => array ( 'Content-Type' => 'application/x-www-form-urlencoded' ),
'body' => $body
);
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Request
$response = wp_remote_post ( $url , $args );
// Error?
if ( is_wp_error ( $response ) ) {
return 500 ; // Internal Server Error
2023-01-21 01:31:34 +01:00
}
2024-05-21 16:44:05 +02:00
// Return response code
return wp_remote_retrieve_response_code ( $response );
2023-01-21 01:31:34 +01:00
}
2024-05-24 21:16:15 +02:00
/**
* Retrieve and decode user data response
*
* @ since 5.19 . 0
*
* @ param object $response API response .
*
* @ return object User data object .
*/
function fictioneer_oauth2_retrieve_user_body ( $response ) {
// Check retrieved response
if ( is_wp_error ( $response ) ) {
fictioneer_oauth_die ( implode ( '<br>' , $response -> get_error_messages () ) );
} else {
$user = json_decode ( wp_remote_retrieve_body ( $response ) );
}
// User data successfully retrieved?
if ( empty ( $user ) || json_last_error () !== JSON_ERROR_NONE ) {
fictioneer_oauth_die ( wp_remote_retrieve_body ( $response ) );
}
// Error?
$response_code = wp_remote_retrieve_response_code ( $response );
if ( $response_code < 200 || $response_code >= 300 ) {
fictioneer_oauth_die ( wp_remote_retrieve_body ( $response ), 'Error ' . $response );
}
// All clear
return $user ;
}
2023-01-21 01:31:34 +01:00
// =============================================================================
2024-05-21 16:44:05 +02:00
// PROVIDERS
2023-01-21 01:31:34 +01:00
// =============================================================================
2024-05-21 16:44:05 +02:00
/**
* Perform OAuth 2.0 authentication for Patreon
*
* @ since 5.19 . 0
*
* @ param object $token_response Token request response .
* @ param array $cookie Decrypted data of the cookie .
*
* @ return string Result code from fictioneer_oauth2_make_user () .
*/
2023-01-21 01:31:34 +01:00
2024-05-24 21:16:15 +02:00
function fictioneer_oauth2_patreon ( $token_response , $cookie ) {
2024-05-21 16:44:05 +02:00
// Build params
2024-08-16 13:31:17 +02:00
$params = '?fields' . urlencode ( '[user]' ) . '=email,first_name,image_url,is_email_verified' ;
$params .= '&fields' . urlencode ( '[tier]' ) . '=title,amount_cents,published,description' ;
2025-01-17 13:05:40 +01:00
$params .= '&fields' . urlencode ( '[member]' ) . '=lifetime_support_cents,campaign_lifetime_support_cents,last_charge_date,last_charge_status,next_charge_date,patron_status' ;
2024-05-21 16:44:05 +02:00
$params .= '&include=memberships.currently_entitled_tiers' ;
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Retrieve user data from Patreon
$user_response = wp_remote_get (
FCN_OAUTH2_API_ENDPOINTS [ 'patreon' ][ 'user' ] . $params ,
array (
'headers' => array (
'Authorization' => 'Bearer ' . $token_response -> access_token
)
)
);
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Check retrieved user response
2024-05-24 21:16:15 +02:00
$user = fictioneer_oauth2_retrieve_user_body ( $user_response );
2024-05-24 17:11:29 +02:00
2024-05-21 16:44:05 +02:00
if ( ! isset ( $user -> data ) ) {
fictioneer_oauth_die ( 'Data node not found.' );
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
if ( ! isset ( $user -> data -> attributes ) ) {
fictioneer_oauth_die ( 'Attributes node not found.' );
}
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
if ( ! isset ( $user -> data -> attributes -> is_email_verified ) || ! $user -> data -> attributes -> is_email_verified ) {
fictioneer_oauth_die ( 'Email not verified.' );
}
2023-08-23 22:40:57 +02:00
2024-05-21 16:44:05 +02:00
// Extract membership data
$tiers = [];
$tier_ids = [];
$membership = [];
if ( isset ( $user -> included ) ) {
// Tiers data
2024-06-17 22:12:39 +02:00
foreach ( $user -> included as $node ) {
2024-05-21 16:44:05 +02:00
if ( isset ( $node -> type ) && $node -> type === 'tier' ) {
2025-01-17 13:05:40 +01:00
$tiers [ $node -> id ] = array (
2024-05-21 16:44:05 +02:00
'tier' => sanitize_text_field ( $node -> attributes -> title ),
'title' => sanitize_text_field ( $node -> attributes -> title ),
'description' => wp_kses_post ( $node -> attributes -> description ? ? '' ),
'published' => filter_var ( $node -> attributes -> published ? ? 0 , FILTER_VALIDATE_BOOLEAN , FILTER_NULL_ON_FAILURE ),
'amount_cents' => absint ( $node -> attributes -> amount_cents ? ? 0 ),
2025-01-17 13:05:40 +01:00
'timestamp' => current_time ( 'U' , true ),
2024-05-21 16:44:05 +02:00
'id' => $node -> id
);
$tier_ids [] = $node -> id ;
2023-01-21 01:31:34 +01:00
}
}
2024-05-21 16:44:05 +02:00
// Membership data
2024-06-17 22:12:39 +02:00
foreach ( $user -> included as $node ) {
2024-05-21 16:44:05 +02:00
if (
isset ( $node -> type ) &&
$node -> type === 'member' &&
isset ( $node -> attributes ) &&
isset ( $node -> relationships -> currently_entitled_tiers -> data ) &&
in_array ( $node -> relationships -> currently_entitled_tiers -> data [ 0 ] -> id , $tier_ids )
) {
2025-01-17 13:05:40 +01:00
$membership [ 'lifetime_support_cents' ] = $node -> attributes -> lifetime_support_cents ? ? $node -> attributes -> campaign_lifetime_support_cents ? ? 0 ;
2024-05-21 16:44:05 +02:00
$membership [ 'last_charge_date' ] = $node -> attributes -> last_charge_date ? ? null ;
$membership [ 'last_charge_status' ] = $node -> attributes -> last_charge_status ? ? null ;
$membership [ 'next_charge_date' ] = $node -> attributes -> next_charge_date ? ? null ;
$membership [ 'patron_status' ] = $node -> attributes -> patron_status ? ? null ;
break ;
2023-01-21 01:31:34 +01:00
}
}
}
2024-05-21 16:44:05 +02:00
/* Patreon does not have a function to revoke a token (2024/05/21). */
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Login or register user
return fictioneer_oauth2_make_user (
array (
'channel' => 'patreon' ,
'uid' => $user -> data -> id ,
'email' => $user -> data -> attributes -> email ,
'username' => $user -> data -> attributes -> first_name ,
'nickname' => $user -> data -> attributes -> first_name ,
'avatar' => esc_url_raw ( $user -> data -> attributes -> image_url ? ? '' ),
'tiers' => $tiers ,
'membership' => $membership
),
$cookie
);
}
2023-10-23 00:11:32 +02:00
2024-05-21 16:44:05 +02:00
/**
* Perform OAuth 2.0 authentication for Discord
*
* @ since 5.19 . 0
*
* @ param object $token_response Token request response .
* @ param array $cookie Decrypted data of the cookie .
*
* @ return string Result code from fictioneer_oauth2_make_user () .
*/
2023-10-23 00:11:32 +02:00
2024-05-24 21:16:15 +02:00
function fictioneer_oauth2_discord ( $token_response , $cookie ) {
2024-05-21 16:44:05 +02:00
// Get user data
$user_response = wp_remote_get (
FCN_OAUTH2_API_ENDPOINTS [ 'discord' ][ 'user' ],
array (
'headers' => array (
'Authorization' => 'Bearer ' . $token_response -> access_token ,
'Client-ID' => get_option ( 'fictioneer_discord_client_id' )
2023-10-26 21:21:36 +02:00
)
2024-05-21 16:44:05 +02:00
)
);
2023-10-26 21:21:36 +02:00
2024-05-21 16:44:05 +02:00
// Check retrieved user response
2024-05-24 21:16:15 +02:00
$user = fictioneer_oauth2_retrieve_user_body ( $user_response );
2024-05-24 17:11:29 +02:00
2024-05-21 16:44:05 +02:00
// Account verified?
if ( ! isset ( $user -> verified ) || ! $user -> verified ) {
fictioneer_oauth_die ( 'Account not verified.' );
}
2023-02-17 16:07:40 +01:00
2024-05-21 16:44:05 +02:00
// Revoke token since it's not needed anymore
fictioneer_oauth2_revoke_token (
FCN_OAUTH2_API_ENDPOINTS [ 'discord' ][ 'revoke' ],
array (
'token' => $token_response -> access_token ,
'token_type_hint' => 'access_token' ,
'client_id' => get_option ( 'fictioneer_discord_client_id' ),
'client_secret' => get_option ( 'fictioneer_discord_client_secret' )
)
);
2023-10-26 21:21:36 +02:00
2024-05-21 16:44:05 +02:00
// Login or register user
return fictioneer_oauth2_make_user (
array (
'channel' => 'discord' ,
'uid' => $user -> id ,
'email' => $user -> email ,
'username' => $user -> username . ( $user -> discriminator ? ? '' ),
'nickname' => $user -> username ,
'avatar' => esc_url_raw ( " https://cdn.discordapp.com/avatars/ { $user -> id } / { $user -> avatar } .png " )
),
$cookie
);
2023-02-17 16:07:40 +01:00
}
2024-05-21 16:44:05 +02:00
/**
* Perform OAuth 2.0 authentication for Google
*
* @ since 5.19 . 0
*
* @ param object $token_response Token request response .
* @ param array $cookie Decrypted data of the cookie .
*
* @ return string Result code from fictioneer_oauth2_make_user () .
*/
2023-10-23 00:11:32 +02:00
2024-05-24 21:16:15 +02:00
function fictioneer_oauth2_google ( $token_response , $cookie ) {
2024-05-21 16:44:05 +02:00
// Retrieve user data from Google
$user_response = wp_remote_get (
FCN_OAUTH2_API_ENDPOINTS [ 'google' ][ 'user' ],
array (
'headers' => array (
'Authorization' => 'Bearer ' . $token_response -> access_token ,
'Client-ID' => get_option ( 'fictioneer_google_client_id' )
2023-10-26 21:21:36 +02:00
)
2024-05-21 16:44:05 +02:00
)
);
2023-10-23 00:11:32 +02:00
2024-05-21 16:44:05 +02:00
// Check retrieved user response
2024-05-24 21:16:15 +02:00
$user = fictioneer_oauth2_retrieve_user_body ( $user_response );
2024-05-24 17:11:29 +02:00
2024-05-21 16:44:05 +02:00
// Account verified?
if ( ! isset ( $user -> verified_email ) || ! $user -> verified_email ) {
fictioneer_oauth_die ( 'Email not verified.' );
2023-01-21 01:31:34 +01:00
}
2024-05-21 16:44:05 +02:00
// Revoke token since it's not needed anymore
fictioneer_oauth2_revoke_token (
FCN_OAUTH2_API_ENDPOINTS [ 'google' ][ 'revoke' ],
array (
'token' => $token_response -> access_token
)
);
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Generate random username (because Google uses real names)
$name = fictioneer_get_random_username ();
2023-01-21 01:31:34 +01:00
2024-05-21 16:44:05 +02:00
// Login or register user
return fictioneer_oauth2_make_user (
array (
'channel' => 'google' ,
'uid' => $user -> id ,
'email' => $user -> email ,
'username' => $name ,
'nickname' => $name ,
'avatar' => esc_url_raw ( $user -> picture ? ? '' )
),
$cookie
);
2023-01-21 01:31:34 +01:00
}
2024-05-21 16:44:05 +02:00
/**
* Perform OAuth 2.0 authentication for Twitch
*
* @ since 5.19 . 0
*
* @ param object $token_response Token request response .
* @ param array $cookie Decrypted data of the cookie .
*
* @ return string Result code from fictioneer_oauth2_make_user () .
*/
2023-10-23 00:11:32 +02:00
2024-05-24 21:16:15 +02:00
function fictioneer_oauth2_twitch ( $token_response , $cookie ) {
2024-05-21 16:44:05 +02:00
// Retrieve user data from Twitch
$user_response = wp_remote_get (
FCN_OAUTH2_API_ENDPOINTS [ 'twitch' ][ 'user' ],
array (
'headers' => array (
'Authorization' => 'Bearer ' . $token_response -> access_token ,
'Client-ID' => get_option ( 'fictioneer_twitch_client_id' )
2023-10-26 21:21:36 +02:00
)
2024-05-21 16:44:05 +02:00
)
);
2023-10-26 21:21:36 +02:00
2024-05-21 16:44:05 +02:00
// Check retrieved user response
2024-05-24 21:16:15 +02:00
$user = fictioneer_oauth2_retrieve_user_body ( $user_response );
2024-05-24 17:11:29 +02:00
2024-05-21 16:44:05 +02:00
// Revoke token since it's not needed anymore
fictioneer_oauth2_revoke_token (
FCN_OAUTH2_API_ENDPOINTS [ 'twitch' ][ 'revoke' ],
array (
'token' => $token_response -> access_token ,
'token_type_hint' => 'access_token' ,
'client_id' => get_option ( 'fictioneer_twitch_client_id' )
)
);
// Login or register user
return fictioneer_oauth2_make_user (
array (
'channel' => 'twitch' ,
'uid' => $user -> data [ 0 ] -> id ,
'email' => $user -> data [ 0 ] -> email ,
'username' => $user -> data [ 0 ] -> login ,
'nickname' => $user -> data [ 0 ] -> display_name ,
'avatar' => esc_url_raw ( $user -> data [ 0 ] -> profile_image_url ? ? '' )
),
$cookie
);
2023-10-23 00:11:32 +02:00
}
2024-05-21 16:44:05 +02:00
/**
* Perform OAuth 2.0 authentication for SubscribeStar
*
* @ since 5.19 . 0
*
* @ param object $token_response Token request response .
* @ param array $cookie Decrypted data of the cookie .
*
* @ return string Result code from fictioneer_oauth2_make_user () .
*/
2023-10-23 00:11:32 +02:00
2024-05-24 21:18:22 +02:00
function fictioneer_oauth2_subscribestar ( $token_response , $cookie ) {
// GraphQL query
$query = " { user { id name email email_verified avatar_url } subscriber { subscription { price } } } " ;
// Retrieve user data from SubscribeStar
$user_response = wp_remote_post (
FCN_OAUTH2_API_ENDPOINTS [ 'subscribestar' ][ 'api' ],
array (
'headers' => array (
'Accept' => 'application/json' ,
'Authorization' => 'Bearer ' . $token_response -> access_token
),
'body' => array ( 'query' => $query ),
'blocking' => true ,
'reject_unsafe_urls' => true ,
'data_format' => 'body'
)
);
fictioneer_oauth2_delete_cookie ();
wp_die ( json_encode ( $user_response ) );
2023-01-21 01:31:34 +01:00
}