2024-12-08 14:31:47 +01:00

410 lines
12 KiB
PHP

<?php
// =============================================================================
// GET USER DATA - AJAX
// =============================================================================
/**
* Get relevant user data via AJAX
*
* @since 5.7.0
*/
function fictioneer_ajax_get_user_data() {
// Rate limit
fictioneer_check_rate_limit( 'fictioneer_ajax_get_user_data', 30 );
// Setup
$logged_in = is_user_logged_in();
$user = wp_get_current_user();
$nonce = wp_create_nonce( 'fictioneer_nonce' );
$data = array(
'user_id' => $user->ID,
'timestamp' => time() * 1000, // Compatible with Date.now() in JavaScript
'loggedIn' => $logged_in,
'follows' => false,
'reminders' => false,
'checkmarks' => false,
'bookmarks' => '{}',
'fingerprint' => fictioneer_get_user_fingerprint( $user->ID ),
'avatarUrl' => '',
'isAdmin' => false,
'isModerator' => false,
'isAuthor' => false,
'isEditor' => false,
'nonce' => $nonce,
'nonceHtml' => '<input id="fictioneer-ajax-nonce" name="fictioneer-ajax-nonce" type="hidden" value="' . $nonce . '">'
);
if ( $logged_in ) {
$data = array_merge(
$data,
array(
'isAdmin' => fictioneer_is_admin( $user->ID ),
'isModerator' => fictioneer_is_moderator( $user->ID ),
'isAuthor' => fictioneer_is_author( $user->ID ),
'isEditor' => fictioneer_is_editor( $user->ID ),
'avatarUrl' => get_avatar_url( $user->ID )
)
);
}
// --- FOLLOWS ---------------------------------------------------------------
if ( $logged_in && get_option( 'fictioneer_enable_follows' ) ) {
$follows = fictioneer_load_follows( $user );
$follows['new'] = false;
// New notifications?
if ( count( $follows['data'] ) > 0 ) {
$latest_count = fictioneer_query_new_followed_chapters_count(
array_keys( $follows['data'] ),
wp_date( 'Y-m-d H:i:s', $follows['seen'] / 1000 )
);
if ( $latest_count > 0 ) {
$follows['new'] = $latest_count;
}
}
$data['follows'] = $follows;
}
// --- REMINDERS -------------------------------------------------------------
if ( $logged_in && get_option( 'fictioneer_enable_reminders' ) ) {
$data['reminders'] = fictioneer_load_reminders( $user );
}
// --- CHECKMARKS ------------------------------------------------------------
if ( $logged_in && get_option( 'fictioneer_enable_checkmarks' ) ) {
$data['checkmarks'] = fictioneer_load_checkmarks( $user );
}
// --- BOOKMARKS -------------------------------------------------------------
if ( $logged_in && get_option( 'fictioneer_enable_bookmarks' ) ) {
$bookmarks = get_user_meta( $user->ID, 'fictioneer_bookmarks', true );
$data['bookmarks'] = $bookmarks ? $bookmarks : '{}';
}
// --- FILTER ----------------------------------------------------------------
$data = apply_filters( 'fictioneer_filter_ajax_get_user_data', $data, $user );
// ---------------------------------------------------------------------------
// Response
wp_send_json_success( $data );
}
add_action( 'wp_ajax_fictioneer_ajax_get_user_data', 'fictioneer_ajax_get_user_data' );
add_action( 'wp_ajax_nopriv_fictioneer_ajax_get_user_data', 'fictioneer_ajax_get_user_data' );
// =============================================================================
// SELF-DELETE USER - AJAX
// =============================================================================
/**
* Delete an user's account via AJAX
*
* @since 4.5.0
*/
function fictioneer_ajax_delete_my_account() {
// Setup
$sender_id = isset( $_POST['id'] ) ? fictioneer_validate_id( $_POST['id'] ) : false;
$current_user = fictioneer_get_validated_ajax_user( 'nonce', 'fictioneer_delete_account' );
// Extra validations
if (
! $sender_id ||
! $current_user ||
$sender_id !== $current_user->ID ||
$current_user->ID === 1 ||
in_array( 'administrator', $current_user->roles ) ||
! current_user_can( 'fcn_allow_self_delete' )
) {
wp_send_json_error(
array(
'failure' => __( 'User role too high.', 'fictioneer' ),
'button' => __( 'Denied', 'fictioneer' )
)
);
}
// Delete user
if ( fictioneer_delete_my_account() ) {
wp_send_json_success();
} else {
wp_send_json_error(
array(
'failure' => __( 'Database error. Account could not be deleted.', 'fictioneer' ),
'button' => __( 'Failure', 'fictioneer' )
)
);
}
}
if ( current_user_can( 'fcn_allow_self_delete' ) ) {
add_action( 'wp_ajax_fictioneer_ajax_delete_my_account', 'fictioneer_ajax_delete_my_account' );
}
// =============================================================================
// SELF-CLEAR USER COMMENT SUBSCRIPTIONS - AJAX
// =============================================================================
/**
* Clears all the user's comment subscriptions via AJAX
*
* Changes the comment notification validation timestamp, effectively terminating
* all associated previous comment subscriptions. This is far cheaper than looping
* and updating all comments.
*
* @since 5.0.0
*/
function fictioneer_ajax_clear_my_comment_subscriptions() {
// Setup and validations
$user = fictioneer_get_validated_ajax_user( 'nonce', 'fictioneer_clear_comment_subscriptions' );
if ( ! $user ) {
wp_send_json_error( array( 'error' => 'Request did not pass validation.' ) );
}
// Change validator
if ( update_user_meta( $user->ID, 'fictioneer_comment_reply_validator', time() ) ) {
wp_send_json_success( array( 'success' => __( 'Data has been cleared.', 'fictioneer' ) ) );
} else {
wp_send_json_error( array( 'failure' => __( 'Database error. Comment subscriptions could not be cleared.', 'fictioneer' ) ) );
}
}
add_action( 'wp_ajax_fictioneer_ajax_clear_my_comment_subscriptions', 'fictioneer_ajax_clear_my_comment_subscriptions' );
// =============================================================================
// SELF-CLEAR USER COMMENTS - AJAX
// =============================================================================
/**
* Clears all the user's comments via AJAX
*
* Queries all comments of the user and overrides the comment data with
* garbage, preserving the comment thread integrity.
*
* @since 5.0.0
*/
function fictioneer_ajax_clear_my_comments() {
// Setup and validations
$user = fictioneer_get_validated_ajax_user( 'nonce', 'fictioneer_clear_comments' );
if ( ! $user ) {
wp_send_json_error( array( 'error' => 'Request did not pass validation.' ) );
}
if (
fictioneer_is_admin( $user->ID ) ||
fictioneer_is_author( $user->ID ) ||
fictioneer_is_moderator( $user->ID )
) {
wp_send_json_error( array( 'failure' => __( 'User role too high. Please ask an administrator.', 'fictioneer' ) ) );
}
// Soft-delete comments
$result = fictioneer_soft_delete_user_comments( $user->ID );
if ( ! $result ) {
wp_send_json_error( array( 'error' => 'Database error. No comments found.' ) );
}
if ( $result['failure'] && ! $result['complete'] ) {
wp_send_json_error(
array(
'failure' => sprintf(
__( 'Partial database error. Only %1$s of %2$s comments could be cleared.', 'fictioneer' ),
$result['updated_count'],
$result['comment_count']
)
)
);
}
if ( $result['failure'] ) {
wp_send_json_error( array( 'failure' => __( 'Database error. Comments could not be cleared.', 'fictioneer' ) ) );
}
// Report success
wp_send_json_success(
array(
'success' => sprintf(
__( '%s comments have been cleared.', 'fictioneer' ),
$result['updated_count']
)
)
);
}
add_action( 'wp_ajax_fictioneer_ajax_clear_my_comments', 'fictioneer_ajax_clear_my_comments' );
// =============================================================================
// SELF-UNSET OAUTH CONNECTIONS - AJAX
// =============================================================================
/**
* Unset one of the user's OAuth bindings via AJAX
*
* @since 4.0.0
*/
function fictioneer_ajax_unset_my_oauth() {
// Setup
$sender_id = fictioneer_validate_id( $_POST['id'] ?? 0 ) ?: false;
$channel = sanitize_text_field( $_POST['channel'] ?? 0 ) ?: false;
$user = fictioneer_get_validated_ajax_user( 'nonce', 'fictioneer_unset_oauth' );
// Validations
if (
! $user ||
! $sender_id ||
! $channel ||
! in_array( $channel, ['discord', 'twitch', 'patreon', 'google'] ) ||
$sender_id !== $user->ID
) {
wp_send_json_error( array( 'error' => 'Request did not pass validation.' ) );
}
// Delete connection
if ( delete_user_meta( $user->ID, "fictioneer_{$channel}_id_hash" ) ) {
// Remove tiers
if ( $channel === 'patreon' ) {
delete_user_meta( $user->ID, 'fictioneer_patreon_tiers' );
}
// Success response
wp_send_json_success(
array(
'channel' => $channel,
'label' => sprintf(
__( '%s disconnected', 'fictioneer' ),
ucfirst( $channel )
)
)
);
} else {
// Failure response
wp_send_json_error( array( 'failure' => __( 'Database error. Please ask an administrator.', 'fictioneer' ) ) );
}
}
add_action( 'wp_ajax_fictioneer_ajax_unset_my_oauth', 'fictioneer_ajax_unset_my_oauth' );
// =============================================================================
// AJAX: CLEAR COOKIES
// =============================================================================
/**
* Clear all cookies and log out.
*
* @since 5.27.0
*/
function fictioneer_ajax_clear_cookies() {
// Setup and validations
$user = fictioneer_get_validated_ajax_user();
if ( ! $user ) {
wp_send_json_error(
array(
'error' => 'Request did not pass validation.',
'failure' => __( 'There has been an error. Try again later and if the problem persists, contact an administrator.', 'fictioneer' )
)
);
}
// Logout
wp_logout();
// Clear remaining cookies
if ( isset( $_SERVER['HTTP_COOKIE'] ) ) {
$cookies = explode( ';', $_SERVER['HTTP_COOKIE'] );
foreach ($cookies as $cookie) {
$parts = explode( '=', $cookie );
$name = trim( $parts[0] );
setcookie( $name, '', time() - 3600, '/' );
unset( $_COOKIE[ $name ] );
}
}
// Response
wp_send_json_success(
array(
'success' => __( 'Cookies and local storage have been cleared. To keep it that way, you should leave the site.', 'fictioneer' )
)
);
}
add_action( 'wp_ajax_fictioneer_ajax_clear_cookies', 'fictioneer_ajax_clear_cookies' );
// =============================================================================
// SAVE BOOKMARKS FOR USERS - AJAX
// =============================================================================
/**
* Save bookmarks JSON for user via AJAX
*
* Note: Bookmarks are not evaluated server-side, only stored as JSON string.
* Everything else happens client-side.
*
* @since 4.0.0
*/
function fictioneer_ajax_save_bookmarks() {
// Enabled?
if ( ! get_option( 'fictioneer_enable_bookmarks' ) ) {
wp_send_json_error( null, 403 );
}
// Setup and validations
$user = fictioneer_get_validated_ajax_user();
if ( ! $user ) {
wp_send_json_error( array( 'error' => 'Request did not pass validation.' ) );
}
if ( empty( $_POST['bookmarks'] ) ) {
wp_send_json_error( array( 'error' => 'Missing arguments.' ) );
}
// Valid?
$bookmarks = sanitize_text_field( $_POST['bookmarks'] );
if ( $bookmarks && fictioneer_is_valid_json( wp_unslash( $bookmarks ) ) ) {
// Inspect
$decoded = json_decode( wp_unslash( $bookmarks ), true );
if ( ! $decoded || ! isset( $decoded['data'] ) ) {
wp_send_json_error( array( 'error' => 'Invalid JSON.' ) );
}
// Update and response (uses wp_slash/wp_unslash internally)
$old_bookmarks = get_user_meta( $user->ID, 'fictioneer_bookmarks', true );
if ( wp_unslash( $bookmarks ) === $old_bookmarks ) {
wp_send_json_success(); // Nothing to update
}
if ( update_user_meta( $user->ID, 'fictioneer_bookmarks', $bookmarks ) ) {
wp_send_json_success();
} else {
wp_send_json_error( array( 'error' => 'Bookmarks could not be updated.' ) );
}
}
// Something went wrong if we end up here...
wp_send_json_error( array( 'error' => 'An unknown error occurred.' ) );
}
if ( get_option( 'fictioneer_enable_bookmarks' ) ) {
add_action( 'wp_ajax_fictioneer_ajax_save_bookmarks', 'fictioneer_ajax_save_bookmarks' );
}