2023-01-21 01:31:34 +01:00
< ? php
2024-10-05 16:11:05 +02:00
// =============================================================================
// CHECK FOR LOCAL DEV ENVIRONMENT
// =============================================================================
/**
* Checks whether the site runs on a local environment
*
* @ since 5.25 . 0
*
* @ return bool True or false .
*/
function fictioneer_is_local_environment () {
$local_hosts = [ 'localhost' , '127.0.0.1' , '::1' ];
if ( in_array ( $_SERVER [ 'HTTP_HOST' ], $local_hosts ) || in_array ( $_SERVER [ 'SERVER_ADDR' ], $local_hosts ) ) {
return true ;
}
return false ;
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// CHECK IF URL EXISTS
// =============================================================================
if ( ! function_exists ( 'fictioneer_url_exists' ) ) {
/**
2023-08-28 10:43:04 +02:00
* Checks whether an URL exists
2023-01-21 01:31:34 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-08-28 10:43:04 +02:00
* @ link https :// www . geeksforgeeks . org / how - to - check - the - existence - of - url - in - php /
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $url The URL to check .
2023-01-21 01:31:34 +01:00
*
2023-08-28 10:43:04 +02:00
* @ return boolean True if the URL exists and false otherwise . Probably .
2023-01-21 01:31:34 +01:00
*/
function fictioneer_url_exists ( $url ) {
2023-10-24 01:21:10 +02:00
if ( empty ( $url ) ) {
2023-08-28 10:43:04 +02:00
return false ;
}
2023-01-21 01:31:34 +01:00
2023-10-24 01:21:10 +02:00
$response = wp_remote_head ( $url );
2023-01-21 01:31:34 +01:00
2023-10-24 01:21:10 +02:00
if ( is_wp_error ( $response ) ) {
return false ;
2023-01-21 01:31:34 +01:00
}
2023-10-24 01:21:10 +02:00
$statusCode = wp_remote_retrieve_response_code ( $response );
// Check for 2xx status codes which indicate success
return ( $statusCode >= 200 && $statusCode < 300 );
2023-01-21 01:31:34 +01:00
}
}
// =============================================================================
// CHECK WHETHER VALID JSON
// =============================================================================
2024-07-10 20:41:15 +02:00
/**
* Check whether a JSON is valid
*
* @ since 4.0 . 0
* @ since 5.21 . 1 - Use json_validate () if on PHP 8.3 or higher .
*
* @ param string $data JSON string hopeful .
*
* @ return boolean True if the JSON is valid , false if not .
*/
2023-08-22 10:28:40 +02:00
2024-07-10 20:41:15 +02:00
function fictioneer_is_valid_json ( $data = null ) {
if ( empty ( $data ) ) {
return false ;
}
2023-08-22 10:28:40 +02:00
2024-07-10 20:41:15 +02:00
// PHP 8.3 or higher
if ( function_exists ( 'json_validate' ) ) {
return json_validate ( $data );
2023-01-21 01:31:34 +01:00
}
2024-07-10 20:41:15 +02:00
$data = @ json_decode ( $data , true );
return ( json_last_error () === JSON_ERROR_NONE );
2023-01-21 01:31:34 +01:00
}
// =============================================================================
// CHECK FOR ACTIVE PLUGINS
// =============================================================================
2024-06-19 22:55:12 +02:00
/**
* Checks whether a plugin is active for the entire network
*
* @ since 5.20 . 2
* @ link https :// developer . wordpress . org / reference / functions / is_plugin_active_for_network /
*
* @ param string $path Relative path to the plugin .
*
* @ return boolean True if the plugin is active , otherwise false .
*/
function fictioneer_is_network_plugin_active ( $path ) {
if ( ! is_multisite () ) {
return false ;
}
$plugins = get_site_option ( 'active_sitewide_plugins' );
2023-01-21 01:31:34 +01:00
2024-06-19 22:55:12 +02:00
if ( isset ( $plugins [ $path ] ) ) {
return true ;
2023-01-21 01:31:34 +01:00
}
2024-06-19 22:55:12 +02:00
return false ;
}
/**
* Checks whether a plugin is active
*
* @ since 4.0 . 0
* @ since 5.20 . 2 - Changed to copy of is_plugin_active () .
* @ link https :// developer . wordpress . org / reference / functions / is_plugin_active /
*
* @ param string $path Relative path to the plugin .
*
* @ return boolean True if the plugin is active , otherwise false .
*/
function fictioneer_is_plugin_active ( $path ) {
return in_array ( $path , ( array ) get_option ( 'active_plugins' , [] ), true ) || fictioneer_is_network_plugin_active ( $path );
2023-01-21 01:31:34 +01:00
}
if ( ! function_exists ( 'fictioneer_seo_plugin_active' ) ) {
/**
* Checks whether any SEO plugin known to the theme is active
*
* The theme ' s SEO features are inherently incompatible with SEO plugins , which
* may be more sophisticated but do not understand the theme ' s content structure .
* At all . Regardless , if the user want to use a SEO plugin , it ' s better to turn
* off the theme ' s own SEO features . This function detects some of them .
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-01-21 01:31:34 +01:00
*
* @ return boolean True if a known SEO is active , otherwise false .
*/
function fictioneer_seo_plugin_active () {
2024-05-17 13:37:42 +02:00
return fictioneer_is_plugin_active ( 'wordpress-seo/wp-seo.php' ) ||
2023-08-22 10:28:40 +02:00
fictioneer_is_plugin_active ( 'wordpress-seo-premium/wp-seo-premium.php' ) ||
function_exists ( 'aioseo' );
2023-01-21 01:31:34 +01:00
}
}
// =============================================================================
// GET USER BY ID OR EMAIL
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_user_by_id_or_email' ) ) {
/**
* Get user by ID or email
*
2024-01-26 17:45:59 +01:00
* @ since 4.6 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param int | string $id_or_email User ID or email address .
2023-01-21 01:31:34 +01:00
*
* @ return WP_User | boolean Returns the user or false if not found .
*/
function fictioneer_get_user_by_id_or_email ( $id_or_email ) {
$user = false ;
if ( is_numeric ( $id_or_email ) ) {
$id = ( int ) $id_or_email ;
$user = get_user_by ( 'id' , $id );
} elseif ( is_object ( $id_or_email ) ) {
if ( ! empty ( $id_or_email -> user_id ) ) {
$id = ( int ) $id_or_email -> user_id ;
$user = get_user_by ( 'id' , $id );
}
} else {
$user = get_user_by ( 'email' , $id_or_email );
}
return $user ;
}
}
2023-08-17 18:27:34 +02:00
// =============================================================================
// GET LAST CHAPTER/STORY UPDATE
// =============================================================================
2024-05-19 03:34:41 +02:00
if ( ! function_exists ( 'fictioneer_get_last_fiction_update' ) ) {
2023-08-17 18:27:34 +02:00
/**
* Get Unix timestamp for last story or chapter update
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-08-17 18:27:34 +02:00
*
* @ return int The timestamp in milliseconds .
*/
function fictioneer_get_last_fiction_update () {
$last_update = get_option ( 'fictioneer_story_or_chapter_updated_timestamp' );
if ( empty ( $last_update ) ) {
$last_update = time () * 1000 ;
update_option ( 'fictioneer_story_or_chapter_updated_timestamp' , $last_update );
}
return $last_update ;
}
}
2024-01-26 15:00:53 +01:00
// =============================================================================
// GET STORY CHAPTERS
// =============================================================================
/**
* Returns array of chapter posts for a story
*
2024-01-26 17:45:59 +01:00
* @ since 5.9 . 2
2024-08-15 23:36:45 +02:00
* @ since 5.22 . 3 - Refactored .
2024-01-26 15:00:53 +01:00
*
2024-08-15 12:32:47 +02:00
* @ param int $story_id ID of the story .
* @ param array $args Optional . Additional query arguments .
2024-08-16 15:56:17 +02:00
* @ param bool $full Optional . Whether to not reduce the posts . Default false .
2024-01-26 15:00:53 +01:00
*
* @ return array Array of chapter posts or empty .
*/
2024-08-16 15:56:17 +02:00
function fictioneer_get_story_chapter_posts ( $story_id , $args = [], $full = false ) {
2024-08-15 23:36:45 +02:00
// Static variable cache
static $cached_results = [];
2024-01-26 15:00:53 +01:00
// Setup
$chapter_ids = fictioneer_get_story_chapter_ids ( $story_id );
// No chapters?
if ( empty ( $chapter_ids ) ) {
return [];
}
2024-08-15 23:36:45 +02:00
// Query arguments
2024-04-08 02:55:10 +02:00
$query_args = array (
2024-08-15 23:36:45 +02:00
'fictioneer_query_name' => 'get_story_chapter_posts' ,
2024-04-08 02:55:10 +02:00
'post_type' => 'fcn_chapter' ,
'post_status' => 'publish' ,
'ignore_sticky_posts' => true ,
2024-08-15 23:36:45 +02:00
'posts_per_page' => - 1 ,
2024-04-08 02:55:10 +02:00
'no_found_rows' => true , // Improve performance
'update_post_term_cache' => false // Improve performance
2024-01-26 15:00:53 +01:00
);
2024-08-15 23:36:45 +02:00
// Apply filters and custom arguments
2024-08-15 12:32:47 +02:00
$query_args = array_merge ( $query_args , $args );
2024-08-15 12:30:38 +02:00
$query_args = apply_filters ( 'fictioneer_filter_story_chapter_posts_query' , $query_args , $story_id , $chapter_ids );
2024-04-08 02:55:10 +02:00
2024-08-15 23:36:45 +02:00
// Static cache key
$cache_key = $story_id . '_' . md5 ( serialize ( $query_args ) );
2024-04-08 02:55:10 +02:00
2024-08-15 23:36:45 +02:00
// Static cache hit?
if ( isset ( $cached_results [ $cache_key ] ) ) {
return $cached_results [ $cache_key ];
}
// Batched or one go?
if ( count ( $chapter_ids ) <= FICTIONEER_QUERY_ID_ARRAY_LIMIT ) {
$query_args [ 'post__in' ] = $chapter_ids ? : [ 0 ];
$chapter_query = new WP_Query ( $query_args );
$chapter_posts = $chapter_query -> posts ;
} else {
$chapter_posts = [];
$batches = array_chunk ( $chapter_ids , FICTIONEER_QUERY_ID_ARRAY_LIMIT );
foreach ( $batches as $batch ) {
$query_args [ 'post__in' ] = $batch ? : [ 0 ];
$chapter_query = new WP_Query ( $query_args );
$chapter_posts = array_merge ( $chapter_posts , $chapter_query -> posts );
}
}
2024-01-26 15:00:53 +01:00
2024-08-15 23:36:45 +02:00
// Restore order
$chapter_positions = array_flip ( $chapter_ids );
usort ( $chapter_posts , function ( $a , $b ) use ( $chapter_positions ) {
return $chapter_positions [ $a -> ID ] - $chapter_positions [ $b -> ID ];
2024-01-26 15:00:53 +01:00
});
// Return chapters selected in story
2024-08-15 23:36:45 +02:00
return $chapter_posts ;
2024-01-26 15:00:53 +01:00
}
2024-10-12 04:50:06 +02:00
// =============================================================================
// GROUP CHAPTERS
// =============================================================================
/**
* Groups and prepares chapters for a specific story
*
* Note : If chapter groups are disabled , all chapters will be
2024-10-12 11:17:02 +02:00
* within the 'all_chapters' group .
2024-10-12 04:50:06 +02:00
*
* @ since 5.25 . 0
*
* @ param int $story_id ID of the story .
* @ param array $chapters Array of ( reduced ) WP_Post objects .
*
* @ return array The grouped and prepared chapters .
*/
function fictioneer_prepare_chapter_groups ( $story_id , $chapters ) {
// Any chapters?
if ( empty ( $chapters ) ) {
return [];
}
// Setup
$chapter_groups = [];
$allowed_permalinks = apply_filters ( 'fictioneer_filter_allowed_chapter_permalinks' , [ 'publish' ] );
$enable_groups = get_option ( 'fictioneer_enable_chapter_groups' ) &&
! get_post_meta ( $story_id , 'fictioneer_story_disable_groups' , true );
// Loop chapters...
foreach ( $chapters as $post ) {
$chapter_id = $post -> ID ;
// Skip missing or not visible chapters
if ( ! $post || get_post_meta ( $chapter_id , 'fictioneer_chapter_hidden' , true ) ) {
continue ;
}
// Data
$group = get_post_meta ( $chapter_id , 'fictioneer_chapter_group' , true );
$group = empty ( $group ) ? fcntr ( 'unassigned_group' ) : $group ;
$group = $enable_groups ? $group : 'all_chapters' ;
$group_key = sanitize_title ( $group );
if ( ! array_key_exists ( $group_key , $chapter_groups ) ) {
$chapter_groups [ $group_key ] = array (
'group' => $group ,
'toggle_icon' => 'fa-solid fa-chevron-down' ,
'data' => [],
'count' => 0 ,
'classes' => array (
'_group-' . sanitize_title ( $group ),
" _story- { $story_id } "
)
);
}
$chapter_groups [ $group_key ][ 'data' ][] = array (
'id' => $chapter_id ,
'story_id' => $story_id ,
'status' => $post -> post_status ,
'link' => in_array ( $post -> post_status , $allowed_permalinks ) ? get_permalink ( $post -> ID ) : '' ,
'timestamp' => get_the_time ( 'U' , $post ),
'password' => ! empty ( $post -> post_password ),
'list_date' => get_the_date ( '' , $post ),
'grid_date' => get_the_time ( get_option ( 'fictioneer_subitem_date_format' , " M j, 'y " ) ? : " M j, 'y " , $post ),
'icon' => fictioneer_get_icon_field ( 'fictioneer_chapter_icon' , $chapter_id ),
'text_icon' => get_post_meta ( $chapter_id , 'fictioneer_chapter_text_icon' , true ),
'prefix' => get_post_meta ( $chapter_id , 'fictioneer_chapter_prefix' , true ),
'title' => fictioneer_get_safe_title ( $chapter_id , 'story-chapter-list' ),
'list_title' => get_post_meta ( $chapter_id , 'fictioneer_chapter_list_title' , true ),
'words' => fictioneer_get_word_count ( $chapter_id ),
'warning' => get_post_meta ( $chapter_id , 'fictioneer_chapter_warning' , true )
);
$chapter_groups [ $group_key ][ 'count' ] += 1 ;
}
return $chapter_groups ;
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// GET STORY DATA
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_story_data' ) ) {
/**
* Get collection of a story ' s data
*
2024-01-26 17:45:59 +01:00
* @ since 4.3 . 0
2024-10-08 12:40:29 +02:00
* @ since 5.25 . 0 - Refactored with custom SQL query .
2023-01-21 01:31:34 +01:00
*
2023-08-22 22:16:37 +02:00
* @ param int $story_id ID of the story .
* @ param boolean $show_comments Optional . Whether the comment count is needed .
* Default true .
2024-01-27 00:30:20 +01:00
* @ param array $args Optional array of arguments .
2023-01-21 01:31:34 +01:00
*
2023-08-22 22:16:37 +02:00
* @ return array | boolean Data of the story or false if invalid .
2023-01-21 01:31:34 +01:00
*/
2023-08-22 22:16:37 +02:00
function fictioneer_get_story_data ( $story_id , $show_comments = true , $args = [] ) {
2024-10-08 12:40:29 +02:00
global $wpdb ;
2023-01-21 01:31:34 +01:00
$story_id = fictioneer_validate_id ( $story_id , 'fcn_story' );
2024-01-29 14:52:56 +01:00
$meta_cache = null ;
2023-01-21 01:31:34 +01:00
2023-08-14 20:55:59 +02:00
if ( empty ( $story_id ) ) {
return false ;
}
2023-01-21 01:31:34 +01:00
2024-01-29 17:08:56 +01:00
// Meta cache (purged on update)?
2023-08-14 20:55:59 +02:00
if ( FICTIONEER_ENABLE_STORY_DATA_META_CACHE ) {
2024-01-29 14:52:56 +01:00
$meta_cache = get_post_meta ( $story_id , 'fictioneer_story_data_collection' , true );
2023-08-14 20:55:59 +02:00
}
2023-01-21 01:31:34 +01:00
2024-04-11 01:41:45 +02:00
if ( $meta_cache && ( $meta_cache [ 'last_modified' ] ? ? 0 ) >= get_the_modified_time ( 'U' , $story_id ) ) {
2023-08-07 14:21:45 +02:00
// Return cached data without refreshing the comment count
2023-08-22 22:16:37 +02:00
if ( ! $show_comments ) {
2024-01-29 14:52:56 +01:00
return $meta_cache ;
2023-08-07 14:21:45 +02:00
}
// Time to refresh comment count?
2024-01-29 14:52:56 +01:00
$comment_count_delay = ( $meta_cache [ 'comment_count_timestamp' ] ? ? 0 ) + FICTIONEER_STORY_COMMENT_COUNT_TIMEOUT ;
2024-01-27 00:30:20 +01:00
$refresh_comments = $comment_count_delay < time () ||
2024-01-29 17:08:56 +01:00
( $args [ 'refresh_comment_count' ] ? ? 0 ) || fictioneer_caching_active ( 'story_data_refresh_comment_count' );
2023-08-07 14:21:45 +02:00
2023-08-22 22:16:37 +02:00
// Refresh comment count
if ( $refresh_comments ) {
2023-08-18 13:35:46 +02:00
// Use old count as fallback
2024-01-29 14:52:56 +01:00
$comment_count = $meta_cache [ 'comment_count' ];
2023-08-18 13:35:46 +02:00
2024-01-29 14:52:56 +01:00
if ( count ( $meta_cache [ 'chapter_ids' ] ) > 0 ) {
2024-08-15 12:56:24 +02:00
$comment_count = fictioneer_get_story_comment_count ( $story_id , $meta_cache [ 'chapter_ids' ] );
2023-08-18 13:35:46 +02:00
}
2024-01-29 14:52:56 +01:00
$meta_cache [ 'comment_count' ] = $comment_count ;
$meta_cache [ 'comment_count_timestamp' ] = time ();
2023-08-07 14:21:45 +02:00
2024-11-10 14:46:36 +01:00
// Update post database comment count
2024-12-18 00:41:18 +01:00
$story_comment_count = get_approved_comments ( $story_id , array ( 'count' => true ) ) ? : 0 ;
fictioneer_sql_update_comment_count ( $story_id , $comment_count + $story_comment_count );
2024-11-10 14:46:36 +01:00
2024-01-26 23:30:58 +01:00
// Update meta cache and purge
2024-01-29 14:52:56 +01:00
update_post_meta ( $story_id , 'fictioneer_story_data_collection' , $meta_cache );
2024-01-26 23:52:16 +01:00
if ( function_exists ( 'fictioneer_purge_post_cache' ) ) {
fictioneer_purge_post_cache ( $story_id );
}
2023-08-05 18:31:39 +02:00
}
2023-01-21 01:31:34 +01:00
// Return cached data
2024-01-29 14:52:56 +01:00
return $meta_cache ;
2023-01-21 01:31:34 +01:00
}
// Setup
$tags = get_the_tags ( $story_id );
$fandoms = get_the_terms ( $story_id , 'fcn_fandom' );
$characters = get_the_terms ( $story_id , 'fcn_character' );
$warnings = get_the_terms ( $story_id , 'fcn_content_warning' );
$genres = get_the_terms ( $story_id , 'fcn_genre' );
2023-11-30 16:03:53 +01:00
$status = get_post_meta ( $story_id , 'fictioneer_story_status' , true );
2023-01-21 01:31:34 +01:00
$icon = 'fa-solid fa-circle' ;
$chapter_count = 0 ;
$word_count = 0 ;
2023-08-18 13:35:46 +02:00
$comment_count = 0 ;
2024-01-29 14:52:56 +01:00
$visible_chapter_ids = [];
2024-04-23 02:47:23 +02:00
$indexed_chapter_ids = [];
2023-01-21 01:31:34 +01:00
// Assign correct icon
if ( $status != 'Ongoing' ) {
switch ( $status ) {
case 'Completed' :
$icon = 'fa-solid fa-circle-check' ;
break ;
case 'Oneshot' :
$icon = 'fa-solid fa-circle-check' ;
break ;
case 'Hiatus' :
$icon = 'fa-solid fa-circle-pause' ;
break ;
case 'Canceled' :
$icon = 'fa-solid fa-ban' ;
break ;
}
}
2024-10-08 12:40:29 +02:00
// Custom SQL query to count chapters, words, comments, etc.
// This significantly faster than WP_Query (up to 15 times with 500 chapters)
$queried_statuses = apply_filters ( 'fictioneer_filter_get_story_data_queried_chapter_statuses' , [ 'publish' ], $story_id );
$indexed_statuses = apply_filters ( 'fictioneer_filter_get_story_data_indexed_chapter_statuses' , [ 'publish' ], $story_id );
$chapter_ids = fictioneer_get_story_chapter_ids ( $story_id );
$chapters = [];
if ( ! empty ( $chapter_ids ) ) {
$chapter_ids_placeholder = implode ( ',' , array_fill ( 0 , count ( $chapter_ids ), '%d' ) );
$status_list = array_map ( function ( $status ) use ( $wpdb ) {
return $wpdb -> prepare ( '%s' , $status );
}, $queried_statuses );
$status_list = implode ( ',' , $status_list );
$query = $wpdb -> prepare (
" SELECT
c . ID as chapter_id ,
c . comment_count ,
SUM ( CASE WHEN pm . meta_key = '_word_count' THEN CAST ( pm . meta_value AS UNSIGNED ) ELSE 0 END ) AS word_count ,
MAX ( CASE WHEN pm . meta_key = 'fictioneer_chapter_hidden' THEN pm . meta_value ELSE '' END ) AS is_hidden ,
MAX ( CASE WHEN pm . meta_key = 'fictioneer_chapter_no_chapter' THEN pm . meta_value ELSE '' END ) AS is_no_chapter ,
c . post_status
FROM { $wpdb -> posts } c
LEFT JOIN { $wpdb -> postmeta } pm ON pm . post_id = c . ID
WHERE c . ID IN ( $chapter_ids_placeholder )
AND c . post_status IN ( $status_list )
GROUP BY c . ID " ,
... $chapter_ids // WHERE clause
);
2024-10-08 19:45:53 +02:00
$query = apply_filters ( 'fictioneer_filter_get_story_data_sql' , $query , $story_id , $chapter_ids , $queried_statuses );
2024-10-08 12:40:29 +02:00
$chapters = $wpdb -> get_results ( $query );
usort ( $chapters , function ( $a , $b ) use ( $chapter_ids ) {
$position_a = array_search ( $a -> chapter_id , $chapter_ids );
$position_b = array_search ( $b -> chapter_id , $chapter_ids );
return $position_a - $position_b ;
});
}
foreach ( $chapters as $chapter ) {
if ( empty ( $chapter -> is_hidden ) ) {
// Do not count non-chapters...
if ( empty ( $chapter -> is_no_chapter ) ) {
$chapter_count ++ ;
$word_count += intval ( $chapter -> word_count );
2023-01-21 01:31:34 +01:00
}
2023-08-18 13:35:46 +02:00
2024-10-08 12:40:29 +02:00
// ... but they are still listed!
$visible_chapter_ids [] = $chapter -> chapter_id ;
// Indexed chapters (accounts for custom filters)
if ( in_array ( $chapter -> post_status , $indexed_statuses ) ) {
$indexed_chapter_ids [] = $chapter -> chapter_id ;
}
2023-01-21 01:31:34 +01:00
}
2024-10-08 12:40:29 +02:00
// Count ALL comments
$comment_count += intval ( $chapter -> comment_count );
2023-01-21 01:31:34 +01:00
}
2023-11-14 17:13:23 +01:00
// Add story word count
2024-08-16 01:12:11 +02:00
$word_count += get_post_meta ( $story_id , '_word_count' , true );
// Customize word count
$modified_word_count = fictioneer_multiply_word_count ( $word_count );
2023-11-14 17:13:23 +01:00
2024-08-26 20:27:15 +02:00
// Rating
$rating = get_post_meta ( $story_id , 'fictioneer_story_rating' , true ) ? : 'Everyone' ;
2023-11-14 17:13:23 +01:00
// Prepare result
2023-01-21 01:31:34 +01:00
$result = array (
'id' => $story_id ,
'chapter_count' => $chapter_count ,
2024-08-16 01:12:11 +02:00
'word_count' => $modified_word_count ,
'word_count_short' => fictioneer_shorten_number ( $modified_word_count ),
2023-01-21 01:31:34 +01:00
'status' => $status ,
'icon' => $icon ,
'has_taxonomies' => $fandoms || $characters || $genres ,
'tags' => $tags ,
'characters' => $characters ,
'fandoms' => $fandoms ,
'warnings' => $warnings ,
'genres' => $genres ,
2024-03-01 12:39:42 +01:00
'title' => fictioneer_get_safe_title ( $story_id , 'utility-get-story-data' ),
2024-08-26 20:27:15 +02:00
'rating' => $rating ,
'rating_letter' => $rating [ 0 ],
2024-01-29 14:52:56 +01:00
'chapter_ids' => $visible_chapter_ids ,
2024-04-23 02:47:23 +02:00
'indexed_chapter_ids' => $indexed_chapter_ids ,
2023-01-21 01:31:34 +01:00
'last_modified' => get_the_modified_time ( 'U' , $story_id ),
2023-08-07 14:21:45 +02:00
'comment_count' => $comment_count ,
2024-04-14 22:43:25 +02:00
'comment_count_timestamp' => time (),
'redirect' => get_post_meta ( $story_id , 'fictioneer_story_redirect_link' , true )
2023-01-21 01:31:34 +01:00
);
2024-01-29 14:52:56 +01:00
// Update meta cache if enabled
2023-08-14 20:59:28 +02:00
if ( FICTIONEER_ENABLE_STORY_DATA_META_CACHE ) {
update_post_meta ( $story_id , 'fictioneer_story_data_collection' , $result );
}
2023-01-21 01:31:34 +01:00
2024-04-11 01:41:45 +02:00
// Update story total word count
update_post_meta ( $story_id , 'fictioneer_story_total_word_count' , $word_count );
2024-11-10 14:46:36 +01:00
// Update post database comment count
2024-12-18 00:41:18 +01:00
$story_comment_count = get_approved_comments ( $story_id , array ( 'count' => true ) ) ? : 0 ;
fictioneer_sql_update_comment_count ( $story_id , $comment_count + $story_comment_count );
2024-11-10 14:46:36 +01:00
2024-01-29 14:52:56 +01:00
// Done
2023-01-21 01:31:34 +01:00
return $result ;
}
}
2024-08-13 16:13:31 +02:00
/**
* Returns the comment count of all story chapters
*
* Note : Includes hidden and non - chapter posts .
*
2024-08-15 12:56:24 +02:00
* @ since 5.22 . 2
* @ since 5.22 . 3 - Switched to SQL query .
2024-08-13 16:13:31 +02:00
*
2024-08-15 12:56:24 +02:00
* @ param int $story_id ID of the story .
* @ param array | null $chapter_ids Optional . Array of chapter IDs .
2024-08-13 16:13:31 +02:00
*
* @ return int Number of comments .
*/
2024-08-15 12:56:24 +02:00
function fictioneer_get_story_comment_count ( $story_id , $chapter_ids = null ) {
2024-08-13 16:13:31 +02:00
// Setup
$comment_count = 0 ;
2024-08-15 12:56:24 +02:00
$chapter_ids = $chapter_ids ? ? fictioneer_get_story_chapter_ids ( $story_id );
2024-08-13 16:13:31 +02:00
2024-08-15 12:56:24 +02:00
// No chapters?
if ( empty ( $chapter_ids ) ) {
return 0 ;
}
// SQL
global $wpdb ;
$chunks = array_chunk ( $chapter_ids , FICTIONEER_QUERY_ID_ARRAY_LIMIT );
2024-09-19 02:24:49 +02:00
foreach ( $chunks as $chunk ) {
2024-08-15 12:56:24 +02:00
$placeholders = implode ( ',' , array_fill ( 0 , count ( $chunk ), '%d' ) );
$query = $wpdb -> prepare ( "
SELECT COUNT ( comment_ID )
FROM { $wpdb -> comments } c
INNER JOIN { $wpdb -> posts } p ON c . comment_post_ID = p . ID
WHERE p . post_type = 'fcn_chapter'
AND p . ID IN ( $placeholders )
AND c . comment_approved = '1'
" , ... $chunk );
$comment_count += $wpdb -> get_var ( $query );
2024-08-13 16:13:31 +02:00
}
// Return result
return $comment_count ;
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// GET AUTHOR STATISTICS
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_author_statistics' ) ) {
/**
2024-01-26 14:40:56 +01:00
* Returns an author ' s statistics
2023-01-21 01:31:34 +01:00
*
2024-01-29 13:19:10 +01:00
* Note : Cached as meta field for an hour .
*
2024-01-26 17:45:59 +01:00
* @ since 4.6 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param int $author_id User ID of the author .
2023-01-21 01:31:34 +01:00
*
2024-01-26 14:40:56 +01:00
* @ return array | boolean Array of statistics or false if user does not exist .
2023-01-21 01:31:34 +01:00
*/
function fictioneer_get_author_statistics ( $author_id ) {
// Setup
$author_id = fictioneer_validate_id ( $author_id );
2023-08-22 10:28:40 +02:00
if ( ! $author_id ) {
return false ;
}
2023-01-21 01:31:34 +01:00
$author = get_user_by ( 'id' , $author_id );
2023-08-22 10:28:40 +02:00
if ( ! $author ) {
return false ;
}
2023-01-21 01:31:34 +01:00
2024-01-29 17:08:56 +01:00
$cache_plugin_active = fictioneer_caching_active ( 'author_statistics' );
2024-01-29 13:19:10 +01:00
// Meta cache?
2024-01-29 17:08:56 +01:00
if ( ! $cache_plugin_active ) {
$meta_cache = $author -> fictioneer_author_statistics ;
2023-01-21 01:31:34 +01:00
2024-01-29 17:08:56 +01:00
if ( $meta_cache && ( $meta_cache [ 'valid_until' ] ? ? 0 ) > time () ) {
return $meta_cache ;
}
2023-01-21 01:31:34 +01:00
}
2023-11-12 15:09:03 +01:00
// Get stories
2023-01-21 01:31:34 +01:00
$stories = get_posts (
array (
2023-11-12 12:42:25 +01:00
'post_type' => 'fcn_story' ,
'post_status' => 'publish' ,
2023-01-21 01:31:34 +01:00
'author' => $author_id ,
'numberposts' => - 1 ,
2023-11-12 15:11:56 +01:00
'update_post_meta_cache' => true ,
2023-11-15 10:32:05 +01:00
'update_post_term_cache' => false ,
'no_found_rows' => true
2023-01-21 01:31:34 +01:00
)
);
2023-11-12 15:09:03 +01:00
// Filter out unwanted stories (faster than meta query)
$stories = array_filter ( $stories , function ( $post ) {
// Story hidden?
2023-11-30 16:03:53 +01:00
$story_hidden = get_post_meta ( $post -> ID , 'fictioneer_story_hidden' , true );
2023-11-12 15:09:03 +01:00
2023-11-12 15:09:43 +01:00
return empty ( $story_hidden ) || $story_hidden === '0' ;
2023-11-12 15:09:03 +01:00
});
// Get chapters
2023-01-21 01:31:34 +01:00
$chapters = get_posts (
array (
2023-11-12 12:42:25 +01:00
'post_type' => 'fcn_chapter' ,
'post_status' => 'publish' ,
2023-01-21 01:31:34 +01:00
'author' => $author_id ,
'numberposts' => - 1 ,
2023-11-12 15:11:56 +01:00
'update_post_meta_cache' => true ,
2023-11-15 10:32:05 +01:00
'update_post_term_cache' => false ,
'no_found_rows' => true
2023-01-21 01:31:34 +01:00
)
);
2023-11-12 15:09:03 +01:00
// Filter out unwanted chapters (faster than meta query)
$chapters = array_filter ( $chapters , function ( $post ) {
// Chapter hidden?
2023-11-30 16:03:53 +01:00
$chapter_hidden = get_post_meta ( $post -> ID , 'fictioneer_chapter_hidden' , true );
2023-11-12 15:09:03 +01:00
$not_hidden = empty ( $chapter_hidden ) || $chapter_hidden === '0' ;
// Not a chapter?
2023-11-30 16:03:53 +01:00
$no_chapter = get_post_meta ( $post -> ID , 'fictioneer_chapter_no_chapter' , true );
2023-11-12 15:09:03 +01:00
$is_chapter = empty ( $no_chapter ) || $no_chapter === '0' ;
// Only keep if both conditions are met
return $not_hidden && $is_chapter ;
});
// Count words and comments
2023-01-21 01:31:34 +01:00
$word_count = 0 ;
$comment_count = 0 ;
2024-01-29 13:19:10 +01:00
foreach ( $stories as $story ) {
$word_count += fictioneer_get_word_count ( $story -> ID );
}
2023-01-21 01:31:34 +01:00
foreach ( $chapters as $chapter ) {
2023-11-30 17:10:19 +01:00
$word_count += fictioneer_get_word_count ( $chapter -> ID );
2024-01-27 00:49:01 +01:00
$comment_count += $chapter -> comment_count ;
2023-01-21 01:31:34 +01:00
}
2023-11-12 15:09:03 +01:00
// Prepare results
2023-01-21 01:31:34 +01:00
$result = array (
'story_count' => count ( $stories ),
'chapter_count' => count ( $chapters ),
'word_count' => $word_count ,
'word_count_short' => fictioneer_shorten_number ( $word_count ),
2024-01-27 00:49:01 +01:00
'valid_until' => time () + HOUR_IN_SECONDS ,
2023-01-21 01:31:34 +01:00
'comment_count' => $comment_count
);
2024-01-29 13:19:10 +01:00
// Update meta cache
2024-01-29 17:08:56 +01:00
if ( ! $cache_plugin_active ) {
fictioneer_update_user_meta ( $author_id , 'fictioneer_author_statistics' , $result );
}
2023-01-21 01:31:34 +01:00
2024-01-29 13:19:10 +01:00
// Done
2023-01-21 01:31:34 +01:00
return $result ;
}
}
2024-01-26 14:40:56 +01:00
// =============================================================================
// GET COLLECTION STATISTICS
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_collection_statistics' ) ) {
/**
* Returns a collection ' s statistics
*
2024-01-26 17:45:59 +01:00
* @ since 5.9 . 2
2024-10-28 10:48:26 +01:00
* @ since 5.26 . 0 - Refactored with custom SQL .
*
* @ global wpdb $wpdb WordPress database object .
2024-01-26 14:40:56 +01:00
*
* @ param int $collection_id ID of the collection .
*
* @ return array Array of statistics .
*/
function fictioneer_get_collection_statistics ( $collection_id ) {
2024-10-28 10:48:26 +01:00
global $wpdb ;
2024-01-29 14:52:56 +01:00
// Meta cache?
2024-01-29 17:08:56 +01:00
$cache_plugin_active = fictioneer_caching_active ( 'collection_statistics' );
2024-01-26 23:01:20 +01:00
2024-01-29 17:08:56 +01:00
if ( ! $cache_plugin_active ) {
$meta_cache = get_post_meta ( $collection_id , 'fictioneer_collection_statistics' , true );
if ( $meta_cache && ( $meta_cache [ 'valid_until' ] ? ? 0 ) > time () ) {
return $meta_cache ;
}
2024-01-26 23:01:20 +01:00
}
2024-01-26 14:40:56 +01:00
// Setup
$featured = get_post_meta ( $collection_id , 'fictioneer_collection_items' , true );
$story_count = 0 ;
$word_count = 0 ;
$chapter_count = 0 ;
$comment_count = 0 ;
$found_chapter_ids = []; // Chapters that were already counted
$query_chapter_ids = []; // Chapters that need to be queried
// Empty collection?
if ( empty ( $featured ) ) {
return array (
'story_count' => 0 ,
'word_count' => 0 ,
'chapter_count' => 0 ,
'comment_count' => 0
);
}
2024-10-28 10:48:26 +01:00
// SQL query to analyze collection posts
$placeholders = implode ( ',' , array_fill ( 0 , count ( $featured ), '%d' ) );
$sql =
" SELECT p.ID, p.post_type
FROM { $wpdb -> posts } p
WHERE p . ID IN ( $placeholders ) " ;
$posts = $wpdb -> get_results ( $wpdb -> prepare ( $sql , ... $featured ) );
2024-01-26 14:40:56 +01:00
2024-10-28 10:48:26 +01:00
foreach ( $posts as $post ) {
2024-01-26 14:40:56 +01:00
// Only look at stories and chapters...
2024-10-28 10:48:26 +01:00
if ( $post -> post_type === 'fcn_chapter' ) {
2024-01-26 14:40:56 +01:00
// ... single chapters need to be looked up separately
2024-10-28 10:48:26 +01:00
$query_chapter_ids [] = $post -> ID ;
} elseif ( $post -> post_type === 'fcn_story' ) {
2024-01-26 14:40:56 +01:00
// ... stories have pre-processed data
2024-10-28 10:48:26 +01:00
$story = fictioneer_get_story_data ( $post -> ID , false );
$found_chapter_ids = array_merge ( $found_chapter_ids , $story [ 'chapter_ids' ] );
$word_count += $story [ 'word_count' ];
$chapter_count += $story [ 'chapter_count' ];
2024-01-26 14:40:56 +01:00
$comment_count += $story [ 'comment_count' ];
$story_count += 1 ;
}
}
// Remove duplicate chapters
$found_chapter_ids = array_unique ( $found_chapter_ids );
$query_chapter_ids = array_unique ( $query_chapter_ids );
// Do not query already counted chapters
$query_chapter_ids = array_diff ( $query_chapter_ids , $found_chapter_ids );
2024-10-28 10:48:26 +01:00
// SQL query for lone chapters not belong to featured stories...
if ( ! empty ( $query_chapter_ids ) ) {
$placeholders = implode ( ',' , array_fill ( 0 , count ( $query_chapter_ids ), '%d' ) );
$sql =
" SELECT p.ID, p.comment_count, COALESCE(pm_word_count.meta_value, 0) AS word_count
FROM { $wpdb -> posts } p
LEFT JOIN { $wpdb -> postmeta } pm_hidden
ON ( p . ID = pm_hidden . post_id AND pm_hidden . meta_key = 'fictioneer_chapter_hidden' )
LEFT JOIN { $wpdb -> postmeta } pm_no_chapter
ON ( p . ID = pm_no_chapter . post_id AND pm_no_chapter . meta_key = 'fictioneer_chapter_no_chapter' )
LEFT JOIN { $wpdb -> postmeta } pm_word_count
ON ( p . ID = pm_word_count . post_id AND pm_word_count . meta_key = '_word_count' )
WHERE p . ID IN ( $placeholders )
AND p . post_type = 'fcn_chapter'
AND p . post_status = 'publish'
AND ( pm_hidden . meta_value IS NULL OR pm_hidden . meta_value = '' OR pm_hidden . meta_value = '0' )
AND ( pm_no_chapter . meta_value IS NULL OR pm_no_chapter . meta_value = '' OR pm_no_chapter . meta_value = '0' ) " ;
$chapters = $wpdb -> get_results ( $wpdb -> prepare ( $sql , ... $query_chapter_ids ) );
foreach ( $chapters as $chapter ) {
$comment_count += $chapter -> comment_count ;
$chapter_count += 1 ;
2024-01-26 14:40:56 +01:00
2024-10-28 10:48:26 +01:00
$words = ( int ) $chapter -> word_count ;
$words = max ( 0 , $words );
$words = apply_filters ( 'fictioneer_filter_word_count' , $words , $chapter -> ID );
$words = fictioneer_multiply_word_count ( $words );
2024-01-26 14:40:56 +01:00
2024-10-28 10:48:26 +01:00
$word_count += $words ;
2024-01-26 14:40:56 +01:00
}
}
2024-01-26 23:01:20 +01:00
// Statistics
$statistics = array (
2024-01-26 14:40:56 +01:00
'story_count' => $story_count ,
'word_count' => $word_count ,
'chapter_count' => $chapter_count ,
2024-01-26 23:01:20 +01:00
'comment_count' => $comment_count ,
'valid_until' => time () + 900 // 15 minutes
2024-01-26 14:40:56 +01:00
);
2024-01-26 23:01:20 +01:00
2024-01-29 14:52:56 +01:00
// Update meta cache
2024-01-29 17:08:56 +01:00
if ( ! $cache_plugin_active ) {
update_post_meta ( $collection_id , 'fictioneer_collection_statistics' , $statistics );
}
2024-01-26 23:01:20 +01:00
2024-01-29 14:52:56 +01:00
// Done
2024-01-26 23:01:20 +01:00
return $statistics ;
2024-01-26 14:40:56 +01:00
}
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// SHORTEN NUMBER WITH LETTER
// =============================================================================
if ( ! function_exists ( 'fictioneer_shorten_number' ) ) {
/**
* Shortens a number to a fractional with a letter
*
2024-01-26 17:45:59 +01:00
* @ since 4.5 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param int $number The number to be shortened .
* @ param int $precision Precision of the fraction . Default 1.
2023-01-21 01:31:34 +01:00
*
* @ return string The minified number string .
*/
function fictioneer_shorten_number ( $number , $precision = 1 ) {
2023-11-27 22:41:43 +01:00
$number = intval ( $number );
2023-01-21 01:31:34 +01:00
// The letters are prefixed by a HAIR SPACE ( )
if ( $number < 1000 ) {
2023-01-26 01:50:06 +01:00
return strval ( $number );
2023-01-21 01:31:34 +01:00
} else if ( $number < 1000000 ) {
return number_format ( $number / 1000 , $precision ) . ' K' ;
} else if ( $number < 1000000000 ) {
return number_format ( $number / 1000000 , $precision ) . ' M' ;
} else {
return number_format ( $number / 1000000000 , $precision ) . ' B' ;
}
}
}
// =============================================================================
// VALIDATE ID
// =============================================================================
if ( ! function_exists ( 'fictioneer_validate_id' ) ) {
/**
* Ensures an ID is a valid integer and positive ; optionally checks whether the
2023-08-28 10:43:04 +02:00
* associated post is of a certain types ( or among an array of types )
2023-01-21 01:31:34 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 4.7 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param int $id The ID to validate .
* @ param string | array $for_type Optional . The expected post type ( s ) .
2023-01-21 01:31:34 +01:00
*
2023-08-05 01:59:55 +02:00
* @ return int | boolean The validated ID or false if invalid .
2023-01-21 01:31:34 +01:00
*/
2023-01-26 01:52:15 +01:00
function fictioneer_validate_id ( $id , $for_type = [] ) {
2023-01-21 01:31:34 +01:00
$safe_id = intval ( $id );
2023-08-21 21:32:52 +02:00
$types = is_array ( $for_type ) ? $for_type : [ $for_type ];
2023-01-21 01:31:34 +01:00
2023-08-21 21:32:52 +02:00
if ( empty ( $safe_id ) || $safe_id < 0 ) {
return false ;
}
2023-08-05 01:59:55 +02:00
if ( ! empty ( $for_type ) && ! in_array ( get_post_type ( $safe_id ), $types ) ) {
return false ;
}
2023-01-21 01:31:34 +01:00
return $safe_id ;
}
}
2024-02-04 03:06:11 +01:00
// =============================================================================
// VALIDATE NONCE PLAUSIBILITY
// =============================================================================
if ( ! function_exists ( 'fictioneer_nonce_plausibility' ) ) {
/**
* Checks nonce to be plausible
*
* This helps to evaluate whether a nonce has been malformed , for example
* through a dynamic update from a cache plugin not working properly .
*
* @ since 5.9 . 4
*
* @ param string $nonce The nonce to check .
*
* @ return boolean Whether the nonce is plausible .
*/
function fictioneer_nonce_plausibility ( $nonce ) {
if ( preg_match ( '/^[a-f0-9]{10}$/i' , $nonce ) === 1 ) {
return true ;
}
return false ;
}
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// GET VALIDATED AJAX USER
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_validated_ajax_user' ) ) {
/**
* Get the current user after performing AJAX validations
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $nonce_name Optional . The name of the nonce . Default 'nonce' .
* @ param string $nonce_value Optional . The value of the nonce . Default 'fictioneer_nonce' .
2023-01-21 01:31:34 +01:00
*
* @ return boolean | WP_User False if not valid , the current user object otherwise .
*/
function fictioneer_get_validated_ajax_user ( $nonce_name = 'nonce' , $nonce_value = 'fictioneer_nonce' ) {
// Setup
$user = wp_get_current_user ();
// Validate
if (
2023-08-21 15:31:07 +02:00
! $user -> exists () ||
2023-01-21 01:31:34 +01:00
! check_ajax_referer ( $nonce_value , $nonce_name , false )
) {
return false ;
}
return $user ;
}
}
// =============================================================================
2023-09-01 21:36:18 +02:00
// RATE LIMIT
// =============================================================================
/**
* Checks rate limit globally or for an action via the session
*
2024-01-26 17:45:59 +01:00
* @ since 5.7 . 1
2023-09-01 21:36:18 +02:00
*
2023-09-01 21:41:52 +02:00
* @ param string $action The action to check for rate - limiting .
* Defaults to 'fictioneer_global' .
* @ param int | null $max Optional . Maximum number of requests .
* Defaults to FICTIONEER_REQUESTS_PER_MINUTE .
2023-09-01 21:36:18 +02:00
*/
2023-09-01 21:41:52 +02:00
function fictioneer_check_rate_limit ( $action = 'fictioneer_global' , $max = null ) {
2023-09-01 21:36:18 +02:00
if ( ! get_option ( 'fictioneer_enable_rate_limits' ) ) {
return ;
}
// Start session if not already done
if ( session_status () == PHP_SESSION_NONE ) {
session_start ();
}
// Initialize if not set
if ( ! isset ( $_SESSION [ $action ][ 'request_times' ] ) ) {
$_SESSION [ $action ][ 'request_times' ] = [];
}
// Setup
$current_time = microtime ( true );
$time_window = 60 ;
2023-09-01 21:41:52 +02:00
$max = $max ? absint ( $max ) : FICTIONEER_REQUESTS_PER_MINUTE ;
$max = max ( 1 , $max );
2023-09-01 21:36:18 +02:00
// Filter out old timestamps
$_SESSION [ $action ][ 'request_times' ] = array_filter (
$_SESSION [ $action ][ 'request_times' ],
function ( $time ) use ( $current_time , $time_window ) {
return ( $current_time - $time ) < $time_window ;
}
);
// Limit exceeded?
2023-09-01 21:41:52 +02:00
if ( count ( $_SESSION [ $action ][ 'request_times' ] ) >= $max ) {
2023-09-01 21:36:18 +02:00
http_response_code ( 429 ); // Too many requests
exit ;
}
// Record the current request time
$_SESSION [ $action ][ 'request_times' ][] = $current_time ;
}
// =============================================================================
2023-01-21 01:31:34 +01:00
// KEY/VALUE STRING REPLACEMENT
// =============================================================================
if ( ! function_exists ( 'fictioneer_replace_key_value' ) ) {
/**
* Replaces key / value pairs in a string
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $text Text that has key / value pairs to be replaced .
* @ param array $args The key / value pairs .
2023-11-30 02:37:05 +01:00
* @ param string $default Optional . To be used if the text is empty .
* Default is an empty string .
2023-01-21 01:31:34 +01:00
*
* @ return string The modified text .
*/
function fictioneer_replace_key_value ( $text , $args , $default = '' ) {
// Check if text exists
2023-08-22 10:28:40 +02:00
if ( empty ( $text ) ) {
$text = $default ;
}
2023-01-21 01:31:34 +01:00
2023-11-30 02:37:05 +01:00
// Check args
$args = is_array ( $args ) ? $args : [];
2023-01-21 01:31:34 +01:00
2023-11-30 02:37:05 +01:00
// Filter args
$args = array_filter ( $args , 'is_scalar' );
2023-01-21 01:31:34 +01:00
// Return modified text
2023-11-30 02:37:05 +01:00
return trim ( strtr ( $text , $args ) );
2023-01-21 01:31:34 +01:00
}
}
// =============================================================================
// CHECK USER CAPABILITIES
// =============================================================================
if ( ! function_exists ( 'fictioneer_is_admin' ) ) {
/**
* Checks if an user is an administrator
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-10-06 02:44:44 +02:00
* @ param int $user_id The user ID to check .
2023-01-21 01:31:34 +01:00
*
* @ return boolean To be or not to be .
*/
2023-10-06 02:44:44 +02:00
function fictioneer_is_admin ( $user_id ) {
2023-01-21 01:31:34 +01:00
// Abort conditions
2023-08-22 10:28:40 +02:00
if ( ! $user_id ) {
return false ;
}
2023-01-21 01:31:34 +01:00
// Check capabilities
$check = user_can ( $user_id , 'administrator' );
// Filter
$check = apply_filters ( 'fictioneer_filter_is_admin' , $check , $user_id );
// Return result
return $check ;
}
}
if ( ! function_exists ( 'fictioneer_is_author' ) ) {
/**
* Checks if an user is an author
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-10-06 02:44:44 +02:00
* @ param int $user_id The user ID to check .
2023-01-21 01:31:34 +01:00
*
* @ return boolean To be or not to be .
*/
2023-10-06 02:44:44 +02:00
function fictioneer_is_author ( $user_id ) {
2023-01-21 01:31:34 +01:00
// Abort conditions
2023-08-22 10:28:40 +02:00
if ( ! $user_id ) {
return false ;
}
2023-01-21 01:31:34 +01:00
// Check capabilities
2024-08-17 18:37:50 +02:00
$check = user_can ( $user_id , 'publish_posts' ) ||
user_can ( $user_id , 'publish_fcn_stories' ) ||
user_can ( $user_id , 'publish_fcn_chapters' ) ||
user_can ( $user_id , 'publish_fcn_collections' );
2023-01-21 01:31:34 +01:00
// Filter
$check = apply_filters ( 'fictioneer_filter_is_author' , $check , $user_id );
// Return result
return $check ;
}
}
if ( ! function_exists ( 'fictioneer_is_moderator' ) ) {
/**
* Checks if an user is a moderator
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-10-06 02:44:44 +02:00
* @ param int $user_id The user ID to check .
2023-01-21 01:31:34 +01:00
*
* @ return boolean To be or not to be .
*/
2023-10-06 02:44:44 +02:00
function fictioneer_is_moderator ( $user_id ) {
2023-01-21 01:31:34 +01:00
// Abort conditions
2023-08-22 10:28:40 +02:00
if ( ! $user_id ) {
return false ;
}
2023-01-21 01:31:34 +01:00
// Check capabilities
$check = user_can ( $user_id , 'moderate_comments' );
// Filter
$check = apply_filters ( 'fictioneer_filter_is_moderator' , $check , $user_id );
// Return result
return $check ;
}
}
if ( ! function_exists ( 'fictioneer_is_editor' ) ) {
/**
* Checks if an user is an editor
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-10-06 02:44:44 +02:00
* @ param int $user_id The user ID to check .
2023-01-21 01:31:34 +01:00
*
* @ return boolean To be or not to be .
*/
2023-10-06 02:44:44 +02:00
function fictioneer_is_editor ( $user_id ) {
2023-01-21 01:31:34 +01:00
// Abort conditions
2023-08-22 10:28:40 +02:00
if ( ! $user_id ) {
return false ;
}
2023-01-21 01:31:34 +01:00
// Check capabilities
$check = user_can ( $user_id , 'editor' ) || user_can ( $user_id , 'administrator' );
// Filter
$check = apply_filters ( 'fictioneer_filter_is_editor' , $check , $user_id );
// Return result
return $check ;
}
}
// =============================================================================
// GET META FIELDS
// =============================================================================
2024-10-27 13:03:27 +01:00
if ( ! function_exists ( 'fictioneer_get_story_chapter_ids' ) ) {
/**
* Wrapper for get_post_meta () to get story chapter IDs
*
* @ since 5.8 . 2
*
* @ param int $post_id Optional . The ID of the post the field belongs to .
* Defaults to current post ID .
*
* @ return array Array of chapter post IDs or an empty array .
*/
function fictioneer_get_story_chapter_ids ( $post_id = null ) {
// Setup
$chapter_ids = get_post_meta ( $post_id ? ? get_the_ID (), 'fictioneer_story_chapters' , true );
// Always return an array
return is_array ( $chapter_ids ) ? $chapter_ids : [];
}
}
2024-10-10 00:10:24 +02:00
if ( ! function_exists ( 'fictioneer_count_words' ) ) {
/**
* Returns word count of a post
*
* @ since 5.25 . 0
*
* @ param int $post_id ID of the post to count the words of .
* @ param string | null $content Optional . The post content . Queries the field by default .
*
* @ return int The word count .
*/
function fictioneer_count_words ( $post_id , $content = null ) {
// Prepare
$content = $content ? ? get_post_field ( 'post_content' , $post_id );
$content = strip_shortcodes ( $content );
$content = strip_tags ( $content );
$content = preg_replace ( [ '/--/' , " /['’ ‘ -]/ " ], [ '—' , '' ], $content );
// Count and return result
return count ( preg_split ( '/\s+/' , $content ) ? : [] );
}
}
2023-11-30 17:10:19 +01:00
if ( ! function_exists ( 'fictioneer_get_word_count' ) ) {
/**
* Wrapper for get_post_meta () to get word count
*
2024-01-26 17:45:59 +01:00
* @ since 5.8 . 2
2024-02-03 14:29:44 +01:00
* @ since 5.9 . 4 - Add logic for optional multiplier .
2024-08-16 01:12:11 +02:00
* @ since 5.22 . 3 - Moved multiplier to fictioneer_multiply_word_count ()
2024-08-28 19:03:43 +02:00
* @ since 5.23 . 1 - Added filter and additional sanitization .
2023-11-30 17:10:19 +01:00
*
* @ param int $post_id Optional . The ID of the post the field belongs to .
* Defaults to current post ID .
*
* @ return int The word count or 0.
*/
function fictioneer_get_word_count ( $post_id = null ) {
2024-08-28 19:03:43 +02:00
// Get word count
$words = get_post_meta ( $post_id ? ? get_the_ID (), '_word_count' , true ) ? : 0 ;
$words = max ( 0 , intval ( $words ) );
// Filter
$words = apply_filters ( 'fictioneer_filter_word_count' , $words , $post_id );
2024-08-16 01:12:11 +02:00
// Apply multiplier and return
return fictioneer_multiply_word_count ( $words );
}
}
if ( ! function_exists ( 'fictioneer_get_story_word_count' ) ) {
/**
* Returns word count for the whole story
*
* @ since 5.22 . 3
2024-08-28 19:03:43 +02:00
* @ since 5.23 . 1 - Added filter and additional sanitization .
2024-08-16 01:12:11 +02:00
*
* @ param int $post_id Post ID of the story .
*
* @ return int The word count or 0.
*/
function fictioneer_get_story_word_count ( $post_id = null ) {
2024-08-28 19:03:43 +02:00
// Get word count
2024-08-16 01:12:11 +02:00
$words = get_post_meta ( $post_id , 'fictioneer_story_total_word_count' , true ) ? : 0 ;
2024-08-28 19:03:43 +02:00
$words = max ( 0 , intval ( $words ) );
// Filter
$words = apply_filters ( 'fictioneer_filter_story_word_count' , $words , $post_id );
2024-08-16 01:12:11 +02:00
// Apply multiplier and return
return fictioneer_multiply_word_count ( $words );
}
}
if ( ! function_exists ( 'fictioneer_multiply_word_count' ) ) {
/**
* Multiplies word count with factor from options
*
* @ since 5.22 . 3
*
* @ param int $words Word count .
*
* @ return int The updated word count .
*/
function fictioneer_multiply_word_count ( $words ) {
// Setup
2024-02-03 14:29:44 +01:00
$multiplier = floatval ( get_option ( 'fictioneer_word_count_multiplier' , 1.0 ) );
2024-08-16 01:12:11 +02:00
// Multiply
2024-02-03 14:29:44 +01:00
if ( $multiplier !== 1.0 ) {
$words = intval ( $words * $multiplier );
}
2023-11-30 17:10:19 +01:00
// Always return an integer greater or equal 0
2024-08-28 19:03:43 +02:00
return max ( 0 , $words );
2023-11-30 17:10:19 +01:00
}
}
2023-01-21 01:31:34 +01:00
if ( ! function_exists ( 'fictioneer_get_content_field' ) ) {
/**
2024-01-28 00:55:23 +01:00
* Wrapper for get_post_meta () with content filters applied
2023-01-21 01:31:34 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $field Name of the meta field to retrieve .
* @ param int $post_id Optional . The ID of the post the field belongs to .
* Defaults to current post ID .
2023-01-21 01:31:34 +01:00
*
* @ return string The single field value formatted as content .
*/
function fictioneer_get_content_field ( $field , $post_id = null ) {
// Setup
2023-11-30 16:03:53 +01:00
$content = get_post_meta ( $post_id ? ? get_the_ID (), $field , true );
2023-01-21 01:31:34 +01:00
// Apply default filter functions from the_content (but nothing else)
2024-06-28 11:33:07 +02:00
$content = do_blocks ( $content );
2023-01-21 01:31:34 +01:00
$content = wptexturize ( $content );
$content = convert_chars ( $content );
$content = wpautop ( $content );
$content = shortcode_unautop ( $content );
$content = prepend_attachment ( $content );
2024-06-28 11:33:07 +02:00
$content = wp_replace_insecure_home_url ( $content );
$content = wp_filter_content_tags ( $content );
$content = convert_smilies ( $content );
2023-01-21 01:31:34 +01:00
// Return formatted/filtered content
return $content ;
}
}
if ( ! function_exists ( 'fictioneer_get_icon_field' ) ) {
/**
2023-11-30 16:03:53 +01:00
* Wrapper for get_post_meta () to get Font Awesome icon class
2023-01-21 01:31:34 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2024-11-05 20:53:19 +01:00
* @ since 5.26 . 0 - Add $icon parameter as override .
2023-01-21 01:31:34 +01:00
*
2024-11-05 20:53:19 +01:00
* @ param string $field Name of the meta field to retrieve .
* @ param int | null $post_id Optional . The ID of the post the field belongs to .
* Defaults to current post ID .
* @ param string | null $icon Optional . Pre - retrieved icon string as override . Default null .
2023-01-21 01:31:34 +01:00
*
* @ return string The Font Awesome class .
*/
2024-11-05 20:53:19 +01:00
function fictioneer_get_icon_field ( $field , $post_id = null , $icon = null ) {
2023-01-21 01:31:34 +01:00
// Setup
2024-11-05 20:53:19 +01:00
$icon = $icon ? ? get_post_meta ( $post_id ? ? get_the_ID (), $field , true );
2023-11-20 14:16:05 +01:00
$icon_object = json_decode ( $icon ); // Check for ACF Font Awesome plugin
2023-01-21 01:31:34 +01:00
// Valid?
2023-09-22 13:25:45 +02:00
if ( ! $icon_object && ( empty ( $icon ) || strpos ( $icon , 'fa-' ) !== 0 ) ) {
2024-05-23 20:30:08 +02:00
return FICTIONEER_DEFAULT_CHAPTER_ICON ;
2023-09-22 13:25:45 +02:00
}
if ( $icon_object && ( ! property_exists ( $icon_object , 'style' ) || ! property_exists ( $icon_object , 'id' ) ) ) {
2024-05-23 20:30:08 +02:00
return FICTIONEER_DEFAULT_CHAPTER_ICON ;
2023-01-21 01:31:34 +01:00
}
// Return
if ( $icon_object && property_exists ( $icon_object , 'style' ) && property_exists ( $icon_object , 'id' ) ) {
return 'fa-' . $icon_object -> style . ' fa-' . $icon_object -> id ;
} else {
return esc_attr ( $icon );
}
}
}
2023-09-18 14:49:36 +02:00
// =============================================================================
// UPDATE META FIELDS
// =============================================================================
2023-09-18 18:06:46 +02:00
if ( ! function_exists ( 'fictioneer_update_user_meta' ) ) {
/**
* Wrapper to update user meta
*
* If the meta value is truthy , the meta field is updated as normal .
* If not , the meta field is deleted instead to keep the database tidy .
*
* @ since 5.7 . 3
*
* @ param int $user_id The ID of the user .
* @ param string $meta_key The meta key to update .
* @ param mixed $meta_value The new meta value . If empty , the meta key will be deleted .
* @ param mixed $prev_value Optional . If specified , only updates existing metadata with this value .
* Otherwise , update all entries . Default empty .
*
* @ return int | bool Meta ID if the key didn ' t exist on update , true on successful update or delete ,
* false on failure or if the value passed to the function is the same as the one
* that is already in the database .
*/
function fictioneer_update_user_meta ( $user_id , $meta_key , $meta_value , $prev_value = '' ) {
2023-10-03 23:42:37 +02:00
if ( empty ( $meta_value ) && ! in_array ( $meta_key , fictioneer_get_falsy_meta_allow_list () ) ) {
2023-09-18 18:06:46 +02:00
return delete_user_meta ( $user_id , $meta_key );
} else {
return update_user_meta ( $user_id , $meta_key , $meta_value , $prev_value );
}
}
}
2023-09-18 14:49:36 +02:00
if ( ! function_exists ( 'fictioneer_update_comment_meta' ) ) {
/**
* Wrapper to update comment meta
*
* If the meta value is truthy , the meta field is updated as normal .
* If not , the meta field is deleted instead to keep the database tidy .
*
* @ since 5.7 . 3
*
* @ param int $comment_id The ID of the comment .
* @ param string $meta_key The meta key to update .
* @ param mixed $meta_value The new meta value . If empty , the meta key will be deleted .
* @ param mixed $prev_value Optional . If specified , only updates existing metadata with this value .
* Otherwise , update all entries . Default empty .
2023-09-18 17:46:56 +02:00
*
* @ return int | bool Meta ID if the key didn ' t exist on update , true on successful update or delete ,
* false on failure or if the value passed to the function is the same as the one
* that is already in the database .
2023-09-18 14:49:36 +02:00
*/
function fictioneer_update_comment_meta ( $comment_id , $meta_key , $meta_value , $prev_value = '' ) {
2023-10-03 23:42:37 +02:00
if ( empty ( $meta_value ) && ! in_array ( $meta_key , fictioneer_get_falsy_meta_allow_list () ) ) {
2023-09-18 17:46:56 +02:00
return delete_comment_meta ( $comment_id , $meta_key );
2023-09-18 14:49:36 +02:00
} else {
2023-09-18 17:46:56 +02:00
return update_comment_meta ( $comment_id , $meta_key , $meta_value , $prev_value );
2023-09-18 14:49:36 +02:00
}
}
}
2023-09-19 23:46:19 +02:00
if ( ! function_exists ( 'fictioneer_update_post_meta' ) ) {
/**
* Wrapper to update post meta
*
* If the meta value is truthy , the meta field is updated as normal .
* If not , the meta field is deleted instead to keep the database tidy .
*
* @ since 5.7 . 4
*
* @ param int $post_id The ID of the post .
* @ param string $meta_key The meta key to update .
* @ param mixed $meta_value The new meta value . If empty , the meta key will be deleted .
* @ param mixed $prev_value Optional . If specified , only updates existing metadata with this value .
* Otherwise , update all entries . Default empty .
*
* @ return int | bool Meta ID if the key didn ' t exist on update , true on successful update or delete ,
* false on failure or if the value passed to the function is the same as the one
* that is already in the database .
*/
function fictioneer_update_post_meta ( $post_id , $meta_key , $meta_value , $prev_value = '' ) {
2023-10-03 23:42:37 +02:00
if ( empty ( $meta_value ) && ! in_array ( $meta_key , fictioneer_get_falsy_meta_allow_list () ) ) {
2023-09-19 23:46:19 +02:00
return delete_post_meta ( $post_id , $meta_key );
} else {
return update_post_meta ( $post_id , $meta_key , $meta_value , $prev_value );
}
}
}
2023-10-03 23:42:37 +02:00
/**
* Return allow list for falsy meta fields
*
* @ since 5.7 . 4
*
* @ return array Meta fields allowed to be saved falsy and not be deleted .
*/
function fictioneer_get_falsy_meta_allow_list () {
2024-05-10 23:01:01 +02:00
return apply_filters ( 'fictioneer_filter_falsy_meta_allow_list' , [] );
2023-10-03 23:42:37 +02:00
}
2023-12-31 04:03:06 +01:00
// =============================================================================
// APPEND CHAPTER
// =============================================================================
/**
* Appends new chapters to story list
*
2024-01-26 17:45:59 +01:00
* @ since 5.4 . 9
* @ since 5.7 . 4 - Updated
* @ since 5.8 . 6 - Added $force param and moved function .
2024-06-10 02:25:46 +02:00
* @ since 5.19 . 1 - Always append chapter to story .
2023-12-31 04:03:06 +01:00
*
* @ param int $post_id The chapter post ID .
* @ param int $story_id The story post ID .
* @ param bool $force Optional . Whether to skip some guard clauses . Default false .
*/
function fictioneer_append_chapter_to_story ( $post_id , $story_id , $force = false ) {
2024-09-25 22:29:18 +02:00
$allowed_statuses = apply_filters (
'fictioneer_filter_append_chapter_to_story_statuses' ,
2024-10-06 15:54:22 +02:00
[ 'publish' , 'future' ],
2024-09-25 22:29:18 +02:00
$post_id ,
$story_id ,
$force
);
2024-09-26 00:47:39 +07:00
2024-09-25 22:29:18 +02:00
// Abort if chapter status is not allowed
2024-09-26 00:47:39 +07:00
if ( ! in_array ( get_post_status ( $post_id ), $allowed_statuses ) ) {
2024-09-03 13:21:24 +02:00
return ;
}
2023-12-31 04:03:06 +01:00
// Setup
$story = get_post ( $story_id );
2024-08-14 23:19:05 +02:00
// Abort if story not found or not a story
if ( ! $story || $story -> post_type !== 'fcn_story' ) {
2023-12-31 04:03:06 +01:00
return ;
}
// Setup, continued
$chapter_author_id = get_post_field ( 'post_author' , $post_id );
$story_author_id = get_post_field ( 'post_author' , $story_id );
2024-10-30 08:46:08 +01:00
$co_authored_story_ids = fictioneer_sql_get_co_authored_story_ids ( $chapter_author_id );
2023-12-31 04:03:06 +01:00
// Abort if the author IDs do not match
2024-10-29 16:32:32 +01:00
if (
$chapter_author_id != $story_author_id &&
! in_array ( $story_id , $co_authored_story_ids ) &&
! $force
) {
2023-12-31 04:03:06 +01:00
return ;
}
// Get current story chapters
2024-01-26 00:24:31 +01:00
$story_chapters = fictioneer_get_story_chapter_ids ( $story_id );
2023-12-31 04:03:06 +01:00
// Append chapter (if not already included) and save to database
if ( ! in_array ( $post_id , $story_chapters ) ) {
$previous_chapters = $story_chapters ;
$story_chapters [] = $post_id ;
$story_chapters = array_unique ( $story_chapters );
2024-01-03 08:36:42 +01:00
// Save updated list
2023-12-31 04:03:06 +01:00
update_post_meta ( $story_id , 'fictioneer_story_chapters' , $story_chapters );
2024-10-06 15:54:22 +02:00
// Remember when chapters have been changed
2024-06-27 20:50:02 +02:00
update_post_meta ( $story_id , 'fictioneer_chapters_modified' , current_time ( 'mysql' , true ) );
2024-10-06 15:54:22 +02:00
// Remember when chapters have been added
$allowed_statuses = apply_filters (
'fictioneer_filter_chapters_added_statuses' ,
[ 'publish' ],
$post_id
);
if ( in_array ( get_post_status ( $post_id ), $allowed_statuses ) ) {
if ( ! get_post_meta ( $post_id , 'fictioneer_chapter_hidden' , true ) ) {
update_post_meta ( $story_id , 'fictioneer_chapters_added' , current_time ( 'mysql' , true ) );
}
}
2023-12-31 04:03:06 +01:00
// Log changes
fictioneer_log_story_chapter_changes ( $story_id , $story_chapters , $previous_chapters );
2024-01-29 18:31:01 +01:00
// Clear meta caches to ensure they get refreshed
2023-12-31 04:03:06 +01:00
delete_post_meta ( $story_id , 'fictioneer_story_data_collection' );
2024-01-29 18:31:01 +01:00
delete_post_meta ( $story_id , 'fictioneer_story_chapter_index_html' );
2023-12-31 04:03:06 +01:00
} else {
// Nothing to do
return ;
}
// Update story post to fire associated actions
wp_update_post ( array ( 'ID' => $story_id ) );
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// GET COOKIE CONSENT
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_consent' ) && get_option ( 'fictioneer_cookie_banner' ) ) {
/**
* Get cookie consent
*
* Checks the current user’ s consent cookie for their preferences , either 'full'
* or 'necessary' by default . Returns false if no consent cookie is set .
*
2024-01-26 17:45:59 +01:00
* @ since 4.7 . 0
2023-01-21 01:31:34 +01:00
*
* @ return boolean | string Either false or a string describing the level of consent .
*/
function fictioneer_get_consent () {
2023-08-28 10:43:04 +02:00
if ( ! isset ( $_COOKIE [ 'fcn_cookie_consent' ] ) || $_COOKIE [ 'fcn_cookie_consent' ] === '' ) {
return false ;
}
2023-01-21 01:31:34 +01:00
return strval ( $_COOKIE [ 'fcn_cookie_consent' ] );
}
}
// =============================================================================
// SANITIZE INTEGER
// =============================================================================
/**
2024-02-03 14:29:44 +01:00
* Sanitizes an integer with options for default , minimum , and maximum
2023-01-21 01:31:34 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-28 10:43:04 +02:00
* @ param mixed $value The value to be sanitized .
* @ param int $default Default value if an invalid integer is provided . Default 0.
* @ param int $min Optional . Minimum value for the integer . Default is no minimum .
* @ param int $max Optional . Maximum value for the integer . Default is no maximum .
2023-01-21 01:31:34 +01:00
*
* @ return int The sanitized integer .
*/
2023-08-28 10:43:04 +02:00
function fictioneer_sanitize_integer ( $value , $default = 0 , $min = null , $max = null ) {
// Ensure $value is numeric in the first place
if ( ! is_numeric ( $value ) ) {
return $default ;
}
// Cast to integer
2023-01-21 01:31:34 +01:00
$value = ( int ) $value ;
2023-08-28 10:43:04 +02:00
// Apply minimum limit if specified
if ( $min !== null && $value < $min ) {
return $min ;
}
// Apply maximum limit if specified
if ( $max !== null && $value > $max ) {
return $max ;
}
2023-01-21 01:31:34 +01:00
return $value ;
}
2024-02-03 14:29:44 +01:00
// =============================================================================
// SANITIZE POSITIVE FLOAT
// =============================================================================
/**
* Sanitizes a float as positive number
*
* @ since 5.9 . 4
*
* @ param mixed $value The value to be sanitized .
* @ param int $default Default value if an invalid float is provided . Default 0.0 .
*
* @ return float The sanitized float .
*/
function fictioneer_sanitize_positive_float ( $value , $default = 0.0 ) {
// Ensure $value is numeric in the first place
if ( ! is_numeric ( $value ) ) {
return $default ;
}
// Cast to float
$value = ( float ) $value ;
// Return positive float
return $value < 0 ? $default : $value ;
}
/**
2024-02-12 13:58:11 +01:00
* Sanitize callback with positive float or default 1.0
2024-02-03 14:29:44 +01:00
*
2024-02-12 13:58:11 +01:00
* @ since 5.10 . 1
2024-02-03 14:29:44 +01:00
*
* @ param mixed $value The value to be sanitized .
*
2024-02-12 13:58:11 +01:00
* @ return float The sanitized positive float .
2024-02-03 14:29:44 +01:00
*/
2024-05-30 13:54:00 +02:00
function fictioneer_sanitize_positive_float_def1 ( $value ) {
2024-02-03 14:29:44 +01:00
// Ensure $value is numeric in the first place
if ( ! is_numeric ( $value ) ) {
return 1.0 ;
}
// Call general sanitizer with params
return fictioneer_sanitize_positive_float ( $value , 1.0 );
}
2024-05-30 13:55:24 +02:00
/**
* Sanitize callback with float or default 0
*
* @ since 5.19 . 0
*
* @ param mixed $value The value to be sanitized .
*
* @ return float The sanitized float .
*/
function fictioneer_sanitize_float ( $value ) {
// Ensure $value is numeric in the first place
if ( ! is_numeric ( $value ) ) {
return 0.0 ;
}
// Cast to float
return ( float ) $value ;
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// SANITIZE CHECKBOX
// =============================================================================
/**
* Sanitizes a checkbox value into true or false
*
2024-01-26 17:45:59 +01:00
* @ since 4.7 . 0
2023-11-10 22:22:33 +01:00
* @ link https :// www . php . net / manual / en / function . filter - var . php
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string | boolean $value The checkbox value to be sanitized .
2023-01-21 01:31:34 +01:00
*
* @ return boolean True or false .
*/
function fictioneer_sanitize_checkbox ( $value ) {
2023-10-03 23:42:14 +02:00
$value = filter_var ( $value , FILTER_VALIDATE_BOOLEAN , FILTER_NULL_ON_FAILURE );
return empty ( $value ) ? 0 : 1 ;
2023-01-21 01:31:34 +01:00
}
2023-09-19 23:46:31 +02:00
// =============================================================================
// SANITIZE SELECT OPTION
// =============================================================================
/**
* Sanitizes a selected option
*
* @ since 5.7 . 4
*
* @ param mixed $value The selected value to be sanitized .
* @ param array $allowed_options The allowed values to be checked against .
* @ param mixed $default Optional . The default value as fallback .
*
* @ return mixed The sanitized value or default , null if not provided .
*/
function fictioneer_sanitize_selection ( $value , $allowed_options , $default = null ) {
$value = sanitize_text_field ( $value );
$value = is_numeric ( $value ) ? intval ( $value ) : $value ;
return in_array ( $value , $allowed_options ) ? $value : $default ;
}
2023-09-29 19:41:28 +02:00
// =============================================================================
// SANITIZE CSS
// =============================================================================
/**
* Sanitizes a CSS string
*
* @ since 5.7 . 4
*
* @ param string $css The CSS string to be sanitized .
*
* @ return string The sanitized string .
*/
function fictioneer_sanitize_css ( $css ) {
$css = sanitize_textarea_field ( $css );
$css = preg_match ( '/<\/?\w+/' , $css ) ? '' : $css ;
$opening_braces = substr_count ( $css , '{' );
$closing_braces = substr_count ( $css , '}' );
if ( $opening_braces < 1 || $opening_braces !== $closing_braces ) {
$css = '' ;
}
return $css ;
}
2024-05-01 12:53:35 +02:00
// =============================================================================
// SANITIZE LIST INTO ARRAY
// =============================================================================
/**
* Sanitizes ( and transforms ) a comma - separated list into array
*
* @ since 5.15 . 0
*
* @ param string $input The comma - separated list .
* @ param array $args {
* Optional . An array of additional arguments .
*
* @ type bool $unique Run array through array_unique () . Default false .
* @ type bool $absint Run all elements through absint () . Default false .
* }
*
* @ return array The comma - separated list turned array .
*/
function fictioneer_sanitize_list_into_array ( $input , $args = [] ) {
$input = fictioneer_explode_list ( sanitize_textarea_field ( $input ) );
if ( $args [ 'absint' ] ? ? 0 ) {
$input = array_map ( 'absint' , $input );
}
if ( $args [ 'unique' ] ? ? 0 ) {
$input = array_unique ( $input );
}
return $input ;
}
2024-04-24 12:17:08 +02:00
// =============================================================================
// SANITIZE QUERY VARIABLE
// =============================================================================
/**
* Sanitizes a query variable
*
* @ since 5.14 . 0
*
* @ param string $var Query variable to sanitize .
* @ param array $allowed Array of allowed string ( lowercase ) .
* @ param string | null $default Optional default value .
* @ param array $args {
* Optional . An array of additional arguments .
*
* @ type bool $keep_case Whether to transform the variable to lowercase . Default false .
* }
*
*
* @ return string The sanitized ( lowercase ) query variable .
*/
function fictioneer_sanitize_query_var ( $var , $allowed , $default = null , $args = [] ) {
2024-05-17 15:26:51 +02:00
if ( isset ( $args [ 'keep_case' ] ) ) {
2024-04-24 12:17:08 +02:00
$sanitized = array_intersect ( [ $var ? ? 0 ], $allowed );
} else {
$sanitized = array_intersect ( [ strtolower ( $var ? ? 0 ) ], $allowed );
}
return reset ( $sanitized ) ? : $default ;
}
2024-06-10 10:22:25 +02:00
// =============================================================================
// SANITIZE URL
// =============================================================================
/**
* Sanitizes an URL
*
* @ since 5.19 . 1
*
2024-06-10 11:05:00 +02:00
* @ param string $url The URL entered .
* @ param string | null $match Optional . URL must start with this string .
* @ param string | null $preg_match Optional . String for a preg_match () test .
2024-06-10 10:22:25 +02:00
*
* @ return string The sanitized URL or an empty string if invalid .
*/
2024-06-10 11:05:00 +02:00
function fictioneer_sanitize_url ( $url , $match = null , $preg_match = null ) {
2024-06-10 10:22:25 +02:00
$url = sanitize_url ( $url );
2024-06-10 11:05:00 +02:00
$url = filter_var ( $url , FILTER_VALIDATE_URL ) ? $url : '' ;
2024-06-10 10:22:25 +02:00
2024-06-10 11:05:00 +02:00
if ( $match && is_string ( $match ) ) {
$url = strpos ( $url , $match ) === 0 ? $url : '' ;
}
if ( $preg_match && is_string ( $preg_match ) ) {
$url = preg_match ( $preg_match , $url ) ? $url : '' ;
}
return $url ;
2024-06-10 10:22:25 +02:00
}
2024-04-14 14:25:18 +02:00
// =============================================================================
2024-04-14 15:13:18 +02:00
// ASPECT RATIO CSS
2024-04-14 14:25:18 +02:00
// =============================================================================
/**
* Sanitizes a CSS aspect ratio value
*
* @ since 5.14 . 0
2024-08-21 22:53:24 +02:00
* @ since 5.23 . 0 - Refactored to accept fractional values .
2024-04-14 14:25:18 +02:00
*
* @ param string $css The CSS value to be sanitized .
* @ param string $default Optional default value .
*
* @ return string | bool The sanitized value or default if invalid .
*/
function sanitize_css_aspect_ratio ( $css , $default = false ) {
// Remove unwanted white spaces
$css = trim ( $css );
// Validate
2024-08-21 22:53:24 +02:00
if ( preg_match ( '/^\d+(\.\d+)?\/\d+(\.\d+)?$/' , $css ) ) {
2024-04-14 14:25:18 +02:00
// Split based on the slash '/'
list ( $numerator , $denominator ) = explode ( '/' , $css , 2 );
// Sanitize parts
2024-08-21 22:53:24 +02:00
$numerator = max ( 0 , floatval ( $numerator ) );
$denominator = max ( 0 , floatval ( $denominator ) );
// Nonsense?
if ( $numerator == 0 || $denominator == 0 ) {
return $default ;
}
2024-04-14 14:25:18 +02:00
// Combine and return
return $numerator . '/' . $denominator ;
}
// Default if invalid
return $default ;
}
2024-04-14 15:13:18 +02:00
/**
* Returns aspect ratio values as tuple
*
* @ since 5.14 . 0
*
* @ param string $css The aspect - ratio CSS value .
*
* @ return array Tuple of aspect - ratio values .
*/
function fictioneer_get_split_aspect_ratio ( $css ) {
// Split based on the slash '/'
list ( $numerator , $denominator ) = explode ( '/' , $css , 2 );
// Return tuple
return array ( ( int ) ( $numerator ? ? 1 ), ( int ) ( $denominator ? ? 1 ) );
}
2024-05-16 14:26:59 +02:00
// =============================================================================
// SHOW LOGIN
// =============================================================================
/**
* Checks whether the login should be rendered
*
* @ since 5.18 . 1
*
* @ return boolean True or false .
*/
function fictioneer_show_login () {
2024-05-21 09:58:56 +02:00
$enabled = get_option ( 'fictioneer_enable_oauth' ) || get_option ( 'fictioneer_show_wp_login_link' );
return ( $enabled && ! is_user_logged_in () ) || get_option ( 'fictioneer_enable_public_cache_compatibility' );
2024-05-16 14:26:59 +02:00
}
2023-01-21 01:31:34 +01:00
// =============================================================================
// SHOW NON-PUBLIC CONTENT
// =============================================================================
/**
* Wrapper for is_user_logged_in () with global public cache consideration
*
* If public caches are served to all users , including logged - in users , it is
* necessary to render items that would normally be skipped for logged - out
* users , such as the profile link . They are hidden via CSS , which works as
* long as the 'logged-in' class is still set on the < body >.
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
* @ return boolean True or false .
*/
function fictioneer_show_auth_content () {
2024-01-16 08:00:17 +01:00
return is_user_logged_in () ||
get_option ( 'fictioneer_enable_public_cache_compatibility' ) ||
get_option ( 'fictioneer_enable_ajax_authentication' );
2023-01-21 01:31:34 +01:00
}
// =============================================================================
// FICTIONEER TRANSLATIONS
// =============================================================================
/**
* Returns selected translations
*
* Adding theme options for all possible translations would be a pain ,
* so this is done with a function that can be filtered as needed . For
* giving your site a personal touch .
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $key Key for requested translation .
* @ param boolean $escape Optional . Escape the string for safe use in
* attributes . Default false .
2023-01-21 01:31:34 +01:00
*
* @ return string The translation or an empty string if not found .
*/
function fcntr ( $key , $escape = false ) {
2024-04-08 12:32:09 +02:00
static $strings = null ;
2023-01-21 01:31:34 +01:00
// Define default translations
2024-04-08 12:32:09 +02:00
if ( $strings === null ) {
$strings = array (
'account' => __ ( 'Account' , 'fictioneer' ),
'bookshelf' => __ ( 'Bookshelf' , 'fictioneer' ),
'admin' => _x ( 'Admin' , 'Caption for administrator badge label.' , 'fictioneer' ),
'anonymous_guest' => __ ( 'Anonymous Guest' , 'fictioneer' ),
'author' => _x ( 'Author' , 'Caption for author badge label.' , 'fictioneer' ),
'bbcodes_modal' => _x ( 'BBCodes' , 'Heading for BBCodes tutorial modal.' , 'fictioneer' ),
'blog' => _x ( 'Blog' , 'Blog page name, mainly used in breadcrumbs.' , 'fictioneer' ),
'bookmark' => __ ( 'Bookmark' , 'fictioneer' ),
'bookmarks' => __ ( 'Bookmarks' , 'fictioneer' ),
'warning_notes' => __ ( 'Warning Notes' , 'fictioneer' ),
'deleted_user' => __ ( 'Deleted User' , 'fictioneer' ),
'follow' => _x ( 'Follow' , 'Follow a story.' , 'fictioneer' ),
'follows' => _x ( 'Follows' , 'List of followed stories.' , 'fictioneer' ),
'forget' => _x ( 'Forget' , 'Forget story set to be read later.' , 'fictioneer' ),
'formatting' => _x ( 'Formatting' , 'Toggle for chapter formatting modal.' , 'fictioneer' ),
'formatting_modal' => _x ( 'Formatting' , 'Chapter formatting modal heading.' , 'fictioneer' ),
'frontpage' => _x ( 'Home' , 'Frontpage page name, mainly used in breadcrumbs.' , 'fictioneer' ),
'is_followed' => _x ( 'Followed' , 'Story is followed.' , 'fictioneer' ),
'is_read' => _x ( 'Read' , 'Story or chapter is marked as read.' , 'fictioneer' ),
'is_read_later' => _x ( 'Read later' , 'Story is marked to be read later.' , 'fictioneer' ),
'jump_to_comments' => __ ( 'Jump: Comments' , 'fictioneer' ),
'jump_to_bookmark' => __ ( 'Jump: Bookmark' , 'fictioneer' ),
'login' => __ ( 'Login' , 'fictioneer' ),
'login_modal' => _x ( 'Login' , 'Login modal heading.' , 'fictioneer' ),
'login_with' => _x ( 'Log in with' , 'OAuth 2.0 login option plus appended icon.' , 'fictioneer' ),
'logout' => __ ( 'Logout' , 'fictioneer' ),
'mark_read' => _x ( 'Mark Read' , 'Mark story as read.' , 'fictioneer' ),
'mark_unread' => _x ( 'Mark Unread' , 'Mark story as unread.' , 'fictioneer' ),
'moderator' => _x ( 'Mod' , 'Caption for moderator badge label' , 'fictioneer' ),
'next' => __ ( '<span class="on">Next</span><span class="off"><i class="fa-solid fa-caret-right"></i></span>' , 'fictioneer' ),
'no_bookmarks' => __ ( 'No bookmarks.' , 'fictioneer' ),
'password' => __ ( 'Password' , 'fictioneer' ),
'previous' => __ ( '<span class="off"><i class="fa-solid fa-caret-left"></i></span><span class="on">Previous</span>' , 'fictioneer' ),
'read_later' => _x ( 'Read Later' , 'Remember a story to be read later.' , 'fictioneer' ),
'read_more' => _x ( 'Read More' , 'Read more of a post.' , 'fictioneer' ),
'reminders' => _x ( 'Reminders' , 'List of stories to read later.' , 'fictioneer' ),
'site_settings' => __ ( 'Site Settings' , 'fictioneer' ),
'story_blog' => _x ( 'Blog' , 'Blog tab of the story.' , 'fictioneer' ),
'subscribe' => _x ( 'Subscribe' , 'Subscribe to a story.' , 'fictioneer' ),
'unassigned_group' => _x ( 'Unassigned' , 'Chapters not assigned to group.' , 'fictioneer' ),
'unfollow' => _x ( 'Unfollow' , 'Stop following a story.' , 'fictioneer' ),
'E' => _x ( 'E' , 'Age rating E for Everyone.' , 'fictioneer' ),
'T' => _x ( 'T' , 'Age rating T for Teen.' , 'fictioneer' ),
'M' => _x ( 'M' , 'Age rating M for Mature.' , 'fictioneer' ),
'A' => _x ( 'A' , 'Age rating A for Adult.' , 'fictioneer' ),
'Everyone' => _x ( 'Everyone' , 'Age rating Everyone.' , 'fictioneer' ),
'Teen' => _x ( 'Teen' , 'Age rating Teen.' , 'fictioneer' ),
'Mature' => _x ( 'Mature' , 'Age rating Mature.' , 'fictioneer' ),
'Adult' => _x ( 'Adult' , 'Age rating Adult.' , 'fictioneer' ),
'Completed' => _x ( 'Completed' , 'Completed story status.' , 'fictioneer' ),
'Ongoing' => _x ( 'Ongoing' , 'Ongoing story status' , 'fictioneer' ),
'Hiatus' => _x ( 'Hiatus' , 'Hiatus story status' , 'fictioneer' ),
'Oneshot' => _x ( 'Oneshot' , 'Oneshot story status' , 'fictioneer' ),
'Canceled' => _x ( 'Canceled' , 'Canceled story status' , 'fictioneer' ),
'comment_anchor' => _x ( '<i class="fa-solid fa-link"></i>' , 'Text or icon for paragraph anchor in comments.' , 'fictioneer' ),
'bbcode_b' => _x ( '<code>[b]</code><strong>Bold</strong><code>[/b]</code> of you to assume I have a plan.' , 'fictioneer' ),
'bbcode_i' => _x ( 'Deathbringer, emphasis on <code>[i]</code><em>death</em><code>[/i]</code>.' , 'fictioneer' ),
'bbcode_s' => _x ( 'I’ m totally <code>[s]</code><strike>crossed out</strike><code>[/s]</code> by this.' , 'fictioneer' ),
'bbcode_li' => _x ( '<ul><li class="comment-list-item">Listless I’ m counting my <code>[li]</code>bullets<code>[/li]</code>.</li></ul>' , 'fictioneer' ),
'bbcode_img' => _x ( '<code>[img]</code>https://www.agine.this<code>[/img]</code> %s' , 'BBCode example.' , 'fictioneer' ),
'bbcode_link' => _x ( '<code>[link]</code><a href="http://topwebfiction.com/" target="_blank" class="link">http://topwebfiction.com</a><code>[/link]</code>.' , 'BBCode example.' , 'fictioneer' ),
'bbcode_link_name' => _x ( '<code>[link=https://www.n.ot]</code><a href="http://topwebfiction.com/" class="link">clickbait</a><code>[/link]</code>.' , 'BBCode example.' , 'fictioneer' ),
'bbcode_quote' => _x ( '<blockquote><code>[quote]</code>… me like my landlord!<code>[/quote]</code></blockquote>' , 'BBCode example.' , 'fictioneer' ),
'bbcode_spoiler' => _x ( '<code>[spoiler]</code><span class="spoiler">Spanish Inquisition!</span><code>[/spoiler]</code>' , 'BBCode example.' , 'fictioneer' ),
'bbcode_ins' => _x ( '<code>[ins]</code><ins>Insert</ins><code>[/ins]</code> more bad puns!' , 'BBCode example.' , 'fictioneer' ),
'bbcode_del' => _x ( '<code>[del]</code><del>Delete</del><code>[/del]</code> your browser history!' , 'BBCode example.' , 'fictioneer' ),
'log_in_with' => _x ( 'Enter your details or log in with:' , 'Comment form login note.' , 'fictioneer' ),
2024-12-06 17:38:32 +01:00
'logged_in_as' => _x ( '<span>Logged in as <strong><a href="%1$s">%2$s</a></strong>. <a class="logout-link" href="%3$s" data-action="click->fictioneer#logout">Log out?</a></span>' , 'Comment form logged-in note.' , 'fictioneer' ),
2024-04-08 12:32:09 +02:00
'accept_privacy_policy' => _x ( 'I accept the <b><a class="link" href="%s" target="_blank">privacy policy</a></b>.' , 'Comment form privacy checkbox.' , 'fictioneer' ),
'save_in_cookie' => _x ( 'Save in cookie for next time.' , 'Comment form cookie checkbox.' , 'fictioneer' ),
'future_prefix' => _x ( 'Scheduled:' , 'Chapter list status prefix.' , 'fictioneer' ),
'trashed_prefix' => _x ( 'Trashed:' , 'Chapter list status prefix.' , 'fictioneer' ),
'private_prefix' => _x ( 'Private:' , 'Chapter list status prefix.' , 'fictioneer' ),
2024-09-10 12:35:22 +02:00
'free_patreon_tier' => _x ( 'Follower (Free)' , 'Free Patreon tier (follower).' , 'fictioneer' ),
'private_comment' => _x ( 'Private Comment' , 'Comment type translation.' , 'fictioneer' ),
'comment_comment' => _x ( 'Public Comment' , 'Comment type translation.' , 'fictioneer' ),
'approved_comment_status' => _x ( 'Approved' , 'Comment status translation.' , 'fictioneer' ),
'hold_comment_status' => _x ( 'Hold' , 'Comment status translation.' , 'fictioneer' ),
'unapproved_comment_status' => _x ( 'Unapproved' , 'Comment status translation.' , 'fictioneer' ),
'spam_comment_status' => _x ( 'Spam' , 'Comment status translation.' , 'fictioneer' ),
'trash_comment_status' => _x ( 'Trash' , 'Comment status translation.' , 'fictioneer' ),
2024-04-08 12:32:09 +02:00
);
2024-05-01 13:08:25 +02:00
// Filter static translations
$strings = apply_filters ( 'fictioneer_filter_translations_static' , $strings );
2024-04-08 12:32:09 +02:00
}
2023-01-21 01:31:34 +01:00
// Filter translations
$strings = apply_filters ( 'fictioneer_filter_translations' , $strings );
// Return requested translation if defined...
if ( array_key_exists ( $key , $strings ) ) {
return $escape ? esc_attr ( $strings [ $key ] ) : $strings [ $key ];
}
// ... otherwise return empty string
return '' ;
}
// =============================================================================
// BALANCE PAGINATION ARRAY
// =============================================================================
/**
* Balances pagination array
*
* Takes an number array of pagination pages and balances the items around the
* current page number , replacing anything above the keep threshold with ellipses .
* E . g . 1 … 7 , 8 , [ 9 ], 10 , 11 … 20.
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param array | int $pages Array of pages to balance . If an integer is provided ,
* it is converted to a number array .
* @ param int $current Current page number .
* @ param int $keep Optional . Balancing factor to each side . Default 2.
* @ param string $ellipses Optional . String for skipped numbers . Default '…' .
2023-01-21 01:31:34 +01:00
*
* @ return array The balanced array .
*/
function fictioneer_balance_pagination_array ( $pages , $current , $keep = 2 , $ellipses = '…' ) {
// Setup
$max_pages = is_array ( $pages ) ? count ( $pages ) : $pages ;
$steps = is_array ( $pages ) ? $pages : [];
if ( ! is_array ( $pages ) ) {
for ( $i = 1 ; $i <= $max_pages ; $i ++ ) {
$steps [] = $i ;
}
}
// You know, I wrote this but don't really get it myself...
if ( $max_pages - $keep * 2 > $current ) {
$start = $current + $keep ;
$end = $max_pages - $keep + 1 ;
for ( $i = $start ; $i < $end ; $i ++ ) {
unset ( $steps [ $i ] );
}
array_splice ( $steps , count ( $steps ) - $keep + 1 , 0 , $ellipses );
}
// It certainly does math...
if ( $current - $keep * 2 >= $keep ) {
$start = $keep - 1 ;
$end = $current - $keep - 1 ;
for ( $i = $start ; $i < $end ; $i ++ ) {
unset ( $steps [ $i ] );
}
array_splice ( $steps , $keep - 1 , 0 , $ellipses );
}
return $steps ;
}
// =============================================================================
// CHECK WHETHER COMMENTING IS DISABLED
// =============================================================================
if ( ! function_exists ( 'fictioneer_is_commenting_disabled' ) ) {
/**
* Check whether commenting is disabled
*
* Differs from comments_open () in the regard that it does not hide the whole
* comment section but does not allow new comments to be posted .
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param int | null $post_id Post ID the comments are for . Defaults to current post ID .
2023-01-21 01:31:34 +01:00
*
* @ return boolean True or false .
*/
function fictioneer_is_commenting_disabled ( $post_id = null ) {
2024-01-09 18:44:37 +01:00
// Setup
$post_id = $post_id ? ? get_the_ID ();
2023-09-20 01:01:43 +02:00
// Return immediately if...
if (
get_option ( 'fictioneer_disable_commenting' ) ||
2023-11-30 16:03:53 +01:00
get_post_meta ( $post_id , 'fictioneer_disable_commenting' , true )
2023-09-20 01:01:43 +02:00
) {
return true ;
}
// Check parent story if chapter...
if ( get_post_type ( $post_id ) === 'fcn_chapter' ) {
2024-10-26 02:03:23 +02:00
$story_id = fictioneer_get_chapter_story_id ( $post_id );
2023-09-20 01:01:43 +02:00
if ( $story_id ) {
2023-11-30 16:03:53 +01:00
return get_post_meta ( $story_id , 'fictioneer_disable_commenting' , true ) == true ;
2023-09-20 01:01:43 +02:00
}
}
return false ;
2023-01-21 01:31:34 +01:00
}
}
// =============================================================================
// CHECK DISALLOWED KEYS WITH OFFENSES RETURNED
// =============================================================================
if ( ! function_exists ( 'fictioneer_check_comment_disallowed_list' ) ) {
/**
* Checks whether a comment contains disallowed characters or words and
* returns the offenders within the comment content
*
2024-01-26 17:45:59 +01:00
* @ since 5.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $author The author of the comment .
* @ param string $email The email of the comment .
* @ param string $url The url used in the comment .
* @ param string $comment The comment content
* @ param string $user_ip The comment author ' s IP address .
* @ param string $user_agent The author ' s browser user agent .
2023-01-21 01:31:34 +01:00
*
* @ return array Tuple of true / false [ 0 ] and offenders [ 1 ] as array .
*/
function fictioneer_check_comment_disallowed_list ( $author , $email , $url , $comment , $user_ip , $user_agent ) {
// Implementation is the same as wp_check_comment_disallowed_list(...)
$mod_keys = trim ( get_option ( 'disallowed_keys' ) );
if ( '' === $mod_keys ) {
2023-01-26 01:58:36 +01:00
return [ false , []]; // If moderation keys are empty.
2023-01-21 01:31:34 +01:00
}
// Ensure HTML tags are not being used to bypass the list of disallowed characters and words.
$comment_without_html = wp_strip_all_tags ( $comment );
$words = explode ( " \n " , $mod_keys );
foreach ( ( array ) $words as $word ) {
$word = trim ( $word );
// Skip empty lines.
if ( empty ( $word ) ) {
2024-02-06 00:32:15 +01:00
continue ;
}
2023-01-21 01:31:34 +01:00
// Do some escaping magic so that '#' chars in the spam words don't break things:
$word = preg_quote ( $word , '#' );
$matches = false ;
$pattern = " # $word #i " ;
if ( preg_match ( $pattern , $author )
|| preg_match ( $pattern , $email )
|| preg_match ( $pattern , $url )
|| preg_match ( $pattern , $comment , $matches )
|| preg_match ( $pattern , $comment_without_html , $matches )
|| preg_match ( $pattern , $user_ip )
|| preg_match ( $pattern , $user_agent )
) {
return [ true , $matches ];
}
}
return [ false , []];
}
}
// =============================================================================
// BBCODES
// =============================================================================
if ( ! function_exists ( 'fictioneer_bbcodes' ) ) {
/**
* Interprets BBCodes into HTML
*
* Note : Spoilers do not work properly if wrapping multiple lines or other codes .
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-08-25 20:08:05 +02:00
* @ link https :// stackoverflow . com / a / 17508056 / 17140970
2023-01-21 01:31:34 +01:00
*
2023-08-25 20:08:05 +02:00
* @ param string $content The content .
2023-08-08 20:33:24 +02:00
*
* @ return string The content with interpreted BBCodes .
2023-01-21 01:31:34 +01:00
*/
function fictioneer_bbcodes ( $content ) {
// Setup
2024-09-20 14:02:36 +02:00
$img_search = '\s*https:[^\"\'|;<>\[\]]+?\.(?:png|jpg|jpeg|gif|webp|svg|avif|tiff).*?\s*' ;
$url_search = '\s*(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?\s*' ;
2023-01-21 01:31:34 +01:00
// Deal with some multi-line spoiler issues
2023-06-29 22:15:03 +02:00
if ( preg_match_all ( '/\[spoiler](.+?)\[\/spoiler]/is' , $content , $spoilers , PREG_PATTERN_ORDER ) ) {
2023-01-21 01:31:34 +01:00
foreach ( $spoilers [ 0 ] as $spoiler ) {
$replace = str_replace ( '<p></p>' , ' ' , $spoiler );
2023-06-29 22:15:03 +02:00
$replace = preg_replace ( '/\[quote](.+?)\[\/quote]/is' , '<blockquote class="spoiler">$1</blockquote>' , $replace );
2023-01-21 01:31:34 +01:00
$content = str_replace ( $spoiler , $replace , $content );
}
}
// Possible patterns
$patterns = array (
2023-06-29 22:15:03 +02:00
'/\[spoiler]\[quote](.+?)\[\/quote]\[\/spoiler]/is' ,
2023-01-21 01:31:34 +01:00
'/\[spoiler](.+?)\[\/spoiler]/i' ,
2023-06-29 22:15:03 +02:00
'/\[spoiler](.+?)\[\/spoiler]/is' ,
2023-01-21 01:31:34 +01:00
'/\[b](.+?)\[\/b]/i' ,
'/\[i](.+?)\[\/i]/i' ,
'/\[s](.+?)\[\/s]/i' ,
2023-06-29 22:15:03 +02:00
'/\[quote](.+?)\[\/quote]/is' ,
'/\[ins](.+?)\[\/ins]/is' ,
'/\[del](.+?)\[\/del]/is' ,
2023-01-21 01:31:34 +01:00
'/\[li](.+?)\[\/li]/i' ,
2024-09-20 14:02:36 +02:00
" / \ [link.*] \ [img] \ s*( $img_search ) \ s* \ [ \ /img] \ [ \ /link]/i " ,
" / \ [img] \ s*( $img_search ) \ s* \ [ \ /img]/i " ,
" / \ [link \ ] \ s*( $url_search ) \ s* \ [ \ /link \ ]/i " ,
" / \ [link= \ s*[ \" ']? \ s*( $url_search ) \ s*[ \" ']? \ s* \ ](.+?) \ [ \ /link \ ]/i " ,
'/\[anchor]\s*([^\"\'|;<>\[\]]+?)\s*\[\/anchor]/i'
2023-01-21 01:31:34 +01:00
);
// HTML replacements
$replacements = array (
'<blockquote class="spoiler">$1</blockquote>' ,
'<span class="spoiler">$1</span>' ,
2023-06-29 22:15:03 +02:00
'<div class="spoiler">$1</div>' ,
2023-01-21 01:31:34 +01:00
'<strong>$1</strong>' ,
'<em>$1</em>' ,
'<strike>$1</strike>' ,
'<blockquote>$1</blockquote>' ,
'<ins>$1</ins>' ,
'<del>$1</del>' ,
'<div class="comment-list-item">$1</div>' ,
2023-08-19 20:13:11 +02:00
'<span class="comment-image-consent-wrapper"><button type="button" class="button _secondary consent-button" title="$1">' . _x ( '<i class="fa-solid fa-image"></i> Show Image' , 'Comment image consent wrapper button.' , 'fictioneer' ) . '</button><a href="$1" class="comment-image-link" rel="noreferrer noopener nofollow" target="_blank"><img class="comment-image" data-src="$1"></a></span>' ,
'<span class="comment-image-consent-wrapper"><button type="button" class="button _secondary consent-button" title="$1">' . _x ( '<i class="fa-solid fa-image"></i> Show Image' , 'Comment image consent wrapper button.' , 'fictioneer' ) . '</button><img class="comment-image" data-src="$1"></span>' ,
2023-01-21 01:31:34 +01:00
" <a href= \" $ 1 \" rel= \" noreferrer noopener nofollow \" > $ 1</a> " ,
" <a href= \" $ 1 \" rel= \" noreferrer noopener nofollow \" > $ 5</a> " ,
2024-09-20 14:04:37 +02:00
'<a href="#$1" rel="noreferrer noopener nofollow" data-block="start" class="comment-anchor">:anchor:</a>'
2023-01-21 01:31:34 +01:00
);
// Pattern replace
$content = preg_replace ( $patterns , $replacements , $content );
// Icons
$content = str_replace ( ':anchor:' , fcntr ( 'comment_anchor' ), $content );
return $content ;
}
}
2023-02-15 01:39:31 +01:00
// =============================================================================
// GET TAXONOMY NAMES
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_taxonomy_names' ) ) {
/**
* Get all taxonomies of a post
*
2023-02-15 01:45:14 +01:00
* @ since 5.0 . 20
2023-02-15 01:39:31 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param int $post_id ID of the post to get the taxonomies of .
* @ param boolean $flatten Whether to flatten the result . Default false .
2023-02-15 01:39:31 +01:00
*
* @ return array Array with all taxonomies .
*/
function fictioneer_get_taxonomy_names ( $post_id , $flatten = false ) {
// Setup
$t = [];
$t [ 'tags' ] = get_the_tags ( $post_id );
$t [ 'fandoms' ] = get_the_terms ( $post_id , 'fcn_fandom' );
$t [ 'characters' ] = get_the_terms ( $post_id , 'fcn_character' );
$t [ 'warnings' ] = get_the_terms ( $post_id , 'fcn_content_warning' );
$t [ 'genres' ] = get_the_terms ( $post_id , 'fcn_genre' );
// Validate
2023-02-15 01:44:58 +01:00
foreach ( $t as $key => $tax ) {
$t [ $key ] = is_array ( $t [ $key ] ) ? $t [ $key ] : [];
2023-02-15 01:39:31 +01:00
}
// Extract
2023-02-15 01:44:58 +01:00
foreach ( $t as $key => $tax ) {
$t [ $key ] = array_map ( function ( $a ) { return $a -> name ; }, $t [ $key ] );
2023-02-15 01:39:31 +01:00
}
// Return flattened
if ( $flatten ) {
return array_merge ( $t [ 'tags' ], $t [ 'fandoms' ], $t [ 'characters' ], $t [ 'warnings' ], $t [ 'genres' ] );
}
// Return without empty arrays
return array_filter ( $t , 'count' );
}
}
2023-02-23 17:03:41 +01:00
// =============================================================================
// GET FONTS
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_fonts' ) ) {
/**
* Returns array of font items
*
2024-04-10 12:05:41 +02:00
* Note : The css string can contain quotes in case of multiple words ,
* such as " Roboto Mono " .
*
2023-02-23 17:03:41 +01:00
* @ since 5.1 . 1
2024-02-07 16:40:14 +01:00
* @ since 5.10 . 0 - Refactor for font manager .
2024-04-10 12:05:41 +02:00
* @ since 5.12 . 5 - Add theme mod for chapter body font .
2023-02-23 17:03:41 +01:00
*
* @ return array Font items ( css , name , and alt ) .
*/
function fictioneer_get_fonts () {
2024-02-07 16:40:14 +01:00
// Make sure fonts are set up!
if (
! get_option ( 'fictioneer_chapter_fonts' ) ||
! is_array ( get_option ( 'fictioneer_chapter_fonts' ) )
) {
fictioneer_build_bundled_fonts ();
2023-08-25 21:56:36 +02:00
}
2024-02-07 16:40:14 +01:00
// Setup
$custom_fonts = get_option ( 'fictioneer_chapter_fonts' );
2024-04-10 12:05:41 +02:00
$primary_chapter_font = get_theme_mod ( 'chapter_chapter_body_font_family_value' , 'default' );
2024-02-07 16:40:14 +01:00
$fonts = array (
2024-02-07 22:14:08 +01:00
array ( 'css' => fictioneer_font_family_value ( FICTIONEER_PRIMARY_FONT_CSS ), 'name' => FICTIONEER_PRIMARY_FONT_NAME ),
2024-02-07 16:40:14 +01:00
array ( 'css' => '' , 'name' => _x ( 'System Font' , 'Font name.' , 'fictioneer' ) )
2023-02-23 17:03:41 +01:00
);
2024-02-07 16:40:14 +01:00
// Build final font array
foreach ( $custom_fonts as $custom_font ) {
if (
! in_array ( $custom_font , $fonts ) &&
$custom_font [ 'name' ] !== FICTIONEER_PRIMARY_FONT_NAME &&
$custom_font [ 'css' ] !== FICTIONEER_PRIMARY_FONT_CSS
) {
2024-04-10 12:05:41 +02:00
if (
$primary_chapter_font !== 'default' &&
2024-04-13 04:20:22 +02:00
strpos ( $custom_font [ 'css' ], $primary_chapter_font ) !== false
2024-04-10 12:05:41 +02:00
) {
array_unshift ( $fonts , $custom_font );
} else {
$fonts [] = $custom_font ;
}
2024-02-07 16:40:14 +01:00
}
}
2023-02-23 17:03:41 +01:00
// Apply filters and return
return apply_filters ( 'fictioneer_filter_fonts' , $fonts );
}
}
2024-02-07 22:14:08 +01:00
// =============================================================================
// WRAP MULTI-WORD FONTS INTO QUOTES
// =============================================================================
/**
* Returns font family value with quotes if required
*
* @ since 5.10 . 0
*
* @ param string $font_value The font family value .
* @ param string $quote Optional . The wrapping character . Default '"' .
*
* @ return string Ready to use font family value .
*/
function fictioneer_font_family_value ( $font_value , $quote = '"' ) {
if ( preg_match ( '/\s/' , $font_value ) ) {
return $quote . $font_value . $quote ;
} else {
return $font_value ;
}
}
2023-02-23 18:41:14 +01:00
// =============================================================================
// GET FONT COLORS
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_font_colors' ) ) {
/**
* Returns array of font color items
*
* @ since 5.1 . 1
*
* @ return array Font items ( css and name ) .
*/
function fictioneer_get_font_colors () {
// Setup default font colors
$colors = array (
2024-04-13 14:51:08 +02:00
array ( 'css' => 'var(--fg-tinted)' , 'name' => _x ( 'Tinted' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-500)' , 'name' => _x ( 'Baseline' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-600)' , 'name' => _x ( 'Low' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-700)' , 'name' => _x ( 'Lower' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-800)' , 'name' => _x ( 'Lowest' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-400)' , 'name' => _x ( 'High' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-300)' , 'name' => _x ( 'Higher' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => 'var(--fg-200)' , 'name' => _x ( 'Highest' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => '#fff' , 'name' => _x ( 'White' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => '#999' , 'name' => _x ( 'Gray' , 'Chapter font color name.' , 'fictioneer' ) ),
array ( 'css' => '#000' , 'name' => _x ( 'Black' , 'Chapter font color name.' , 'fictioneer' ) )
2023-02-23 18:41:14 +01:00
);
// Apply filters and return
return apply_filters ( 'fictioneer_filter_font_colors' , $colors );
}
}
2023-03-08 00:27:21 +01:00
// =============================================================================
// ARRAY FROM COMMA SEPARATED STRING
// =============================================================================
/**
* Explodes string into an array
*
2023-09-16 02:04:22 +02:00
* Strips lines breaks , trims whitespaces , and removes empty elements .
2023-08-13 13:22:22 +02:00
* Values might not be unique .
*
2023-03-08 00:27:21 +01:00
* @ since 5.1 . 3
*
2023-08-08 20:33:24 +02:00
* @ param string $string The string to explode .
2023-03-08 00:27:21 +01:00
*
* @ return array The string content as array .
*/
function fictioneer_explode_list ( $string ) {
2023-08-13 13:22:22 +02:00
if ( empty ( $string ) ) {
return [];
}
2023-03-10 14:40:30 +01:00
2023-08-13 13:22:22 +02:00
$string = str_replace ( [ " \n " , " \r " ], '' , $string ); // Remove line breaks
2023-03-08 00:27:21 +01:00
$array = explode ( ',' , $string );
$array = array_map ( 'trim' , $array ); // Remove extra whitespaces
$array = array_filter ( $array , 'strlen' ); // Remove empty elements
$array = is_array ( $array ) ? $array : [];
return $array ;
}
2023-05-31 15:43:42 +02:00
// =============================================================================
2024-05-17 13:52:08 +02:00
// BUILD FRONTEND NOTICE
2023-05-31 15:43:42 +02:00
// =============================================================================
if ( ! function_exists ( 'fictioneer_notice' ) ) {
/**
2023-08-08 20:33:24 +02:00
* Render or return a frontend notice element
2023-05-31 15:43:42 +02:00
*
* @ since 5.2 . 5
*
2023-08-08 20:33:24 +02:00
* @ param string $message The notice to show .
* @ param string $type Optional . The notice type . Default 'warning' .
* @ param bool $display Optional . Whether to render or return . Default true .
2023-05-31 15:43:42 +02:00
*
* @ return void | string The build HTML or nothing if rendered .
*/
function fictioneer_notice ( $message , $type = 'warning' , $display = true ) {
2024-02-04 18:03:40 +01:00
$output = '<div class="notice _' . esc_attr ( $type ) . '">' ;
if ( $type === 'warning' ) {
$output .= '<i class="fa-solid fa-triangle-exclamation"></i>' ;
}
$output .= " <div> { $message } </div></div> " ;
2023-05-31 15:43:42 +02:00
if ( $display ) {
echo $output ;
} else {
return $output ;
}
}
}
2023-06-10 18:23:04 +02:00
// =============================================================================
// MINIFY HTML
// =============================================================================
if ( ! function_exists ( 'fictioneer_minify_html' ) ) {
/**
* Minifies a HTML string
*
* This is not safe for `<pre>` or `<code>` tags !
*
* @ since 5.4 . 0
*
2023-08-08 20:33:24 +02:00
* @ param string $html The HTML string to be minified .
2023-06-10 18:23:04 +02:00
*
* @ return string The minified HTML string .
*/
function fictioneer_minify_html ( $html ) {
return preg_replace ( '/\s+/' , ' ' , trim ( $html ) );
}
}
2024-10-07 10:59:58 +02:00
// =============================================================================
// MINIFY CSS
// =============================================================================
if ( ! function_exists ( 'fictioneer_minify_css' ) ) {
/**
* Minify CSS .
*
* @ license CC BY - SA 4.0
* @ author Qtax https :// stackoverflow . com / users / 107152 / qtax
* @ author lots0logs https :// stackoverflow . com / users / 2639936 / lots0logs
*
* @ since 4.7 . 0
* @ link https :// stackoverflow . com / a / 15195752 / 17140970
* @ link https :// stackoverflow . com / a / 44350195 / 17140970
*
* @ param string $string The to be minified CSS string .
*
* @ return string The minified CSS string .
*/
function fictioneer_minify_css ( $string = '' ) {
$comments = <<< 'EOS'
( ? sx )
# don't change anything inside of quotes
( " (?:[^ " \\ ] ++| \\ . ) *+ " | '(?:[^' \\ ]++| \\ .)*+' )
|
# comments
/ \ * ( ?> .*? \*/ )
EOS ;
$everything_else = <<< 'EOS'
( ? six )
# don't change anything inside of quotes
( " (?:[^ " \\ ] ++| \\ . ) *+ " | '(?:[^' \\ ]++| \\ .)*+' )
|
# spaces before and after ; and }
\s *+ ; \s *+ ( } ) \s *+
|
# all spaces around meta chars/operators (excluding + and -)
\s *+ ( [ * $ ~^| ] ? += | [{};, >~ ] | ! important\b ) \s *+
|
# all spaces around + and - (in selectors only!)
\s * ([ +- ]) \s * ( ? = [ ^ }] * {)
|
# spaces right of ( [ :
( [[( : ] ) \s ++
|
# spaces left of ) ]
\s ++ ( [])] )
|
# spaces left (and right) of : (but not in selectors)!
\s + ( : )( ? ! [ ^ \ }] * \ {)
|
# spaces at beginning/end of string
^ \s ++ | \s ++ \z
|
# double spaces to single
( \s ) \s +
EOS ;
$search_patterns = array ( " % { $comments } % " , " % { $everything_else } % " );
$replace_patterns = array ( '$1' , '$1$2$3$4$5$6$7$8' );
return preg_replace ( $search_patterns , $replace_patterns , $string );
}
}
2023-06-11 16:43:26 +02:00
// =============================================================================
// GET CLEAN CURRENT URL
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_clean_url' ) ) {
/**
* Returns URL without query arguments or page number
*
* @ since 5.4 . 0
*
* @ return string The clean URL .
*/
function fictioneer_get_clean_url () {
global $wp ;
// Setup
$url = home_url ( $wp -> request );
// Remove page (if any)
$url = preg_replace ( '/\/page\/\d+\/$/' , '' , $url );
$url = preg_replace ( '/\/page\/\d+$/' , '' , $url );
// Return cleaned URL
return $url ;
}
}
2023-07-28 12:05:22 +02:00
// =============================================================================
// GET AUTHOR IDS OF POST
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_post_author_ids' ) ) {
/**
* Returns array of author IDs for a post ID
*
* @ since 5.4 . 8
*
* @ param int $post_id The post ID .
*
* @ return array The author IDs .
*/
function fictioneer_get_post_author_ids ( $post_id ) {
2023-11-30 16:03:53 +01:00
$author_ids = get_post_meta ( $post_id , 'fictioneer_story_co_authors' , true ) ? : [];
2024-01-27 21:00:00 +01:00
$author_ids = is_array ( $author_ids ) ? $author_ids : [];
array_unshift ( $author_ids , get_post_field ( 'post_author' , $post_id ) );
2023-07-28 12:05:22 +02:00
return array_unique ( $author_ids );
}
}
2023-08-03 13:17:32 +02:00
// =============================================================================
// DELETE TRANSIENTS THAT INCLUDE A STRING
// =============================================================================
if ( ! function_exists ( 'fictioneer_delete_transients_like' ) ) {
/**
* Delete Transients with a like key and return the count deleted
*
2024-10-12 20:18:25 +02:00
* Note : The fast variant of this function is not compatible with
* external object caches such as Memcached , because without the
* help of delete_transient (), it won ' t know about the change .
*
2023-08-03 13:17:32 +02:00
* @ since 5.4 . 9
*
* @ param string $partial_key String that is part of the key .
* @ param boolean $fast Optional . Whether to delete with a single SQL query or
* loop each Transient with delete_transient (), which can
2024-10-12 20:18:25 +02:00
* trigger hooked actions ( if any ) . Default false .
2023-08-03 13:17:32 +02:00
*
* @ return int Count of deleted Transients .
*/
2024-10-12 20:18:25 +02:00
function fictioneer_delete_transients_like ( $partial_key , $fast = false ) {
2023-08-03 13:17:32 +02:00
// Globals
global $wpdb ;
// Setup
$count = 0 ;
// Fast?
if ( $fast ) {
// Prepare SQL
$sql = $wpdb -> prepare (
" DELETE FROM $wpdb->options WHERE `option_name` LIKE %s OR `option_name` LIKE %s " ,
" %_transient% { $partial_key } % " ,
" %_transient_timeout% { $partial_key } % "
);
// Query
2024-10-12 20:18:25 +02:00
$count = $wpdb -> query ( $sql );
2023-08-03 13:17:32 +02:00
} else {
// Prepare SQL
$sql = $wpdb -> prepare (
" SELECT `option_name` AS `name` FROM $wpdb->options WHERE `option_name` LIKE %s " ,
" _transient% { $partial_key } % "
);
// Query
$transients = $wpdb -> get_col ( $sql );
// Build full keys and delete
foreach ( $transients as $transient ) {
$key = str_replace ( '_transient_' , '' , $transient );
$count += delete_transient ( $key ) ? 1 : 0 ;
}
}
// Return count
return $count ;
}
}
2023-08-03 23:51:04 +02:00
// =============================================================================
// GET OPTION PAGE LINK
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_assigned_page_link' ) ) {
/**
* Returns permalink for an assigned page or null
*
* @ since 5.4 . 9
*
* @ param string $option The option name of the page assignment .
*
* @ return string | null The permalink or null .
*/
function fictioneer_get_assigned_page_link ( $option ) {
// Setup
$page_id = get_option ( $option );
// Null if no page has been selected (null or -1)
if ( empty ( $page_id ) || $page_id < 0 ) {
return null ;
}
// Get permalink from options or post
$link = get_option ( " { $option } _link " );
if ( empty ( $link ) ) {
$link = get_permalink ( $page_id );
update_option ( " { $option } _link " , $link , true ); // Save for next time
}
// Return
return $link ;
}
}
2023-08-05 12:12:07 +02:00
// =============================================================================
// MULTI SAVE GUARD
// =============================================================================
if ( ! function_exists ( 'fictioneer_multi_save_guard' ) ) {
/**
* Prevents multi - fire in update hooks
*
2023-08-18 10:17:18 +02:00
* Unfortunately , the block editor always fires twice : once as REST request and
* followed by WP_POST . Only the first will have the correct parameters such as
* $update set , the second is technically no longer an update . Since blocking
* the follow - up WP_POST would block programmatically triggered actions , there
* is no other choice but to block the REST request and live with it .
*
2023-08-05 12:12:07 +02:00
* @ since 5.5 . 2
*
* @ param int $post_id The ID of the updated post .
*
2023-08-05 13:27:00 +02:00
* @ return boolean True if NOT allowed , false otherwise .
2023-08-05 12:12:07 +02:00
*/
function fictioneer_multi_save_guard ( $post_id ) {
2024-08-17 17:27:09 +02:00
// Always allow trash action to pass
if ( get_post_status ( $post_id ) === 'trash' ) {
return false ;
}
// Block REST requests and unnecessary triggers
2023-08-05 12:12:07 +02:00
if (
2024-08-17 17:27:09 +02:00
( defined ( 'REST_REQUEST' ) && REST_REQUEST && ! get_option ( 'fictioneer_allow_rest_save_actions' ) ) ||
2023-08-05 12:12:07 +02:00
wp_is_post_autosave ( $post_id ) ||
wp_is_post_revision ( $post_id ) ||
2023-08-18 10:04:15 +02:00
get_post_status ( $post_id ) === 'auto-draft'
2023-08-05 12:12:07 +02:00
) {
return true ;
}
2023-08-17 10:39:00 +02:00
// Pass
2023-08-05 12:12:07 +02:00
return false ;
}
}
2023-08-08 20:33:24 +02:00
// =============================================================================
// GET TOTAL WORD COUNT FOR ALL STORIES
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_stories_total_word_count' ) ) {
/**
2024-08-15 01:34:53 +02:00
* Returns the total word count of all published stories
2023-08-08 20:33:24 +02:00
*
2024-08-16 01:12:11 +02:00
* Note : Does not include standalone chapters for performance reasons .
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2024-08-15 01:34:53 +02:00
* @ since 5.22 . 3 - Refactored with SQL query for better performance .
*
* @ return int The word count of all published stories .
2023-08-08 20:33:24 +02:00
*/
function fictioneer_get_stories_total_word_count () {
2024-01-30 22:27:14 +01:00
// Look for cached value (purged after each update, should never be stale)
2024-01-29 14:52:56 +01:00
$transient_word_count_cache = get_transient ( 'fictioneer_stories_total_word_count' );
2023-08-08 20:33:24 +02:00
// Return cached value if found
2024-01-29 14:52:56 +01:00
if ( $transient_word_count_cache ) {
return $transient_word_count_cache ;
2023-08-19 13:33:25 +02:00
}
2023-08-08 20:33:24 +02:00
2024-08-15 01:34:53 +02:00
global $wpdb ;
2023-08-08 20:33:24 +02:00
// Setup
2024-08-15 01:34:53 +02:00
$words = 0 ;
2023-08-08 20:33:24 +02:00
2024-08-15 01:34:53 +02:00
// Sum of all word counts
$words = $wpdb -> get_var (
$wpdb -> prepare (
"
SELECT SUM ( CAST ( pm . meta_value AS UNSIGNED ))
FROM $wpdb -> postmeta AS pm
INNER JOIN $wpdb -> posts AS p ON pm . post_id = p . ID
WHERE pm . meta_key = % s
2024-08-16 01:12:11 +02:00
AND p . post_type = % s
2024-08-15 01:34:53 +02:00
AND p . post_status = % s
" ,
2024-08-16 01:12:11 +02:00
'fictioneer_story_total_word_count' ,
2024-08-15 01:34:53 +02:00
'fcn_story' ,
'publish'
2023-08-08 20:33:24 +02:00
)
);
2024-08-16 01:12:11 +02:00
// Customize
$words = fictioneer_multiply_word_count ( $words );
2023-08-08 20:33:24 +02:00
2023-11-30 17:12:48 +01:00
// Cache for next time
2024-08-15 18:56:01 +02:00
set_transient ( 'fictioneer_stories_total_word_count' , $words , DAY_IN_SECONDS );
2023-08-08 20:33:24 +02:00
// Return newly calculated value
2024-08-15 01:34:53 +02:00
return $words ;
2023-08-08 20:33:24 +02:00
}
}
2023-08-17 16:01:36 +02:00
// =============================================================================
// REDIRECT TO 404
// =============================================================================
2023-08-17 19:27:54 +02:00
if ( ! function_exists ( 'fictioneer_redirect_to_404' ) ) {
/**
* Redirects the current request to the WordPress 404 page
*
2024-01-26 17:45:59 +01:00
* @ since 5.6 . 0
2023-08-20 16:14:19 +02:00
*
2023-08-17 19:27:54 +02:00
* @ global WP_Query $wp_query The main WP_Query instance .
*/
2023-08-17 16:01:36 +02:00
2023-08-17 19:27:54 +02:00
function fictioneer_redirect_to_404 () {
global $wp_query ;
2023-08-17 16:01:36 +02:00
2023-08-17 21:30:42 +02:00
// Remove scripts to avoid errors
add_action ( 'wp_print_scripts' , function () {
wp_dequeue_script ( 'fictioneer-chapter-scripts' );
wp_dequeue_script ( 'fictioneer-suggestion-scripts' );
wp_dequeue_script ( 'fictioneer-tts-scripts' );
wp_dequeue_script ( 'fictioneer-story-scripts' );
}, 99 );
// Set query to 404
2023-08-17 19:27:54 +02:00
$wp_query -> set_404 ();
status_header ( 404 );
2023-08-17 21:30:42 +02:00
nocache_headers ();
2023-08-17 19:27:54 +02:00
get_template_part ( 404 );
2023-08-17 16:01:36 +02:00
2023-08-17 21:30:42 +02:00
// Terminate
2023-08-17 19:27:54 +02:00
exit ();
}
2023-08-17 16:01:36 +02:00
}
2023-08-17 19:27:54 +02:00
// =============================================================================
// PREVIEW ACCESS VERIFICATION
// =============================================================================
2023-08-31 17:30:24 +02:00
if ( ! function_exists ( 'fictioneer_verify_unpublish_access' ) ) {
2023-08-17 19:27:54 +02:00
/**
2023-08-31 17:30:24 +02:00
* Verifies access to unpublished posts ( not drafts )
2023-08-17 19:27:54 +02:00
*
2024-01-26 17:45:59 +01:00
* @ since 5.6 . 0
2023-08-20 16:14:19 +02:00
*
2023-08-17 19:27:54 +02:00
* @ return boolean True if access granted , false otherwise .
*/
2023-08-31 17:30:24 +02:00
function fictioneer_verify_unpublish_access ( $post_id ) {
// Setup
$post = get_post ( $post_id );
2024-09-25 22:00:32 +07:00
$authorized = false ;
2023-08-31 17:30:24 +02:00
// Always let owner to pass
if ( get_current_user_id () === absint ( $post -> post_author ) ) {
2024-09-25 22:00:32 +07:00
$authorized = true ;
2023-08-31 17:30:24 +02:00
}
// Always let administrators pass
if ( current_user_can ( 'manage_options' ) ) {
2024-09-25 22:00:32 +07:00
$authorized = true ;
2023-08-31 17:30:24 +02:00
}
// Check capability for post type
if ( $post -> post_status === 'private' ) {
switch ( $post -> post_type ) {
case 'post' :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'edit_private_posts' );
2023-08-31 17:30:24 +02:00
break ;
case 'page' :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'edit_private_pages' );
2023-08-31 17:30:24 +02:00
break ;
case 'fcn_chapter' :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'read_private_fcn_chapters' );
2023-08-31 17:30:24 +02:00
break ;
case 'fcn_story' :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'read_private_fcn_stories' );
2023-08-31 17:30:24 +02:00
break ;
case 'fcn_recommendation' :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'read_private_fcn_recommendations' );
2023-08-31 17:30:24 +02:00
break ;
case 'fcn_collection' :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'read_private_fcn_collections' );
2023-08-31 17:30:24 +02:00
break ;
default :
2024-09-25 22:00:32 +07:00
$authorized = current_user_can ( 'edit_others_posts' );
2023-08-17 19:27:54 +02:00
}
}
2023-08-31 17:30:24 +02:00
// Drafts are handled by WordPress
2024-09-25 22:00:32 +07:00
return apply_filters ( 'fictioneer_filter_verify_unpublish_access' , $authorized , $post_id , $post );
2023-08-17 18:55:36 +02:00
}
}
2023-08-20 16:32:35 +02:00
// =============================================================================
// ADD ACTION TO SAVE/TRASH/UNTRASH/DELETE HOOKS WITH POST ID
// =============================================================================
/**
* Adds callback to save , trash , untrash , and delete hooks ( 1 argument )
*
* This helper saves some time / space adding a callback action to all four
* default post operations . But only with the first argument : post_id .
*
2024-01-26 17:45:59 +01:00
* @ since 5.6 . 3
2023-08-20 16:32:35 +02:00
*
* @ param callable $function The callback function to be added .
* @ param int $priority Optional . Used to specify the order in which the
* functions associated with a particular action are
* executed . Default 10. Lower numbers correspond with
* earlier execution , and functions with the same
* priority are executed in the order in which they
* were added to the action .
*
* @ return true Will always return true .
*/
function fictioneer_add_stud_post_actions ( $function , $priority = 10 ) {
$hooks = [ 'save_post' , 'trashed_post' , 'delete_post' , 'untrash_post' ];
foreach ( $hooks as $hook ) {
add_action ( $hook , $function , $priority );
}
return true ;
}
2023-10-02 00:02:12 +02:00
// =============================================================================
// CONVERT URL LIST TO ARRAY
// =============================================================================
/**
* Turn line - break separated list into array of links
*
2024-01-26 17:45:59 +01:00
* @ since 5.7 . 4
2023-10-02 00:02:12 +02:00
*
* @ param string $list The list of links
*
* @ return array The array of links .
*/
function fictioneer_url_list_to_array ( $list ) {
// Already array?
if ( is_array ( $list ) ) {
return $list ;
}
// Catch falsy values
if ( empty ( $list ) ) {
return [];
}
// Prepare URLs
$urls = [];
$lines = explode ( " \n " , $list );
// Extract
foreach ( $lines as $line ) {
$tuple = explode ( '|' , $line );
$tuple = array_map ( 'trim' , $tuple );
$urls [] = array (
'name' => wp_strip_all_tags ( $tuple [ 0 ] ),
2024-06-10 10:32:23 +02:00
'url' => fictioneer_sanitize_url ( $tuple [ 1 ] )
2023-10-02 00:02:12 +02:00
);
}
// Return
return $urls ;
}
2023-11-08 13:41:15 +01:00
// =============================================================================
2023-11-08 19:59:14 +01:00
// ARRAY OPERATIONS
2023-11-08 13:41:15 +01:00
// =============================================================================
/**
* Unset array element by value
*
2024-01-26 17:45:59 +01:00
* @ since 5.7 . 5
2023-11-08 13:41:15 +01:00
*
* @ param mixed $value The value to look for .
* @ param array $array The array to be modified .
*
* @ return array The modified array .
*/
function fictioneer_unset_by_value ( $value , $array ) {
if ( ( $key = array_search ( $value , $array ) ) !== false ) {
unset ( $array [ $key ] );
}
return $array ;
}
2023-11-08 19:59:14 +01:00
// =============================================================================
// RETURN NO FORMAT STRING
// =============================================================================
/**
2024-01-19 14:42:19 +01:00
* Returns an unformatted replacement string
2023-11-08 19:59:14 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 5.7 . 5
2023-11-08 19:59:14 +01:00
*
* @ return string Just a simple '%s' .
*/
function fictioneer__return_no_format () {
return '%s' ;
}
2024-01-19 14:42:19 +01:00
// =============================================================================
// TRUNCATE STRING
// =============================================================================
/**
* Returns a truncated string without tags
*
2024-01-26 17:45:59 +01:00
* @ since 5.9 . 0
2024-01-19 14:42:19 +01:00
*
* @ param string $string The string to truncate .
* @ param int $length Maximum length in characters .
* @ param string | null $ellipsis Optional . Truncation indicator suffix .
*
* @ return string The truncated string without tags .
*/
function fictioneer_truncate ( string $string , int $length , string $ellipsis = null ) {
// Setup
$string = wp_strip_all_tags ( $string ); // Prevent tags from being cut off
$ellipsis = $ellipsis ? ? FICTIONEER_TRUNCATION_ELLIPSIS ;
// Return truncated string
2024-08-21 21:06:14 +02:00
if ( function_exists ( 'mb_strimwidth' ) ) {
2024-01-19 14:42:19 +01:00
return mb_strimwidth ( $string , 0 , $length , $ellipsis );
} else {
return strlen ( $string ) > $length ? substr ( $string , 0 , $length ) . $ellipsis : $string ;
}
}
2024-01-28 15:31:16 +01:00
// =============================================================================
2024-03-06 22:06:09 +01:00
// COMPARE WORDPRESS VERSION
// =============================================================================
/**
* Compare installed WordPress version against version string
*
* @ since 5.12 . 2
* @ global wpdb $wp_version Current WordPress version string .
*
* @ param string $version The version string to test against .
* @ param string $operator Optional . How to compare . Default '>=' .
*
* @ return boolean True or false .
*/
function fictioneer_compare_wp_version ( $version , $operator = '>=' ) {
global $wp_version ;
return version_compare ( $wp_version , $version , $operator );
}
2024-03-08 11:59:39 +01:00
// =============================================================================
// CSS LOADING PATTERN
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_async_css_loading_pattern' ) ) {
/**
* Returns the media attribute and loading strategy for stylesheets
*
* @ since 5.12 . 2
*
* @ return string Media attribute script for stylesheet links .
*/
function fictioneer_get_async_css_loading_pattern () {
if ( FICTIONEER_ENABLE_ASYNC_ONLOAD_PATTERN ) {
return 'media="print" onload="this.media=\'all\'; this.onload=null;"' ;
}
return 'media="all"' ;
}
}
2024-04-16 21:00:31 +02:00
// =============================================================================
2024-04-17 15:08:47 +02:00
// GENERATE PLACEHOLDER
2024-04-16 21:00:31 +02:00
// =============================================================================
2024-04-17 15:08:47 +02:00
if ( ! function_exists ( 'fictioneer_generate_placeholder' ) ) {
2024-04-16 21:00:31 +02:00
/**
2024-04-17 15:08:47 +02:00
* Dummy implementation ( currently used a CSS background )
2024-04-16 21:00:31 +02:00
*
* @ since 5.14 . 0
*
2024-04-17 15:08:47 +02:00
* @ param array | null $args Optional arguments to generate a placeholder .
2024-04-16 21:00:31 +02:00
*
2024-04-17 15:08:47 +02:00
* @ return string The placeholder URL or data URI .
2024-04-16 21:00:31 +02:00
*/
2024-04-17 15:08:47 +02:00
function fictioneer_generate_placeholder ( $args = [] ) {
return '' ;
2024-04-16 21:00:31 +02:00
}
}
2024-04-26 12:31:34 +02:00
// =============================================================================
// FIND USER(S)
// =============================================================================
/**
* Find ( first ) user by display name
*
* @ since 5.14 . 1
*
* @ param string $display_name The display name to search for .
*
* @ return WP_User | null The first matching user or null if not found .
*/
function fictioneer_find_user_by_display_name ( $display_name ) {
// No choice but to query all because the display name is not unique
$users = get_users (
array (
'search' => sanitize_user ( $display_name ),
'search_columns' => [ 'display_name' ],
'number' => 1
)
);
// If found, return first
if ( ! empty ( $users ) ) {
return $users [ 0 ];
}
// Not found
return null ;
}
2024-04-28 11:35:11 +02:00
// =============================================================================
// JOIN ARRAYS IN SPECIAL WAYS
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_human_readable_list' ) ) {
/**
* Join string in an array as human readable list .
*
* @ since 5.15 . 0
* @ link https :// gist . github . com / SleeplessByte / 4514697
*
* @ param array $array Array of strings .
*
* @ return string The human readable list .
*/
function fictioneer_get_human_readable_list ( $array ) {
// Setup
$comma = _x ( ', ' , 'Human readable list joining three or more items except the last two.' , 'fictioneer' );
$double = _x ( ' or ' , 'Human readable list joining two items.' , 'fictioneer' );
$final = _x ( ', or ' , 'Human readable list joining the last two of three or more items.' , 'fictioneer' );
// One or two items
if ( count ( $array ) < 3 ) {
return implode ( $double , $array );
}
// Three or more items
array_splice ( $array , - 2 , 2 , implode ( $final , array_slice ( $array , - 2 , 2 ) ) );
// Finish
return implode ( $comma , $array );
}
}
2024-04-28 14:02:24 +02:00
// =============================================================================
// PATREON UTILITIES
// =============================================================================
/**
2024-05-10 16:49:45 +02:00
* Get Patreon data for a post
*
* Note : Considers both the post meta and global settings .
2024-04-28 14:02:24 +02:00
*
* @ since 5.15 . 0
*
* @ param WP_Post | null $post The post to check . Default to global $post .
*
2024-04-29 22:39:33 +02:00
* @ return array | null Array with 'gated' ( bool ), 'gate_tiers' ( array ), 'gate_cents' ( int ),
* and 'gate_lifetime_cents' ( int ) . Null if the post could not be found .
2024-04-28 14:02:24 +02:00
*/
2024-05-10 16:49:45 +02:00
function fictioneer_get_post_patreon_data ( $post = null ) {
2024-04-28 14:02:24 +02:00
// Static cache
static $cache = [];
// Post?
$post = $post ? ? get_post ();
2024-05-17 17:46:54 +02:00
$post_id = $post -> ID ;
2024-04-28 14:02:24 +02:00
if ( ! $post ) {
return null ;
}
// Check cache
2024-05-17 17:46:54 +02:00
if ( isset ( $cache [ $post_id ] ) ) {
return $cache [ $post_id ];
2024-04-28 14:02:24 +02:00
}
// Setup
2024-04-28 14:16:24 +02:00
$global_tiers = get_option ( 'fictioneer_patreon_global_lock_tiers' , [] ) ? : [];
$global_amount_cents = get_option ( 'fictioneer_patreon_global_lock_amount' , 0 ) ? : 0 ;
2024-04-29 02:52:00 +02:00
$global_lifetime_amount_cents = get_option ( 'fictioneer_patreon_global_lock_lifetime_amount' , 0 ) ? : 0 ;
2024-05-17 17:46:54 +02:00
$post_tiers = get_post_meta ( $post_id , 'fictioneer_patreon_lock_tiers' , true );
2024-04-28 14:16:24 +02:00
$post_tiers = is_array ( $post_tiers ) ? $post_tiers : [];
2024-05-17 17:46:54 +02:00
$post_amount_cents = absint ( get_post_meta ( $post_id , 'fictioneer_patreon_lock_amount' , true ) );
2024-04-28 14:16:24 +02:00
$check_tiers = array_merge ( $global_tiers , $post_tiers );
2024-04-28 15:45:00 +02:00
$check_tiers = array_unique ( $check_tiers );
2024-04-28 14:16:24 +02:00
$check_amount_cents = $post_amount_cents > 0 ? $post_amount_cents : $global_amount_cents ;
$check_amount_cents = $post_amount_cents > 0 && $global_amount_cents > 0
? min ( $post_amount_cents , $global_amount_cents ) : $check_amount_cents ;
$check_amount_cents = max ( $check_amount_cents , 0 );
2024-04-28 14:02:24 +02:00
// Compile
$data = array (
2024-04-28 14:16:24 +02:00
'gated' => $check_tiers || $check_amount_cents > 0 ,
'gate_tiers' => $check_tiers ,
'gate_cents' => $check_amount_cents ,
2024-04-29 02:52:00 +02:00
'gate_lifetime_cents' => $global_lifetime_amount_cents
2024-04-28 14:02:24 +02:00
);
// Cache
2024-05-17 17:46:54 +02:00
$cache [ $post_id ] = $data ;
2024-04-28 14:02:24 +02:00
// Return
return $data ;
}
2024-05-21 00:00:37 +02:00
// =============================================================================
// ENCRYPTION/DECRYPTION
// =============================================================================
/**
* Encrypt data
*
* @ since 5.19 . 0
*
* @ param mixed $data The data to encrypt .
*
* @ return string | false The encrypted data or false on failure .
*/
function fictioneer_encrypt ( $data ) {
$key = wp_salt ();
$encrypted = openssl_encrypt (
json_encode ( $data ),
'aes-256-cbc' ,
$key ,
0 ,
substr ( hash ( 'sha256' , $key ), 0 , 16 )
);
if ( $encrypted === false ) {
return false ;
}
return base64_encode ( $encrypted );
}
/**
* Decrypt data
*
* @ since 5.19 . 0
*
* @ param string $data The data to decrypt .
*
* @ return mixed The decrypted data .
*/
function fictioneer_decrypt ( $data ) {
$key = wp_salt ();
$decrypted = openssl_decrypt (
base64_decode ( $data ),
'aes-256-cbc' ,
$key ,
0 ,
substr ( hash ( 'sha256' , $key ), 0 , 16 )
);
return json_decode ( $decrypted , true );
}
2024-05-21 14:43:33 +02:00
// =============================================================================
// RANDOM USERNAME
// =============================================================================
/**
2024-05-21 20:09:46 +02:00
* Returns array of adjectives for randomized username generation
2024-05-21 14:43:33 +02:00
*
* @ since 5.19 . 0
*
2024-05-21 20:09:46 +02:00
* @ return array Array of nouns .
2024-05-21 14:43:33 +02:00
*/
2024-05-21 20:09:46 +02:00
function fictioneer_get_username_adjectives () {
2024-05-21 14:43:33 +02:00
$adjectives = array (
'Radical' , 'Tubular' , 'Gnarly' , 'Epic' , 'Electric' , 'Neon' , 'Bodacious' , 'Rad' ,
'Totally' , 'Funky' , 'Wicked' , 'Fresh' , 'Chill' , 'Groovy' , 'Vibrant' , 'Flashy' ,
'Buff' , 'Hella' , 'Motor' , 'Cyber' , 'Pixel' , 'Holo' , 'Stealth' , 'Synthetic' ,
'Enhanced' , 'Synth' , 'Bio' , 'Laser' , 'Virtual' , 'Analog' , 'Mega' , 'Wave' , 'Solo' ,
'Retro' , 'Quantum' , 'Robotic' , 'Digital' , 'Hyper' , 'Punk' , 'Giga' , 'Electro' ,
'Chrome' , 'Fusion' , 'Vivid' , 'Stellar' , 'Galactic' , 'Turbo' , 'Atomic' , 'Cosmic' ,
'Artificial' , 'Kinetic' , 'Binary' , 'Hypersonic' , 'Runic' , 'Data' , 'Knightly' ,
'Cryonic' , 'Nebular' , 'Golden' , 'Silver' , 'Red' , 'Crimson' , 'Augmented' , 'Vorpal' ,
'Ascended' , 'Serious' , 'Solid' , 'Master' , 'Prism' , 'Spinning' , 'Masked' , 'Hardcore' ,
'Somber' , 'Celestial' , 'Arcane' , 'Luminous' , 'Ionized' , 'Lunar' , 'Uncanny' , 'Subatomic' ,
'Luminary' , 'Radiant' , 'Ultra' , 'Starship' , 'Space' , 'Starlight' , 'Interstellar' , 'Metal' ,
2024-05-21 17:23:21 +02:00
'Bionic' , 'Machine' , 'Isekai' , 'Warp' , 'Neo' , 'Alpha' , 'Power' , 'Unhinged' , 'Ash' ,
'Savage' , 'Silent' , 'Screaming' , 'Misty' , 'Rending' , 'Horny' , 'Dreadful' , 'Bizarre' ,
'Chaotic' , 'Wacky' , 'Twisted' , 'Manic' , 'Crystal' , 'Infernal' , 'Ruthless' , 'Grim' ,
2024-09-01 03:55:17 +02:00
'Mortal' , 'Forsaken' , 'Heretical' , 'Cursed' , 'Blighted' , 'Scarlet' , 'Delightful' ,
'Nuclear' , 'Azure' , 'Emerald' , 'Amber' , 'Mystic' , 'Ethereal' , 'Enchanted' , 'Valiant' ,
'Fierce' , 'Obscure' , 'Enigmatic'
2024-05-21 14:43:33 +02:00
);
2024-05-21 20:09:46 +02:00
return apply_filters ( 'fictioneer_random_username_adjectives' , $adjectives );
}
/**
* Returns array of nouns for randomized username generation
*
* @ since 5.19 . 0
*
* @ return array Array of nouns .
*/
function fictioneer_get_username_nouns () {
2024-05-21 14:43:33 +02:00
$nouns = array (
'Avatar' , 'Cassette' , 'Rubiks' , 'Gizmo' , 'Synthwave' , 'Tron' , 'Replicant' , 'Warrior' ,
'Hacker' , 'Samurai' , 'Cyborg' , 'Runner' , 'Mercenary' , 'Shogun' , 'Maverick' , 'Glitch' ,
'Byte' , 'Matrix' , 'Motion' , 'Shinobi' , 'Circuit' , 'Droid' , 'Virus' , 'Vortex' , 'Mech' ,
'Codex' , 'Hologram' , 'Specter' , 'Intelligence' , 'Technomancer' , 'Rider' , 'Ghost' ,
'Hunter' , 'Hound' , 'Wizard' , 'Knight' , 'Rogue' , 'Scout' , 'Ranger' , 'Paladin' , 'Sorcerer' ,
'Mage' , 'Artificer' , 'Cleric' , 'Tank' , 'Fighter' , 'Pilot' , 'Necromancer' , 'Neuromancer' ,
'Barbarian' , 'Streetpunk' , 'Phantom' , 'Shaman' , 'Druid' , 'Dragon' , 'Dancer' , 'Captain' ,
'Pirate' , 'Snake' , 'Rebel' , 'Kraken' , 'Spark' , 'Blitz' , 'Alchemist' , 'Dragoon' , 'Geomancer' ,
'Neophyte' , 'Terminator' , 'Tempest' , 'Enigma' , 'Automaton' , 'Daemon' , 'Juggernaut' ,
'Paragon' , 'Sentinel' , 'Viper' , 'Velociraptor' , 'Spirit' , 'Punk' , 'Synth' , 'Biomech' ,
'Engineer' , 'Pentagoose' , 'Vampire' , 'Soldier' , 'Chimera' , 'Lobotomy' , 'Mutant' ,
2024-05-21 17:23:21 +02:00
'Revenant' , 'Wraith' , 'Chupacabra' , 'Banshee' , 'Fae' , 'Leviathan' , 'Cenobite' , 'Bob' ,
'Ketchum' , 'Collector' , 'Student' , 'Lover' , 'Chicken' , 'Alien' , 'Titan' , 'Sinner' ,
'Nightmare' , 'Bioplague' , 'Annihilation' , 'Elder' , 'Priest' , 'Guardian' , 'Quagmire' ,
'Berserker' , 'Oblivion' , 'Decimator' , 'Devastation' , 'Calamity' , 'Doom' , 'Ruin' , 'Abyss' ,
'Heretic' , 'Armageddon' , 'Obliteration' , 'Inferno' , 'Torment' , 'Carnage' , 'Purgatory' ,
2024-09-01 03:55:17 +02:00
'Chastity' , 'Angel' , 'Raven' , 'Star' , 'Trinity' , 'Idol' , 'Eidolon' , 'Havoc' , 'Nirvana' ,
'Digitron' , 'Phoenix' , 'Lantern' , 'Warden' , 'Falcon'
2024-05-21 14:43:33 +02:00
);
2024-05-21 20:09:46 +02:00
return apply_filters ( 'fictioneer_random_username_nouns' , $nouns );
}
/**
* Returns randomized username
*
* @ since 5.19 . 0
*
* @ param bool $unique Optional . Whether the username must be unique . Default true .
*
* @ return string Sanitized random username .
*/
function fictioneer_get_random_username ( $unique = true ) {
// Setup
$adjectives = fictioneer_get_username_adjectives ();
$nouns = fictioneer_get_username_nouns ();
2024-05-21 14:43:33 +02:00
2024-05-21 17:23:21 +02:00
// Shuffle the arrays to ensure more randomness
shuffle ( $adjectives );
shuffle ( $nouns );
2024-05-21 20:09:46 +02:00
// Build username
2024-05-21 14:43:33 +02:00
do {
$username = $adjectives [ array_rand ( $adjectives ) ] . $nouns [ array_rand ( $nouns ) ] . rand ( 1000 , 9999 );
$username = sanitize_user ( $username , true );
2024-05-21 20:09:46 +02:00
} while ( username_exists ( $username ) && $unique );
2024-05-21 14:43:33 +02:00
2024-05-21 20:09:46 +02:00
// Return username
2024-05-21 14:43:33 +02:00
return $username ;
}
2024-06-24 15:33:48 +02:00
2024-07-30 09:30:40 +02:00
// =============================================================================
// STRING LENGTH
// =============================================================================
if ( ! function_exists ( 'mb_strlen' ) ) {
/**
* Fallback function for mb_strlen
*
* @ param string $string The string to being measured .
* @ param string $encoding The character encoding . Default UTF - 8.
*
* @ return int The number of characters in the string .
*/
function mb_strlen ( $string , $encoding = 'UTF-8' ) {
if ( $encoding !== 'UTF-8' ) {
return strlen ( $string );
}
$converted_string = iconv ( $encoding , 'UTF-16' , $string );
if ( $converted_string === false ) {
return strlen ( $string );
} else {
return strlen ( $converted_string ) / 2 ; // Each character is 2 bytes in UTF-16
}
}
}
2024-09-01 00:35:16 +02:00
// =============================================================================
// GET ALL PUBLISHING AUTHORS
// =============================================================================
/**
* Returns all authors with published posts
*
2024-09-01 15:08:16 +02:00
* Note : Qualified post types are fcn_story , fcn_chapter , fcn_recommendation ,
* and post . The result is cached for 12 hours as Transient .
2024-09-01 00:35:16 +02:00
*
2024-10-01 12:07:27 +02:00
* @ since 5.24 . 0
2024-09-01 15:08:16 +02:00
* @ link https :// developer . wordpress . org / reference / functions / get_users /
*
* @ param array $args Optional . Array of additional query arguments .
*
* @ return array Array of WP_User object , stdClass objects , or IDs .
2024-09-01 00:35:16 +02:00
*/
2024-09-01 15:08:16 +02:00
function fictioneer_get_publishing_authors ( $args = [] ) {
2024-09-01 00:35:16 +02:00
static $authors = null ;
2024-09-01 15:08:16 +02:00
$key = 'fictioneer_publishing_authors_' . md5 ( serialize ( $args ) );
if ( ! $authors && $transient = get_transient ( $key ) ) {
2024-09-01 00:35:16 +02:00
$authors = $transient ;
}
if ( $authors ) {
return $authors ;
}
$authors = get_users (
2024-09-01 15:08:16 +02:00
array_merge (
array ( 'has_published_posts' => [ 'fcn_story' , 'fcn_chapter' , 'fcn_recommendation' , 'post' ] ),
$args
2024-09-01 00:35:16 +02:00
)
);
2024-09-01 15:08:16 +02:00
set_transient ( $key , $authors , 12 * HOUR_IN_SECONDS );
2024-09-01 00:35:16 +02:00
return $authors ;
}
2024-10-01 12:07:27 +02:00
// =============================================================================
2024-10-04 16:28:08 +02:00
// GET POST LABELS
2024-10-01 12:07:27 +02:00
// =============================================================================
/**
* Returns the translated label of the post status
*
* @ since 5.24 . 5
*
* @ param string $status Post status .
*
* @ return string Translated label of the post status or the post status if custom .
*/
function fictioneer_get_post_status_label ( $status ) {
static $labels = null ;
if ( ! $labels ) {
$labels = array (
'draft' => get_post_status_object ( 'draft' ) -> label ,
'pending' => get_post_status_object ( 'pending' ) -> label ,
'publish' => get_post_status_object ( 'publish' ) -> label ,
'private' => get_post_status_object ( 'private' ) -> label ,
'future' => get_post_status_object ( 'future' ) -> label ,
'trash' => get_post_status_object ( 'trash' ) -> label
);
}
return $labels [ $status ] ? ? $status ;
}
2024-10-04 16:28:08 +02:00
/**
* Returns the translated label of the post type
*
* @ since 5.25 . 0
*
* @ param string $type Post type .
*
* @ return string Translated label of the post type or the post type if custom .
*/
function fictioneer_get_post_type_label ( $type ) {
static $labels = null ;
if ( ! $labels ) {
$labels = array (
'post' => _x ( 'Post' , 'Post type label.' , 'fictioneer' ),
'page' => _x ( 'Page' , 'Post type label.' , 'fictioneer' ),
'fcn_story' => _x ( 'Story' , 'Post type label.' , 'fictioneer' ),
'fcn_chapter' => _x ( 'Chapter' , 'Post type label.' , 'fictioneer' ),
'fcn_collection' => _x ( 'Collection' , 'Post type label.' , 'fictioneer' ),
'fcn_recommendation' => _x ( 'Rec' , 'Post type label.' , 'fictioneer' )
);
}
return $labels [ $type ] ? ? $type ;
}
2024-10-11 13:44:59 +02:00
2024-10-12 20:23:13 +02:00
// =============================================================================
// LOGS
// =============================================================================
/**
* Returns ( or creates ) secret log hash used to obscure the log file name
*
* @ since 5.24 . 1
*
* @ return string The log hash .
*/
function fictioneer_get_log_hash () {
$hash = strval ( get_option ( 'fictioneer_log_hash' ) );
if ( ! empty ( $hash ) ) {
return $hash ;
}
$hash = wp_generate_password ( 32 , false );
update_option ( 'fictioneer_log_hash' , $hash , 'no' );
return $hash ;
}
/**
* Logs a message to the theme log file
*
* @ since 5.0 . 0
*
* @ param string $message What has been updated
* @ param WP_User | null $user The user who did it . Defaults to current user .
*/
function fictioneer_log ( $message , $current_user = null ) {
// Setup
$current_user = $current_user ? ? wp_get_current_user ();
$username = _x ( 'System' , 'Default name in logs.' , 'fictioneer' );
$log_hash = fictioneer_get_log_hash ();
$log_file = WP_CONTENT_DIR . " /fictioneer- { $log_hash } -log.log " ;
$log_limit = 5000 ;
$date = current_time ( 'mysql' , true );
if ( is_object ( $current_user ) && $current_user -> ID > 0 ) {
$username = $current_user -> user_login . ' #' . $current_user -> ID ;
}
if ( empty ( $current_user ) && wp_doing_cron () ) {
$username = 'WP Cron' ;
}
if ( empty ( $current_user ) && wp_doing_ajax () ) {
$username = 'AJAX' ;
}
$username = empty ( $username ) ? __ ( 'Anonymous' , 'fictioneer' ) : $username ;
// Make sure the log file exists
if ( ! file_exists ( $log_file ) ) {
file_put_contents ( $log_file , '' );
}
// Read
$log_contents = file_get_contents ( $log_file );
// Parse
$log_entries = explode ( " \n " , $log_contents );
// Limit (if too large)
$log_entries = array_slice ( $log_entries , - ( $log_limit + 1 ) );
// Add new entry
$log_entries [] = " [ { $date } UTC] [ { $username } ] $message " ;
// Concatenate and save
file_put_contents ( $log_file , implode ( " \n " , $log_entries ) );
// Set file permissions
chmod ( $log_file , 0600 );
// Security
$silence = WP_CONTENT_DIR . '/index.php' ;
if ( ! file_exists ( $silence ) ) {
file_put_contents ( $silence , " <?php \n // Silence is golden. \n " );
chmod ( $silence , 0600 );
}
}
/**
* Retrieves the log entries and returns an HTML representation
*
* @ return string The HTML representation of the log entries .
*/
function fictioneer_get_log () {
// Setup
$log_hash = fictioneer_get_log_hash ();
$log_file = WP_CONTENT_DIR . " /fictioneer- { $log_hash } -log.log " ;
$output = '' ;
// Check whether log file exists
if ( ! file_exists ( $log_file ) ) {
return '<ul class="fictioneer-log"><li class="fictioneer-log__item">No log entries yet.</li></ul>' ;
}
// Read
$log_contents = file_get_contents ( $log_file );
// Parse
$log_entries = explode ( " \n " , $log_contents );
// Limit display to 250
$log_entries = array_slice ( $log_entries , - 250 );
// Reverse
$log_entries = array_reverse ( $log_entries );
// Build list items
foreach ( $log_entries as $entry ) {
$output .= '<li class="fictioneer-log__item">' . esc_html ( $entry ) . '</li>' ;
}
// Return HTML
return '<ul class="fictioneer-log">' . $output . '</ul>' ;
}
/**
* Retrieves the debug log entries and returns an HTML representation
*
* @ return string The HTML representation of the log entries .
*/
function fictioneer_get_wp_debug_log () {
// Setup
$log_file = WP_CONTENT_DIR . '/debug.log' ;
$output = '' ;
// Check whether log file exists
if ( ! file_exists ( $log_file ) ) {
return '<ul class="fictioneer-log _wp-debug-log"><li class="fictioneer-log__item">No log entries yet.</li></ul>' ;
}
// Read
$log_contents = file_get_contents ( $log_file );
// Parse
$log_entries = explode ( " \n " , $log_contents );
// Limit display to 250
$log_entries = array_slice ( $log_entries , - 250 );
// Reverse
$log_entries = array_reverse ( $log_entries );
// Build list items
foreach ( $log_entries as $entry ) {
$output .= '<li class="fictioneer-log__item _wp-debug-log">' . esc_html ( $entry ) . '</li>' ;
}
// Return HTML
return '<ul class="fictioneer-log _wp-debug-log">' . $output . '</ul>' ;
}