Introduce FFCNR
This commit is contained in:
parent
59de1db9d1
commit
349e21f9a4
@ -1491,6 +1491,14 @@ Visit [Google Fonts](https://fonts.google.com/) and browse for a font you like.
|
|||||||
https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap
|
https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### FFCNR (Fast Fictioneer Requests)
|
||||||
|
|
||||||
|
FFCNR is an alternative bootloader that utilizes the `SHORTINIT` constant to load a minimal WordPress environment. Although `SHORTINIT` is not officially documented, you can find guides about it with a quick search. In this mode, almost nothing is loaded: no themes, no plugins, and only a small subset of WordPress itself. For implementation details, refer to `ffcnr.php` in the theme directory.
|
||||||
|
|
||||||
|
In FFCNR mode, you have access to the `$wpdb` object for database communication, anything defined in `wp-config.php`, and `apply_filters()` and `do_action()` functionality. However, **you do not** have access to user authentication functions like `wp_get_current_user()` or permission checks such as `current_user_can()`. Additionally, you cannot use conditionals like `is_single()`, utility functions like `esc_attr()` or `get_permalink()`, or content functions such as `the_content()`.
|
||||||
|
|
||||||
|
FFCNR is meant exclusively for performance-critical requests, such as AJAX calls to retrieve or write data to the database. It is not secure and should never be used for sensitive operations unless you fully understand the implications. For that reason, no further guidance will be provided here. If you cannot figure this out on your own, you should not be experimenting with it.
|
||||||
|
|
||||||
### Constants
|
### Constants
|
||||||
|
|
||||||
Some options are not available in the settings because tempering with them can break the theme or result in unexpected behavior. Those options are defined via constants in the **function.php**. If you want to change them, you need a [child theme](https://developer.wordpress.org/themes/advanced-topics/child-themes/) or access to your **wp-config.php**. Just override them in the child theme’s own **function.php** or config, but only if you know what you are doing!
|
Some options are not available in the settings because tempering with them can break the theme or result in unexpected behavior. Those options are defined via constants in the **function.php**. If you want to change them, you need a [child theme](https://developer.wordpress.org/themes/advanced-topics/child-themes/) or access to your **wp-config.php**. Just override them in the child theme’s own **function.php** or config, but only if you know what you are doing!
|
||||||
|
@ -770,6 +770,14 @@
|
|||||||
"rFN" : 0,
|
"rFN" : 0,
|
||||||
"uCM" : 0
|
"uCM" : 0
|
||||||
},
|
},
|
||||||
|
"\/ffcnr.php" : {
|
||||||
|
"cB" : 0,
|
||||||
|
"ft" : 8192,
|
||||||
|
"hM" : 0,
|
||||||
|
"oA" : 0,
|
||||||
|
"oAP" : "\/ffcnr.php",
|
||||||
|
"oF" : 0
|
||||||
|
},
|
||||||
"\/FILTERS.md" : {
|
"\/FILTERS.md" : {
|
||||||
"cB" : 0,
|
"cB" : 0,
|
||||||
"cS" : 0,
|
"cS" : 0,
|
||||||
|
39
ffcnr.php
Normal file
39
ffcnr.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Fast Request Entry Point
|
||||||
|
*
|
||||||
|
* This file set up a minimal WordPress environment that is many times
|
||||||
|
* faster than regular endpoints but does not load anything beyond the
|
||||||
|
* absolute basics. No theme functions or plugins will work by default.
|
||||||
|
* Only use this for frequent and performance-critical requests.
|
||||||
|
*
|
||||||
|
* @package WordPress
|
||||||
|
* @subpackage Fictioneer
|
||||||
|
* @since 5.xx.x
|
||||||
|
*/
|
||||||
|
|
||||||
|
define( 'SHORTINIT', true );
|
||||||
|
define( 'FFCNR', true );
|
||||||
|
|
||||||
|
header( 'X-Robots-Tag: noindex, nofollow', true );
|
||||||
|
header( 'X-Content-Type-Options: nosniff' );
|
||||||
|
header( 'X-Frame-Options: DENY' );
|
||||||
|
|
||||||
|
header( 'Referrer-Policy: no-referrer' );
|
||||||
|
header( "Content-Security-Policy: default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';" ); // Just because
|
||||||
|
|
||||||
|
if ( isset( $_SERVER['DOCUMENT_ROOT'] ) && file_exists( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' ) ) {
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php';
|
||||||
|
} else {
|
||||||
|
$load_path = dirname( __DIR__, 3 ) . '/wp-load.php';
|
||||||
|
|
||||||
|
if ( file_exists( $load_path ) ) {
|
||||||
|
require_once $load_path;
|
||||||
|
} else {
|
||||||
|
header( 'HTTP/1.1 500 Internal Server Error' );
|
||||||
|
echo 'Critical error: Unable to locate wp-load.php.';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/functions/requests/_setup.php';
|
@ -43,7 +43,7 @@ if ( ! function_exists( 'fictioneer_get_custom_avatar_url' ) ) {
|
|||||||
|
|
||||||
function fictioneer_get_avatar_url( $url, $id_or_email, $args ) {
|
function fictioneer_get_avatar_url( $url, $id_or_email, $args ) {
|
||||||
// Abort conditions...
|
// Abort conditions...
|
||||||
if ( $args['force_default'] ?? false || empty( $id_or_email ) ) {
|
if ( ( $args['force_default'] ?? false ) || empty( $id_or_email ) ) {
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,12 +402,7 @@ if ( ! function_exists( 'fictioneer_get_user_fingerprint' ) ) {
|
|||||||
// If hash does not yet exist, create one
|
// If hash does not yet exist, create one
|
||||||
if ( empty( $fingerprint ) ) {
|
if ( empty( $fingerprint ) ) {
|
||||||
$fingerprint = md5( $user->user_login . $user_id );
|
$fingerprint = md5( $user->user_login . $user_id );
|
||||||
|
update_user_meta( $user_id, 'fictioneer_user_fingerprint', $fingerprint );
|
||||||
update_user_meta(
|
|
||||||
$user_id,
|
|
||||||
'fictioneer_user_fingerprint',
|
|
||||||
$fingerprint
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return hash
|
// Return hash
|
||||||
|
@ -1256,8 +1256,10 @@ function fictioneer_build_dynamic_scripts() {
|
|||||||
$scripts .= "var fictioneer_ajax = " . json_encode( array(
|
$scripts .= "var fictioneer_ajax = " . json_encode( array(
|
||||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||||
'rest_url' => get_rest_url( null, 'fictioneer/v1/' ),
|
'rest_url' => get_rest_url( null, 'fictioneer/v1/' ),
|
||||||
|
'ffcnr_url' => get_template_directory_uri() . '/ffcnr.php',
|
||||||
'ttl' => FICTIONEER_AJAX_TTL,
|
'ttl' => FICTIONEER_AJAX_TTL,
|
||||||
'post_debounce_rate' => FICTIONEER_AJAX_POST_DEBOUNCE_RATE
|
'post_debounce_rate' => FICTIONEER_AJAX_POST_DEBOUNCE_RATE,
|
||||||
|
'ffcnr_auth' => get_option( 'fictioneer_enable_ffcnr_auth', 0 ) ? 1 : 0
|
||||||
)) . ";";
|
)) . ";";
|
||||||
|
|
||||||
// --- Removable query args --------------------------------------------------
|
// --- Removable query args --------------------------------------------------
|
||||||
|
404
includes/functions/requests/_auth.php
Normal file
404
includes/functions/requests/_auth.php
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// No direct access!
|
||||||
|
defined( 'ABSPATH' ) OR exit;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// REPLACEMENT FUNCTIONS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user avatar URL.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param stdClass $user User object.
|
||||||
|
* @param int $size Optional. Size of the avatar. Default 96.
|
||||||
|
*
|
||||||
|
* @return string The URL or an empty string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_get_avatar_url( $user, $size = 96 ) {
|
||||||
|
if ( ! $user ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = ffcnr_load_options( ['avatar_default'] );
|
||||||
|
$default = $options['avatar_default'] ?? 'mystery';
|
||||||
|
$meta = ffcnr_load_user_meta( $user->ID, 'fictioneer' );
|
||||||
|
$email = $user->user_email ?? 'nonexistentemail@example.com';
|
||||||
|
$disabled = ( $meta['fictioneer_disable_avatar'] ?? 0 ) || ( $meta['fictioneer_admin_disable_avatar'] ?? 0 );
|
||||||
|
|
||||||
|
$filtered = apply_filters( 'ffcnr_get_avatar_url', '', $user, $size, $meta, $options );
|
||||||
|
|
||||||
|
if ( ! empty( $filtered ) ) {
|
||||||
|
return $filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom avatar
|
||||||
|
if (
|
||||||
|
! $disabled &&
|
||||||
|
! ( $meta['fictioneer_enforce_gravatar'] ?? 0 ) &&
|
||||||
|
( $meta['fictioneer_external_avatar_url'] ?? 0 )
|
||||||
|
) {
|
||||||
|
return $meta['fictioneer_external_avatar_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gravatar
|
||||||
|
if ( $email ) {
|
||||||
|
$gravatar_styles = array(
|
||||||
|
'mystery' => 'mm',
|
||||||
|
'blank' => 'blank',
|
||||||
|
'gravatar_default' => '',
|
||||||
|
'identicon' => 'identicon',
|
||||||
|
'wavatar' => 'wavatar',
|
||||||
|
'monsterid' => 'monsterid',
|
||||||
|
'retro' => 'retro'
|
||||||
|
);
|
||||||
|
|
||||||
|
$default = $gravatar_styles[ $default ];
|
||||||
|
$email_hash = $disabled ? 'foobar' : md5( mb_strtolower( trim( $user->user_email ) ) );
|
||||||
|
|
||||||
|
return "https://www.gravatar.com/avatar/{$email_hash}?s={$size}&d={$default}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an user is an administrator.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param stdClass $user User object.
|
||||||
|
*
|
||||||
|
* @return boolean To be or not to be.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_is_admin( $user ) {
|
||||||
|
return $user->caps['manage_options'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an user is an author.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param stdClass $user User object.
|
||||||
|
*
|
||||||
|
* @return boolean To be or not to be.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_is_author( $user ) {
|
||||||
|
return ($user->caps['publish_posts'] ?? false) ||
|
||||||
|
($user->caps['publish_fcn_stories'] ?? false) ||
|
||||||
|
($user->caps['publish_fcn_chapters'] ?? false) ||
|
||||||
|
($user->caps['publish_fcn_collections'] ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an user is a moderator.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param stdClass $user User object.
|
||||||
|
*
|
||||||
|
* @return boolean To be or not to be.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_is_moderator( $user ) {
|
||||||
|
return $user->caps['moderate_comments'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an user is an editor.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param stdClass $user User object.
|
||||||
|
*
|
||||||
|
* @return boolean To be or not to be.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_is_editor( $user ) {
|
||||||
|
return $user->caps['edit_others_posts'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an user's Follows
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @see includes/functions/users/_follows.php
|
||||||
|
*
|
||||||
|
* @param stdClass $user User to get the Follows for.
|
||||||
|
*
|
||||||
|
* @return array Follows.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fictioneer_load_follows( $user ) {
|
||||||
|
// Setup
|
||||||
|
$follows = ffcnr_get_user_meta( $user->ID, 'fictioneer_user_follows', 'fictioneer' );
|
||||||
|
$timestamp = time() * 1000;
|
||||||
|
|
||||||
|
// Validate/Initialize
|
||||||
|
if ( empty( $follows ) || ! is_array( $follows ) || ! array_key_exists( 'data', $follows ) ) {
|
||||||
|
$follows = array( 'data' => [], 'seen' => $timestamp, 'updated' => $timestamp );
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_follows', $follows );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! array_key_exists( 'updated', $follows ) ) {
|
||||||
|
$follows['updated'] = $timestamp;
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_follows', $follows );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! array_key_exists( 'seen', $follows ) ) {
|
||||||
|
$follows['seen'] = $timestamp;
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_follows', $follows );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return $follows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query count of new chapters for followed stories
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @see includes/functions/users/_follows.php
|
||||||
|
*
|
||||||
|
* @param array $story_ids IDs of the followed stories.
|
||||||
|
* @param string|null $after_date Optional. Only return chapters after this date,
|
||||||
|
* e.g. wp_date( 'Y-m-d H:i:s', $timestamp ).
|
||||||
|
* @param int $count Optional. Maximum number of chapters. Default 20.
|
||||||
|
*
|
||||||
|
* @return array Number of new chapters found.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fictioneer_query_new_followed_chapters_count( $story_ids, $after_date = null, $count = 20 ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$story_ids = array_map( 'absint', $story_ids );
|
||||||
|
|
||||||
|
if ( empty( $story_ids ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$story_ids_placeholder = implode( ',', array_fill( 0, count( $story_ids ), '%d' ) );
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT COUNT(p.ID) as count
|
||||||
|
FROM {$wpdb->posts} p
|
||||||
|
INNER JOIN {$wpdb->postmeta} pm_story ON p.ID = pm_story.post_id
|
||||||
|
LEFT JOIN {$wpdb->postmeta} pm_hidden ON p.ID = pm_hidden.post_id
|
||||||
|
WHERE p.post_type = 'fcn_chapter'
|
||||||
|
AND p.post_status = 'publish'
|
||||||
|
AND pm_story.meta_key = 'fictioneer_chapter_story'
|
||||||
|
AND pm_story.meta_value IN ({$story_ids_placeholder})
|
||||||
|
AND (pm_hidden.meta_key IS NULL OR pm_hidden.meta_value = '0')
|
||||||
|
";
|
||||||
|
|
||||||
|
if ( $after_date ) {
|
||||||
|
$sql .= " AND p.post_date > %s";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " LIMIT %d";
|
||||||
|
|
||||||
|
$query_args = array_merge( $story_ids, $after_date ? [ $after_date ] : [], [ $count ] );
|
||||||
|
|
||||||
|
return (int) $wpdb->get_var( $wpdb->prepare( $sql, $query_args ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an user's Reminders
|
||||||
|
*
|
||||||
|
* Get an user's Reminders array from the database or creates a new one if it
|
||||||
|
* does not yet exist.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @see includes/functions/users/_reminders.php
|
||||||
|
*
|
||||||
|
* @param stdClass $user User to get the Reminders for.
|
||||||
|
*
|
||||||
|
* @return array Reminders.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fictioneer_load_reminders( $user ) {
|
||||||
|
// Setup
|
||||||
|
$reminders = ffcnr_get_user_meta( $user->ID, 'fictioneer_user_reminders', 'fictioneer' );
|
||||||
|
$timestamp = time() * 1000; // Compatible with Date.now() in JavaScript
|
||||||
|
|
||||||
|
// Validate/Initialize
|
||||||
|
if ( empty( $reminders ) || ! is_array( $reminders ) || ! array_key_exists( 'data', $reminders ) ) {
|
||||||
|
$reminders = array( 'data' => [], 'updated' => $timestamp );
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_reminders', $reminders );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! array_key_exists( 'updated', $reminders ) ) {
|
||||||
|
$reminders['updated'] = $timestamp;
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_reminders', $reminders );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return $reminders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an user's Checkmarks
|
||||||
|
*
|
||||||
|
* Get an user's Checkmarks array from the database or creates a new one if it
|
||||||
|
* does not yet exist.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @see includes/functions/users/_checkmarks.php
|
||||||
|
*
|
||||||
|
* @param stdClass $user User to get the checkmarks for.
|
||||||
|
*
|
||||||
|
* @return array Checkmarks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fictioneer_load_checkmarks( $user ) {
|
||||||
|
// Setup
|
||||||
|
$checkmarks = ffcnr_get_user_meta( $user->ID, 'fictioneer_user_checkmarks', 'fictioneer' );
|
||||||
|
$timestamp = time() * 1000;
|
||||||
|
|
||||||
|
// Validate/Initialize
|
||||||
|
if ( empty( $checkmarks ) || ! is_array( $checkmarks ) || ! array_key_exists( 'data', $checkmarks ) ) {
|
||||||
|
$checkmarks = array( 'data' => [], 'updated' => $timestamp );
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_checkmarks', $checkmarks );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! array_key_exists( 'updated', $checkmarks ) ) {
|
||||||
|
$checkmarks['updated'] = $timestamp;
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_checkmarks', $checkmarks );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return $checkmarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unique MD5 hash for the user.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @see includes/functions/_helpers-users.php
|
||||||
|
*
|
||||||
|
* @param stdClass $user User to get the fingerprint for.
|
||||||
|
*
|
||||||
|
* @return string The unique fingerprint hash or empty string if not found.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fictioneer_get_user_fingerprint( $user ) {
|
||||||
|
if ( ! $user ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$fingerprint = ffcnr_get_user_meta( $user->ID, 'fictioneer_user_fingerprint', 'fictioneer' );
|
||||||
|
|
||||||
|
if ( empty( $fingerprint ) ) {
|
||||||
|
$fingerprint = md5( $user->user_login . $user->ID );
|
||||||
|
ffcnr_update_user_meta( $user->ID, 'fictioneer_user_fingerprint', $fingerprint );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// GET USER DATA
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
function ffcnr_get_user_data() {
|
||||||
|
// Load options
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$options = ffcnr_load_options([
|
||||||
|
'siteurl', 'avatar_default', 'fictioneer_enable_reminders', 'fictioneer_enable_checkmarks',
|
||||||
|
'fictioneer_enable_bookmarks', 'fictioneer_enable_follows', "{$wpdb->prefix}user_roles"
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
$user = ffcnr_get_current_user( $options );
|
||||||
|
$logged_in = !!($user ? $user->ID : 0);
|
||||||
|
$nonce = ffcnr_create_nonce( 'fictioneer_nonce', $logged_in ? $user->ID : 0 );
|
||||||
|
$data = array(
|
||||||
|
'user_id' => $logged_in ? $user->ID : 0,
|
||||||
|
'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 ),
|
||||||
|
'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' => ffcnr_is_admin( $user ),
|
||||||
|
'isModerator' => ffcnr_is_moderator( $user ),
|
||||||
|
'isAuthor' => ffcnr_is_author( $user ),
|
||||||
|
'isEditor' => ffcnr_is_editor( $user ),
|
||||||
|
'avatarUrl' => ffcnr_get_avatar_url( $user )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FOLLOWS ---------------------------------------------------------------
|
||||||
|
|
||||||
|
if ( $logged_in && $options['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 && $options['fictioneer_enable_reminders'] ) {
|
||||||
|
$data['reminders'] = fictioneer_load_reminders( $user );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CHECKMARKS ------------------------------------------------------------
|
||||||
|
|
||||||
|
if ( $logged_in && $options['fictioneer_enable_checkmarks'] ) {
|
||||||
|
$data['checkmarks'] = fictioneer_load_checkmarks( $user );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BOOKMARKS -------------------------------------------------------------
|
||||||
|
|
||||||
|
if ( $logged_in && $options['fictioneer_enable_bookmarks'] ) {
|
||||||
|
$bookmarks = ffcnr_get_user_meta( $user->ID, 'fictioneer_bookmarks', 'fictioneer' );
|
||||||
|
$data['bookmarks'] = $bookmarks ? $bookmarks : '{}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FILTER ----------------------------------------------------------------
|
||||||
|
|
||||||
|
$data = apply_filters( 'ffcnr_get_user_data', $data, $user );
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Response
|
||||||
|
header( 'Content-Type: application/json; charset=utf-8' );
|
||||||
|
header( 'HTTP/1.1 200 OK' );
|
||||||
|
echo json_encode( array( 'success' => true, 'data' => $data ) );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ffcnr_get_user_data();
|
443
includes/functions/requests/_setup.php
Normal file
443
includes/functions/requests/_setup.php
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// REPLACEMENTS FOR WP FUNCTIONS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads specific WordPress options into a global variable.
|
||||||
|
*
|
||||||
|
* Note: Regardless of the given option names to query, the function
|
||||||
|
* always queries a set of default WP options for convenience.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @global wpdb $wpdb WordPress database object.
|
||||||
|
* @global array $ffcnr_options Array of previously loaded options.
|
||||||
|
*
|
||||||
|
* @param array $option_names Optional. Array of option names to load.
|
||||||
|
*
|
||||||
|
* @return array Array of loaded options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_load_options( $option_names = [] ) {
|
||||||
|
global $wpdb, $ffcnr_options;
|
||||||
|
|
||||||
|
if ( ! isset( $ffcnr_options ) ) {
|
||||||
|
$ffcnr_options = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$default_options = ['siteurl', 'home', 'blogname', 'blogdescription', 'users_can_register', 'admin_email', 'timezone_string', 'date_format', 'time_format', 'posts_per_page', 'permalink_structure', 'upload_path', 'template', 'blog_charset', 'active_plugins', 'gmt_offset', 'stylesheet', 'default_role', 'avatar_rating', 'show_avatars', 'avatar_default', 'page_for_posts', 'page_on_front', 'site_icon', 'wp_user_roles', 'cron', 'nonce_key', 'nonce_salt', 'current_theme', 'show_on_front', 'blog_public', 'theme_switched'];
|
||||||
|
|
||||||
|
$default_options = apply_filters( 'ffcnr_load_options_defaults', $default_options );
|
||||||
|
|
||||||
|
$query_options = array_unique( array_merge( $option_names, $default_options ) );
|
||||||
|
$missing_options = array_diff( $query_options, array_keys( $ffcnr_options ) );
|
||||||
|
|
||||||
|
if ( ! empty( $missing_options ) ) {
|
||||||
|
$placeholders = implode( ',', array_fill( 0, count( $missing_options ), '%s' ) );
|
||||||
|
|
||||||
|
$results = $wpdb->get_results(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT `option_name`, `option_value` FROM $wpdb->options WHERE `option_name` IN ($placeholders)",
|
||||||
|
$missing_options
|
||||||
|
),
|
||||||
|
'OBJECT_K'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $results as $option_name => $option ) {
|
||||||
|
$ffcnr_options[ $option_name ] = maybe_unserialize( $option->option_value );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $missing_options as $missing_option ) {
|
||||||
|
if ( ! isset( $ffcnr_options[ $missing_option ] ) ) {
|
||||||
|
$ffcnr_options[ $missing_option ] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ffcnr_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns hash of a given string.
|
||||||
|
*
|
||||||
|
* Note: Reduced alternative to wp_hash().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param string $data Plain text to hash.
|
||||||
|
* @param string $scheme Authentication scheme (auth, nonce).
|
||||||
|
*
|
||||||
|
* @return string Hash of $data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_hash( $data, $scheme = 'auth' ){
|
||||||
|
$salts = array(
|
||||||
|
'auth' => LOGGED_IN_KEY . LOGGED_IN_SALT,
|
||||||
|
'nonce' => NONCE_KEY . NONCE_SALT
|
||||||
|
);
|
||||||
|
|
||||||
|
return hash_hmac( 'md5', $data, $salts[ $scheme ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns hashed session token.
|
||||||
|
*
|
||||||
|
* Note: Reduced alternative to WP_Session_Tokens::hash_token().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param string $token Session token to hash.
|
||||||
|
*
|
||||||
|
* @return string A hash of the session token (a verifier).
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_hash_token( $token ){
|
||||||
|
if ( function_exists( 'hash' ) ) {
|
||||||
|
return hash( 'sha256', $token );
|
||||||
|
} else {
|
||||||
|
return sha1( $token );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns authentication cookie.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @return string The unprocessed cookie string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_get_auth_cookie() {
|
||||||
|
static $cookie;
|
||||||
|
|
||||||
|
if ( $cookie ) {
|
||||||
|
return $cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ffcnr_options = ffcnr_load_options( ['siteurl'] );
|
||||||
|
$cookie_hash = md5( $ffcnr_options['siteurl'] );
|
||||||
|
|
||||||
|
if ( ! isset( $_COOKIE[ "wordpress_logged_in_{$cookie_hash}" ] ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cookie = $_COOKIE[ "wordpress_logged_in_{$cookie_hash}" ];
|
||||||
|
|
||||||
|
return $cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current session token from the logged_in cookie.
|
||||||
|
*
|
||||||
|
* Note: Alternative to wp_get_session_token().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @return string The session token or empty string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_get_session_token() {
|
||||||
|
$cookie = ffcnr_get_auth_cookie();
|
||||||
|
$cookie = explode( '|', $cookie );
|
||||||
|
|
||||||
|
return ! empty( $cookie[2] ) ? $cookie[2] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time-dependent variable for nonce creation.
|
||||||
|
*
|
||||||
|
* Note: Alternative to wp_nonce_tick().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @return float Float value rounded up to the next highest integer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_nonce_tick( $action = -1 ) {
|
||||||
|
$nonce_life = apply_filters( 'ffcnr_nonce_life', DAY_IN_SECONDS, $action );
|
||||||
|
|
||||||
|
return ceil( time() / ( $nonce_life / 2 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a cryptographic token tied to a specific action,
|
||||||
|
* user, user session, and window of time.
|
||||||
|
*
|
||||||
|
* Note: Alternative to wp_create_nonce().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param string $action Scalar value to add context to the nonce.
|
||||||
|
* @param int $uid User Id.
|
||||||
|
*
|
||||||
|
* @return string The nonce.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_create_nonce( $action, $uid ) {
|
||||||
|
$token = ffcnr_get_session_token();
|
||||||
|
$i = ffcnr_nonce_tick( $action );
|
||||||
|
|
||||||
|
if ( ! $uid ) {
|
||||||
|
$uid = apply_filters( 'ffcnr_nonce_user_logged_out', $uid, $action );
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr( ffcnr_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current user.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param array $options Optional. Pre-queried theme options.
|
||||||
|
* @param int|null $blog_id_override Optional. Override current blog ID.
|
||||||
|
*
|
||||||
|
* @return stdClass|int User or 0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_get_current_user( $options = null, $blog_id_override = null ) {
|
||||||
|
global $wpdb, $blog_id;
|
||||||
|
|
||||||
|
$_blog_id = $blog_id_override ?? $blog_id ?? 1;
|
||||||
|
$site_prefix = $wpdb->get_blog_prefix( $_blog_id );
|
||||||
|
$options = $options ?: ffcnr_load_options( ['siteurl', "{$site_prefix}user_roles"] );
|
||||||
|
$cookie = ffcnr_get_auth_cookie();
|
||||||
|
$cookie_elements = explode( '|', $cookie );
|
||||||
|
|
||||||
|
if ( count( $cookie_elements ) !== 4 ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
list( $username, $expiration, $token, $hmac ) = $cookie_elements;
|
||||||
|
|
||||||
|
if ( $expiration < time() ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $wpdb->get_row(
|
||||||
|
$wpdb->prepare( "SELECT * FROM $wpdb->users WHERE `user_login`=%s", $username ),
|
||||||
|
'OBJECT'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! $user ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pass_frag = substr( $user->user_pass, 8, 4 );
|
||||||
|
$key = ffcnr_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token );
|
||||||
|
$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
|
||||||
|
$hash = hash_hmac( $algo, $username . '|' . $expiration . '|' . $token, $key );
|
||||||
|
|
||||||
|
if ( ! hash_equals( $hash, $hmac ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_options = $wpdb->get_results(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT `meta_key`, `meta_value` FROM {$wpdb->usermeta}
|
||||||
|
WHERE `user_id` = %d AND `meta_key` IN ( 'session_tokens', %s )",
|
||||||
|
$user->ID,
|
||||||
|
"{$site_prefix}capabilities"
|
||||||
|
),
|
||||||
|
OBJECT_K
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! $user_options ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessions = maybe_unserialize( $user_options['session_tokens']->meta_value );
|
||||||
|
$verifier = ffcnr_hash_token( $token );
|
||||||
|
|
||||||
|
if ( ! isset( $sessions[ $verifier ] ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $sessions[ $verifier ]['expiration'] < time() ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$role_caps = maybe_unserialize( $options[ "{$site_prefix}user_roles" ] );
|
||||||
|
$user_caps = maybe_unserialize( $user_options[ "{$site_prefix}capabilities" ]->meta_value );
|
||||||
|
$all_caps = [];
|
||||||
|
$roles = [];
|
||||||
|
|
||||||
|
foreach ( $user_caps as $key => $value ) {
|
||||||
|
if ( isset( $role_caps[ $key ] ) && $value ) {
|
||||||
|
$all_caps = array_merge( $all_caps, $role_caps[ $key ]['capabilities'] );
|
||||||
|
$roles[] = $key;
|
||||||
|
} else {
|
||||||
|
$all_caps[ $key ] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->caps = $all_caps;
|
||||||
|
$user->roles = $roles;
|
||||||
|
|
||||||
|
return apply_filters( 'ffcnr_get_current_user', $user, $cookie, $role_caps, $user_caps, $_blog_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads meta fields for a given user ID.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @global wpdb $wpdb WordPress database object.
|
||||||
|
*
|
||||||
|
* @param int $user_id User ID.
|
||||||
|
* @param string $filter Optional. String to filter meta keys. Only keys
|
||||||
|
* containing this string will be considered.
|
||||||
|
* @param bool $reload Skip the static cache and query again.
|
||||||
|
*
|
||||||
|
* @return array Array of meta data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_load_user_meta( $user_id, $filter = '', $reload = false ) {
|
||||||
|
static $cache = [];
|
||||||
|
|
||||||
|
if ( ! $reload && isset( $cache[ $user_id ] ) ) {
|
||||||
|
return $cache[ $user_id ];
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$query = $wpdb->prepare(
|
||||||
|
"SELECT `meta_key`, `meta_value` FROM $wpdb->usermeta WHERE `user_id` = %d",
|
||||||
|
$user_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! empty( $filter ) ) {
|
||||||
|
$query .= $wpdb->prepare( " AND `meta_key` LIKE %s", '%' . $wpdb->esc_like( $filter ) . '%' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $wpdb->get_results( $query, OBJECT_K );
|
||||||
|
|
||||||
|
$meta = [];
|
||||||
|
|
||||||
|
foreach ( $result as $key => $value ) {
|
||||||
|
$meta[ $key ] = maybe_unserialize( $value->meta_value );
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache[ $user_id ] = $meta;
|
||||||
|
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a meta field.
|
||||||
|
*
|
||||||
|
* Note: Alternative to get_user_meta().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param int $user_id User ID.
|
||||||
|
* @param string $meta_key Meta key.
|
||||||
|
* @param string $filter Optional. String to filter meta keys. Only keys
|
||||||
|
* containing this string will be queried. Passed
|
||||||
|
* on to ffcnr_load_user_meta().
|
||||||
|
*
|
||||||
|
* @return mixed The meta field value or an empty string if not found.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_get_user_meta( $user_id, $meta_key, $filter = '' ) {
|
||||||
|
$meta = ffcnr_load_user_meta( $user_id, $filter );
|
||||||
|
$value = apply_filters( 'ffcnr_get_user_meta', $meta[ $meta_key ] ?? '', $user_id, $meta_key, $filter );
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update or insert a meta field.
|
||||||
|
*
|
||||||
|
* Note: Alternative to update_user_meta().
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
* @global wpdb $wpdb WordPress database object.
|
||||||
|
*
|
||||||
|
* @param int $user_id User ID.
|
||||||
|
* @param string $meta_key Meta key.
|
||||||
|
* @param mixed $meta_value The value to update or insert.
|
||||||
|
*
|
||||||
|
* @return bool True if successful, false if not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_update_user_meta( $user_id, $meta_key, $meta_value ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$user_id = absint( $user_id );
|
||||||
|
$meta_key = sanitize_key( $meta_key );
|
||||||
|
$meta_value = apply_filters( 'ffcnr_update_user_meta', $meta_value, $user_id, $meta_key );
|
||||||
|
|
||||||
|
$umeta_id = $wpdb->get_var(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT `umeta_id` FROM {$wpdb->usermeta} WHERE `user_id` = %d AND `meta_key` = %s",
|
||||||
|
$user_id,
|
||||||
|
$meta_key
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $umeta_id ) {
|
||||||
|
$updated = $wpdb->update(
|
||||||
|
$wpdb->usermeta,
|
||||||
|
array( 'meta_value' => maybe_serialize( $meta_value ) ),
|
||||||
|
array( 'umeta_id' => $umeta_id ),
|
||||||
|
['%s'],
|
||||||
|
['%d']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $updated !== false;
|
||||||
|
} else {
|
||||||
|
$inserted = $wpdb->insert(
|
||||||
|
$wpdb->usermeta,
|
||||||
|
array(
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'meta_key' => $meta_key,
|
||||||
|
'meta_value' => maybe_serialize( $meta_value )
|
||||||
|
),
|
||||||
|
['%d', '%s', '%s']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $inserted !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// CHILD THEME
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes a ffcnr-functions.php file from the active theme.
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @return bool True if included, false if not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ffcnr_load_child_theme_functions() {
|
||||||
|
$options = ffcnr_load_options();
|
||||||
|
|
||||||
|
$dir = ABSPATH . 'wp-content/themes/' . $options['stylesheet'];
|
||||||
|
$path = $dir . '/ffcnr-functions.php';
|
||||||
|
|
||||||
|
if ( file_exists( $path ) ) {
|
||||||
|
include_once $path;
|
||||||
|
|
||||||
|
define( 'CHILD_FUNCTIONS_LOADED', true );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
define( 'CHILD_FUNCTIONS_LOADED', false );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ffcnr_load_child_theme_functions();
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DELEGATE TO REQUEST
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
$action = apply_filters( 'ffcnr_request_action', $_REQUEST['action'] ?? 0 );
|
||||||
|
|
||||||
|
if ( $action === 'auth' ) {
|
||||||
|
require_once __DIR__ . '/_auth.php';
|
||||||
|
}
|
@ -743,6 +743,12 @@ define( 'FICTIONEER_OPTIONS', array(
|
|||||||
'group' => 'fictioneer-settings-general-group',
|
'group' => 'fictioneer-settings-general-group',
|
||||||
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
|
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
|
||||||
'default' => 0
|
'default' => 0
|
||||||
|
),
|
||||||
|
'fictioneer_enable_ffcnr_auth' => array(
|
||||||
|
'name' => 'fictioneer_enable_ffcnr_auth',
|
||||||
|
'group' => 'fictioneer-settings-general-group',
|
||||||
|
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
|
||||||
|
'default' => 0
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'integers' => array(
|
'integers' => array(
|
||||||
@ -1217,6 +1223,8 @@ function fictioneer_get_option_label( $option ) {
|
|||||||
'fictioneer_disable_chapter_list_transients' => __( 'Disable chapter list Transient caching', 'fictioneer' ),
|
'fictioneer_disable_chapter_list_transients' => __( 'Disable chapter list Transient caching', 'fictioneer' ),
|
||||||
'fictioneer_disable_shortcode_transients' => __( 'Disable shortcode Transient caching', 'fictioneer' ),
|
'fictioneer_disable_shortcode_transients' => __( 'Disable shortcode Transient caching', 'fictioneer' ),
|
||||||
'fictioneer_enable_css_skins' => __( 'Enable CSS skins (requires account)', 'fictioneer' ),
|
'fictioneer_enable_css_skins' => __( 'Enable CSS skins (requires account)', 'fictioneer' ),
|
||||||
|
'fictioneer_exclude_non_stories_from_cloud_counts' => __( 'Only count stories in taxonomy clouds', 'fictioneer' ),
|
||||||
|
'fictioneer_enable_ffcnr_auth' => __( 'Enable FFCNR user authentication', 'fictioneer' ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,8 @@ function fictioneer_settings_checkbox_updated( $option, $old_value, $value ) {
|
|||||||
if ( ! $purged ) {
|
if ( ! $purged ) {
|
||||||
$cache_purge_options = ['fictioneer_hide_chapter_icons', 'fictioneer_enable_chapter_groups',
|
$cache_purge_options = ['fictioneer_hide_chapter_icons', 'fictioneer_enable_chapter_groups',
|
||||||
'fictioneer_collapse_groups_by_default', 'fictioneer_disable_chapter_collapsing',
|
'fictioneer_collapse_groups_by_default', 'fictioneer_disable_chapter_collapsing',
|
||||||
'fictioneer_count_characters_as_words', 'fictioneer_override_chapter_status_icons'];
|
'fictioneer_count_characters_as_words', 'fictioneer_override_chapter_status_icons',
|
||||||
|
'fictioneer_enable_ffcnr_auth'];
|
||||||
|
|
||||||
if ( in_array( $option, $cache_purge_options ) ) {
|
if ( in_array( $option, $cache_purge_options ) ) {
|
||||||
fictioneer_purge_theme_caches();
|
fictioneer_purge_theme_caches();
|
||||||
|
@ -1092,6 +1092,20 @@ $images = get_template_directory_uri() . '/img/documentation/';
|
|||||||
<h3 class="fictioneer-card__header"><?php _e( 'Performance', 'fictioneer' ); ?></h3>
|
<h3 class="fictioneer-card__header"><?php _e( 'Performance', 'fictioneer' ); ?></h3>
|
||||||
<div class="fictioneer-card__content">
|
<div class="fictioneer-card__content">
|
||||||
|
|
||||||
|
<div class="fictioneer-card__row">
|
||||||
|
<?php
|
||||||
|
fictioneer_settings_label_checkbox(
|
||||||
|
'fictioneer_enable_ffcnr_auth',
|
||||||
|
__( 'Enable FFCNR user authentication', 'fictioneer' ),
|
||||||
|
__( 'Recommended. Significantly faster frontend user setup, but can cause issues in rare cases.', 'fictioneer' ),
|
||||||
|
sprintf(
|
||||||
|
__( 'FFCNR stands for <em>Fast Fictioneer Requests</em>, an alternative entry point to WordPress with minimal environment. In this mode, almost nothing is loaded: no themes, no plugins, and only a tiny subset of WordPress itself. This reduced environment significantly improves performance for certain tasks, such as fetching user data for the frontend. Refer to the <a href="%s" target="_blank" rel="noopener noreferrer">installation guide</a> for details on interacting with FFCNR.', 'fictioneer' ),
|
||||||
|
'https://github.com/Tetrakern/fictioneer/blob/main/INSTALLATION.md'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="fictioneer-card__row">
|
<div class="fictioneer-card__row">
|
||||||
<?php
|
<?php
|
||||||
fictioneer_settings_label_checkbox(
|
fictioneer_settings_label_checkbox(
|
||||||
|
@ -55,13 +55,13 @@ if ( ! function_exists( 'fictioneer_query_followed_chapters' ) ) {
|
|||||||
* @since 4.3.0
|
* @since 4.3.0
|
||||||
*
|
*
|
||||||
* @param array $story_ids IDs of the followed stories.
|
* @param array $story_ids IDs of the followed stories.
|
||||||
* @param string|false $after_date Optional. Only return chapters after this date, e.g. wp_date( 'c', $timestamp ).
|
* @param string|null $after_date Optional. Only return chapters after this date, e.g. wp_date( 'c', $timestamp ).
|
||||||
* @param int $count Optional. Maximum number of chapters to be returned. Default 20.
|
* @param int $count Optional. Maximum number of chapters to be returned. Default 20.
|
||||||
*
|
*
|
||||||
* @return array Collection of chapters.
|
* @return array Collection of chapters.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function fictioneer_query_followed_chapters( $story_ids, $after_date = false, $count = 20 ) {
|
function fictioneer_query_followed_chapters( $story_ids, $after_date = null, $count = 20 ) {
|
||||||
// Setup
|
// Setup
|
||||||
$query_args = array (
|
$query_args = array (
|
||||||
'post_type' => 'fcn_chapter',
|
'post_type' => 'fcn_chapter',
|
||||||
@ -102,6 +102,55 @@ if ( ! function_exists( 'fictioneer_query_followed_chapters' ) ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! function_exists( 'fictioneer_query_new_followed_chapters_count' ) ) {
|
||||||
|
/**
|
||||||
|
* Query count of new chapters for followed stories
|
||||||
|
*
|
||||||
|
* @since 5.xx.x
|
||||||
|
*
|
||||||
|
* @param array $story_ids IDs of the followed stories.
|
||||||
|
* @param string|null $after_date Optional. Only return chapters after this date,
|
||||||
|
* e.g. wp_date( 'Y-m-d H:i:s', $timestamp ).
|
||||||
|
* @param int $count Optional. Maximum number of chapters. Default 20.
|
||||||
|
*
|
||||||
|
* @return array Number of new chapters found.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fictioneer_query_new_followed_chapters_count( $story_ids, $after_date = null, $count = 20 ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$story_ids = array_map( 'absint', $story_ids );
|
||||||
|
|
||||||
|
if ( empty( $story_ids ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$story_ids_placeholder = implode( ',', array_fill( 0, count( $story_ids ), '%d' ) );
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT COUNT(p.ID) as count
|
||||||
|
FROM {$wpdb->posts} p
|
||||||
|
INNER JOIN {$wpdb->postmeta} pm_story ON p.ID = pm_story.post_id
|
||||||
|
LEFT JOIN {$wpdb->postmeta} pm_hidden ON p.ID = pm_hidden.post_id
|
||||||
|
WHERE p.post_type = 'fcn_chapter'
|
||||||
|
AND p.post_status = 'publish'
|
||||||
|
AND pm_story.meta_key = 'fictioneer_chapter_story'
|
||||||
|
AND pm_story.meta_value IN ({$story_ids_placeholder})
|
||||||
|
AND (pm_hidden.meta_key IS NULL OR pm_hidden.meta_value = '0')
|
||||||
|
";
|
||||||
|
|
||||||
|
if ( $after_date ) {
|
||||||
|
$sql .= " AND p.post_date > %s";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " LIMIT %d";
|
||||||
|
|
||||||
|
$query_args = array_merge( $story_ids, $after_date ? [ $after_date ] : [], [ $count ] );
|
||||||
|
|
||||||
|
return (int) $wpdb->get_var( $wpdb->prepare( $sql, $query_args ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// AJAX REQUESTS
|
// AJAX REQUESTS
|
||||||
// > Return early if no AJAX functions are required.
|
// > Return early if no AJAX functions are required.
|
||||||
|
@ -54,17 +54,16 @@ function fictioneer_ajax_get_user_data() {
|
|||||||
if ( $logged_in && get_option( 'fictioneer_enable_follows' ) ) {
|
if ( $logged_in && get_option( 'fictioneer_enable_follows' ) ) {
|
||||||
$follows = fictioneer_load_follows( $user );
|
$follows = fictioneer_load_follows( $user );
|
||||||
$follows['new'] = false;
|
$follows['new'] = false;
|
||||||
$latest = 0;
|
|
||||||
|
|
||||||
// New notifications?
|
// New notifications?
|
||||||
if ( count( $follows['data'] ) > 0 ) {
|
if ( count( $follows['data'] ) > 0 ) {
|
||||||
$latest = fictioneer_query_followed_chapters(
|
$latest_count = fictioneer_query_new_followed_chapters_count(
|
||||||
array_keys( $follows['data'] ),
|
array_keys( $follows['data'] ),
|
||||||
wp_date( 'c', $follows['seen'] / 1000 )
|
wp_date( 'Y-m-d H:i:s', $follows['seen'] / 1000 )
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $latest ) {
|
if ( $latest_count > 0 ) {
|
||||||
$follows['new'] = count( $latest );
|
$follows['new'] = $latest_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
js/application.min.js
vendored
4
js/application.min.js
vendored
File diff suppressed because one or more lines are too long
8
js/complete.min.js
vendored
8
js/complete.min.js
vendored
File diff suppressed because one or more lines are too long
2
js/dev-tools.min.js
vendored
2
js/dev-tools.min.js
vendored
@ -1 +1 @@
|
|||||||
async function fcn_benchmarkAjax(e=1,n={},t=null,o={},r="get"){let s=0;console.log(`Starting benchmark with ${e} AJAX requests...`);for(let c=0;c<e;c++){const e=performance.now();try{"get"===r?await FcnUtils.aGet(n,t,o):await FcnUtils.aPost(n,t,o),s+=performance.now()-e}catch(e){console.error("Error during AJAX request:",e)}}const c=s/e;return console.log(`Finished benchmarking. Average AJAX response time over ${e} requests: ${c.toFixed(2)} ms`),c}function fcn_printAjaxResponse(e,n="get"){"get"===n?FcnUtils.aGet(e).then((e=>{console.log(e)})):FcnUtils.aPost(e).then((e=>{console.log(e)}))}
|
async function fcn_benchmarkAjax(e=1,t={},n=null,o={},r="get"){let s=0;console.log(`Starting benchmark with ${e} AJAX requests...`);try{"get"===r?await FcnUtils.aGet(t,n,o):await FcnUtils.aPost(t,n,o)}catch(e){console.error("Error during warm-up request:",e)}for(let c=0;c<e;c++){const e=performance.now();try{"get"===r?await FcnUtils.aGet(t,n,o):await FcnUtils.aPost(t,n,o),s+=performance.now()-e}catch(e){console.error("Error during AJAX request:",e)}}const c=s/e;return console.log(`Finished benchmarking. Average AJAX response time over ${e} requests: ${c.toFixed(2)} ms`),c}function fcn_printAjaxResponse(e,t="get"){"get"===t?FcnUtils.aGet(e).then((e=>{console.log(e)})):FcnUtils.aPost(e).then((e=>{console.log(e)}))}
|
@ -51,6 +51,22 @@ const FcnGlobals = {
|
|||||||
|
|
||||||
restURL: fictioneer_ajax.rest_url,
|
restURL: fictioneer_ajax.rest_url,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL for FFCNR requests.
|
||||||
|
*
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
ffcnrURL: fictioneer_ajax.ffcnr_url,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use the FFCNR user authentication.
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
ffcnrAuth: fictioneer_ajax.ffcnr_auth,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounce rate in milliseconds (default 700).
|
* Debounce rate in milliseconds (default 700).
|
||||||
*
|
*
|
||||||
@ -247,10 +263,13 @@ application.register('fictioneer', class extends Stimulus.Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request
|
// Request
|
||||||
FcnUtils.aGet({
|
FcnUtils.aGet(
|
||||||
'action': 'fictioneer_ajax_get_user_data',
|
{
|
||||||
|
'action': FcnGlobals.ffcnrAuth ? 'auth' : 'fictioneer_ajax_get_user_data',
|
||||||
'fcn_fast_ajax': 1
|
'fcn_fast_ajax': 1
|
||||||
})
|
},
|
||||||
|
FcnGlobals.ffcnrAuth ? FcnGlobals.ffcnrURL : null
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
// Prepare user data object
|
// Prepare user data object
|
||||||
|
@ -22,6 +22,16 @@ async function fcn_benchmarkAjax(n = 1, data = {}, url = null, headers = {}, met
|
|||||||
|
|
||||||
console.log(`Starting benchmark with ${n} AJAX requests...`);
|
console.log(`Starting benchmark with ${n} AJAX requests...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (method === 'get') {
|
||||||
|
await FcnUtils.aGet(data, url, headers);
|
||||||
|
} else {
|
||||||
|
await FcnUtils.aPost(data, url, headers);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during warm-up request:', error);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user