2023-01-21 01:31:34 +01:00
< ? php
// =============================================================================
// 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
// =============================================================================
if ( ! function_exists ( 'fictioneer_is_valid_json' ) ) {
/**
* Check whether a JSON is valid
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $data JSON string hopeful .
2023-01-21 01:31:34 +01:00
*
* @ return boolean True if the JSON is valid , false if not .
*/
function fictioneer_is_valid_json ( $data = null ) {
2023-08-22 10:28:40 +02:00
if ( empty ( $data ) ) {
return false ;
}
2023-01-21 01:31:34 +01:00
$data = @ json_decode ( $data , true );
2023-08-22 10:28:40 +02:00
2023-01-21 01:31:34 +01:00
return ( json_last_error () === JSON_ERROR_NONE );
}
}
// =============================================================================
// CHECK FOR ACTIVE PLUGINS
// =============================================================================
if ( ! function_exists ( 'fictioneer_is_plugin_active' ) ) {
/**
* Checks whether a plugin is active
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-01-21 01:31:34 +01:00
*
2023-08-08 20:33:24 +02:00
* @ param string $path Relative path to the plugin .
2023-01-21 01:31:34 +01:00
*
* @ return boolean True if the plugin is active , otherwise false .
*/
function fictioneer_is_plugin_active ( $path ) {
return in_array ( $path , apply_filters ( 'active_plugins' , get_option ( 'active_plugins' ) ) );
}
}
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 () {
2023-08-22 10:28:40 +02:00
$bool = fictioneer_is_plugin_active ( 'wordpress-seo/wp-seo.php' ) ||
fictioneer_is_plugin_active ( 'wordpress-seo-premium/wp-seo-premium.php' ) ||
function_exists ( 'aioseo' );
2023-01-21 01:31:34 +01:00
return $bool ;
}
}
// =============================================================================
// 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
// =============================================================================
if ( ! function_exists ( 'fictioneer_get_last_story_or_chapter_update' ) ) {
/**
* 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-01-26 15:00:53 +01:00
*
* @ param int $story_id ID of the story .
*
* @ return array Array of chapter posts or empty .
*/
function fictioneer_get_story_chapter_posts ( $story_id ) {
// Setup
$chapter_ids = fictioneer_get_story_chapter_ids ( $story_id );
// No chapters?
if ( empty ( $chapter_ids ) ) {
return [];
}
2024-01-26 16:22:07 +01:00
// Few chapters?
if ( count ( $chapter_ids ) < 50 ) {
2024-01-26 16:23:48 +01:00
// Query with post__in, which should be faster than meta query
// as long as the ID array is not too large.
2024-01-26 16:22:07 +01:00
$chapter_query = new WP_Query (
array (
'post_type' => 'fcn_chapter' ,
'post_status' => 'publish' ,
'post__in' => fictioneer_rescue_array_zero ( $chapter_ids ),
'orderby' => 'post__in' ,
'ignore_sticky_posts' => true ,
'posts_per_page' => - 1 ,
'no_found_rows' => true , // Improve performance
'update_post_term_cache' => false // Improve performance
)
);
return $chapter_query -> posts ;
}
2024-01-26 15:00:53 +01:00
// Query
$chapter_query = new WP_Query (
array (
'post_type' => 'fcn_chapter' ,
'post_status' => 'publish' ,
'meta_key' => 'fictioneer_chapter_story' ,
'meta_value' => $story_id ,
'ignore_sticky_posts' => true ,
'posts_per_page' => - 1 , // Get all chapters (this can be hundreds)
'no_found_rows' => true , // Improve performance
'update_post_term_cache' => false // Improve performance
)
);
// Filter out chapters not included in chapter ID array
$filtered_chapters = array_filter ( $chapter_query -> posts , function ( $post ) use ( $chapter_ids ) {
return in_array ( $post -> ID , $chapter_ids );
});
// Sort by order of chapter ID array
usort ( $filtered_chapters , function ( $a , $b ) use ( $chapter_ids ) {
$pos_a = array_search ( $a -> ID , $chapter_ids );
$pos_b = array_search ( $b -> ID , $chapter_ids );
return $pos_a - $pos_b ;
});
// Return chapters selected in story
return $filtered_chapters ;
}
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
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 = [] ) {
2023-01-21 01:31:34 +01:00
$story_id = fictioneer_validate_id ( $story_id , 'fcn_story' );
2023-08-14 20:55:59 +02:00
$old_data = false ;
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
// Check cache
2023-08-14 20:55:59 +02:00
if ( FICTIONEER_ENABLE_STORY_DATA_META_CACHE ) {
2023-11-30 16:03:53 +01:00
$old_data = 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
if ( ! empty ( $old_data ) && $old_data [ 'last_modified' ] >= 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 ) {
2023-08-07 14:21:45 +02:00
return $old_data ;
}
// Time to refresh comment count?
$comment_count_delay = ( $old_data [ 'comment_count_timestamp' ] ? ? 0 ) + FICTIONEER_STORY_COMMENT_COUNT_TIMEOUT ;
2024-01-27 00:30:20 +01:00
$refresh_comments = $comment_count_delay < time () ||
( $args [ 'refresh_comment_count' ] ? ? 0 ) || fictioneer_caching_active ();
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
$comment_count = $old_data [ 'comment_count' ];
if ( count ( $old_data [ 'chapter_ids' ] ) > 0 ) {
// Counting the stored comment count of chapters is typically
// faster than querying and counting all comments.
2024-01-26 15:00:53 +01:00
$chapters = fictioneer_get_story_chapter_posts ( $story_id );
if ( ! empty ( $chapters ) ) {
2023-08-18 13:35:46 +02:00
$comment_count = 0 ; // Reset
2024-01-26 15:00:53 +01:00
foreach ( $chapters as $chapter ) {
2023-08-18 13:35:46 +02:00
$comment_count += $chapter -> comment_count ;
}
}
}
2023-08-05 18:31:39 +02:00
$old_data [ 'comment_count' ] = $comment_count ;
2023-08-07 14:21:45 +02:00
$old_data [ 'comment_count_timestamp' ] = time ();
2024-01-26 23:30:58 +01:00
// Update meta cache and purge
update_post_meta ( $story_id , 'fictioneer_story_data_collection' , $old_data );
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
return $old_data ;
}
// Setup
2024-01-26 15:00:53 +01:00
$chapters = fictioneer_get_story_chapter_posts ( $story_id );
2023-01-21 01:31:34 +01:00
$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 ;
2023-01-21 01:31:34 +01:00
$chapter_ids = [];
// 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 ;
}
}
// Count chapters and words
2024-01-26 15:00:53 +01:00
if ( ! empty ( $chapters ) ) {
foreach ( $chapters as $chapter ) {
2023-08-05 18:28:35 +02:00
// This is about 50 times faster than using a meta query lol
2023-11-30 16:03:53 +01:00
if ( ! get_post_meta ( $chapter -> ID , 'fictioneer_chapter_hidden' , true ) ) {
2023-08-05 18:28:35 +02:00
// Do not count non-chapters...
2023-11-30 16:03:53 +01:00
if ( ! get_post_meta ( $chapter -> ID , 'fictioneer_chapter_no_chapter' , true ) ) {
2023-01-21 01:31:34 +01:00
$chapter_count += 1 ;
2023-11-30 17:10:19 +01:00
$word_count += fictioneer_get_word_count ( $chapter -> ID );
2023-01-21 01:31:34 +01:00
}
2023-08-05 18:28:35 +02:00
// ... but they are still listed!
$chapter_ids [] = $chapter -> ID ;
2023-01-21 01:31:34 +01:00
}
2023-08-18 13:35:46 +02:00
// Count ALL comments
$comment_count += $chapter -> comment_count ;
2023-01-21 01:31:34 +01:00
}
}
2023-11-14 17:13:23 +01:00
// Add story word count
2023-11-30 17:10:19 +01:00
$word_count += fictioneer_get_word_count ( $story_id );
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 ,
'word_count' => $word_count ,
'word_count_short' => fictioneer_shorten_number ( $word_count ),
'status' => $status ,
'icon' => $icon ,
'has_taxonomies' => $fandoms || $characters || $genres ,
'tags' => $tags ,
'characters' => $characters ,
'fandoms' => $fandoms ,
'warnings' => $warnings ,
'genres' => $genres ,
'title' => fictioneer_get_safe_title ( $story_id ),
2023-11-30 16:03:53 +01:00
'rating' => get_post_meta ( $story_id , 'fictioneer_story_rating' , true ),
'rating_letter' => get_post_meta ( $story_id , 'fictioneer_story_rating' , true )[ 0 ],
2023-01-21 01:31:34 +01:00
'chapter_ids' => $chapter_ids ,
'last_modified' => get_the_modified_time ( 'U' , $story_id ),
2023-08-07 14:21:45 +02:00
'comment_count' => $comment_count ,
'comment_count_timestamp' => time ()
2023-01-21 01:31:34 +01:00
);
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
return $result ;
}
}
// =============================================================================
// 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-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-27 00:49:01 +01:00
// Cache?
2023-01-21 01:31:34 +01:00
$old_data = $author -> fictioneer_author_statistics ;
2024-01-27 00:49:01 +01:00
if ( $old_data && ( $old_data [ 'valid_until' ] ? ? 0 ) > time () ) {
2023-01-21 01:31:34 +01:00
return $old_data ;
}
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 ;
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
);
2023-11-12 15:09:03 +01:00
// Remember results
2023-09-18 18:06:46 +02:00
fictioneer_update_user_meta ( $author_id , 'fictioneer_author_statistics' , $result );
2023-01-21 01:31:34 +01:00
2023-11-12 15:09:03 +01:00
// Return results
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-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-01-26 23:01:20 +01:00
// Cache?
$cache = get_post_meta ( $collection_id , 'fictioneer_collection_statistics_cache' , true );
if ( ! empty ( $cache ) && ( $cache [ 'valid_until' ] ? ? 0 ) > time () ) {
return $cache ;
}
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
);
}
// Process featured items...
foreach ( $featured as $post_id ) {
$post_type = get_post_type ( $post_id );
// Only look at stories and chapters...
if ( $post_type == 'fcn_chapter' ) {
// ... single chapters need to be looked up separately
$query_chapter_ids [] = $post_id ;
} elseif ( $post_type == 'fcn_story' ) {
// ... stories have pre-processed data
$story = fictioneer_get_story_data ( $post_id , false ); // Does not refresh comment count!
$found_chapter_ids = array_merge ( $found_chapter_ids , $story [ 'chapter_ids' ] ); // Excluding hidden chapters
$word_count += $story [ 'word_count' ]; // Excluding non-chapters
$chapter_count += $story [ 'chapter_count' ]; // Excluding non-chapters
$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 );
// Query lone chapters not belong to featured stories...
$chapter_query_args = array (
'post_type' => 'fcn_chapter' ,
'post_status' => 'publish' ,
'post__in' => fictioneer_rescue_array_zero ( $query_chapter_ids ),
'posts_per_page' => - 1 ,
2024-01-26 15:42:06 +01:00
'update_post_term_cache' => false , // Improve performance
'no_found_rows' => true // Improve performance
2024-01-26 14:40:56 +01:00
);
$chapters = new WP_Query ( $chapter_query_args );
// Add found chapters to statistics...
foreach ( $chapters -> posts as $chapter ) {
$comment_count += $chapter -> comment_count ;
// This is about 50 times faster than using a meta query
if (
! get_post_meta ( $chapter -> ID , 'fictioneer_chapter_hidden' , true ) &&
! get_post_meta ( $chapter -> ID , 'fictioneer_chapter_no_chapter' , true )
) {
$chapter_count += 1 ;
$word_count += fictioneer_get_word_count ( $chapter -> ID );
}
}
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
// Set cache
update_post_meta ( $collection_id , 'fictioneer_collection_statistics_cache' , $statistics );
// Return result
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 ;
}
}
// =============================================================================
// 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
$check = user_can ( $user_id , 'publish_posts' );
// 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-01-26 00:24:31 +01:00
if ( ! function_exists ( 'fictioneer_get_story_chapter_ids' ) ) {
2023-11-30 16:21:16 +01:00
/**
2024-01-26 00:24:31 +01:00
* Wrapper for get_post_meta () to get story chapter IDs
2023-11-30 16:21:16 +01:00
*
2024-01-26 17:45:59 +01:00
* @ since 5.8 . 2
2023-11-30 16:21:16 +01:00
*
* @ param int $post_id Optional . The ID of the post the field belongs to .
* Defaults to current post ID .
*
2024-01-26 00:24:31 +01:00
* @ return array Array of chapter post IDs or an empty array .
2023-11-30 16:21:16 +01:00
*/
2024-01-26 00:24:31 +01:00
function fictioneer_get_story_chapter_ids ( $post_id = null ) {
2023-11-30 16:21:16 +01:00
// 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 : [];
}
}
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
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 ) {
// Setup
$words = get_post_meta ( $post_id ? ? get_the_ID (), '_word_count' , true );
$words = ( int ) $words ;
// Always return an integer greater or equal 0
return $words > 0 ? $words : 0 ;
}
}
2023-01-21 01:31:34 +01:00
if ( ! function_exists ( 'fictioneer_get_content_field' ) ) {
/**
2023-11-30 16:03:53 +01:00
* Wrapper for get_post_meta () with content filers 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)
$content = wptexturize ( $content );
$content = convert_chars ( $content );
$content = wpautop ( $content );
$content = shortcode_unautop ( $content );
$content = prepend_attachment ( $content );
// 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
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 Font Awesome class .
*/
function fictioneer_get_icon_field ( $field , $post_id = null ) {
// Setup
2023-11-30 16:03:53 +01:00
$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 ) ) {
return 'fa-solid fa-book' ;
}
if ( $icon_object && ( ! property_exists ( $icon_object , 'style' ) || ! property_exists ( $icon_object , 'id' ) ) ) {
2023-01-21 01:31:34 +01:00
return 'fa-solid fa-book' ;
}
// 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 () {
2023-10-04 03:04:45 +02:00
$allowed = [];
2023-10-03 23:42:37 +02:00
$allowed = apply_filters ( 'fictioneer_filter_falsy_meta_allow_list' , $allowed );
return $allowed ;
}
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 .
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-01-10 13:27:16 +01:00
// Abort if older than n (30) seconds
$publishing_time = get_post_time ( 'U' , true , $post_id );
2023-12-31 04:03:06 +01:00
$current_time = current_time ( 'timestamp' , true );
2024-01-10 13:27:16 +01:00
if ( $current_time - $publishing_time > FICTIONEER_OLD_POST_THRESHOLD && ! $force ) {
2023-12-31 04:03:06 +01:00
return ;
}
// Setup
$story = get_post ( $story_id );
// Abort if story not found
if ( ! $story || ! $story_id ) {
return ;
}
// Setup, continued
$chapter_author_id = get_post_field ( 'post_author' , $post_id );
$story_author_id = get_post_field ( 'post_author' , $story_id );
// Abort if the author IDs do not match
if ( $chapter_author_id != $story_author_id && ! $force ) {
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 );
// Remember when chapter list has been last updated
update_post_meta ( $story_id , 'fictioneer_chapters_modified' , current_time ( 'mysql' ) );
update_post_meta ( $story_id , 'fictioneer_chapters_added' , current_time ( 'mysql' ) );
// Log changes
fictioneer_log_story_chapter_changes ( $story_id , $story_chapters , $previous_chapters );
// Clear story data cache to ensure it gets refreshed
delete_post_meta ( $story_id , 'fictioneer_story_data_collection' );
} 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
// =============================================================================
/**
2023-08-28 10:43:04 +02: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 ;
}
// =============================================================================
// 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-16 02:04:02 +02:00
// =============================================================================
// SANITIZE ARGUMENTS
// =============================================================================
/**
* Sanitizes an array of arguments
*
* @ since 5.7 . 3
*
2023-09-29 19:41:28 +02:00
* @ param array $args Array of arguments to be sanitized .
2023-09-16 02:04:02 +02:00
*
* @ return array The sanitized arguments .
*/
function fictioneer_sanitize_args ( $args ) {
$sanitized_args = [];
foreach ( $args as $key => $value ) {
if ( is_string ( $value ) ) {
$sanitized_args [ $key ] = sanitize_text_field ( $value );
} elseif ( is_numeric ( $value ) ) {
$sanitized_args [ $key ] = intval ( $value );
} elseif ( is_bool ( $value ) ) {
$sanitized_args [ $key ] = filter_var ( $value , FILTER_VALIDATE_BOOLEAN );
} elseif ( is_array ( $value ) ) {
$sanitized_args [ $key ] = fictioneer_sanitize_args ( $value );
} else {
$sanitized_args [ $key ] = $value ;
}
}
return $sanitized_args ;
}
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 ;
}
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
}
// =============================================================================
2023-08-27 12:29:24 +02:00
// AJAX AUTHENTICATION
2023-01-21 01:31:34 +01:00
// =============================================================================
/**
2023-08-27 12:29:24 +02:00
* Send user authentication status via AJAX
2023-01-21 01:31:34 +01:00
*
2023-08-27 12:29:24 +02:00
* @ since 5.7 . 0
2023-01-21 01:31:34 +01:00
*/
2023-08-27 12:29:24 +02:00
function fictioneer_ajax_get_auth () {
2023-08-21 13:25:25 +02:00
// Enabled?
if ( ! get_option ( 'fictioneer_enable_ajax_authentication' ) ) {
wp_send_json_error (
array ( 'error' => __ ( 'Not allowed.' , 'fictioneer' ) ),
403
);
}
2023-01-21 01:31:34 +01:00
// Setup
$user = wp_get_current_user ();
2023-08-27 12:29:24 +02:00
$nonce = wp_create_nonce ( 'fictioneer_nonce' );
$nonce_html = '<input id="fictioneer-ajax-nonce" name="fictioneer-ajax-nonce" type="hidden" value="' . $nonce . '">' ;
2023-01-21 01:31:34 +01:00
2023-08-27 12:29:24 +02:00
// Response
2023-01-21 01:31:34 +01:00
wp_send_json_success (
array (
'loggedIn' => is_user_logged_in (),
'isAdmin' => fictioneer_is_admin ( $user -> ID ),
'isModerator' => fictioneer_is_moderator ( $user -> ID ),
'isAuthor' => fictioneer_is_author ( $user -> ID ),
2023-08-27 12:29:24 +02:00
'isEditor' => fictioneer_is_editor ( $user -> ID ),
'nonce' => $nonce ,
'nonceHtml' => $nonce_html
2023-01-21 01:31:34 +01:00
)
);
}
2023-08-28 10:43:04 +02:00
2023-08-19 13:33:25 +02:00
if ( get_option ( 'fictioneer_enable_ajax_authentication' ) ) {
2023-08-27 12:29:24 +02:00
add_action ( 'wp_ajax_fictioneer_ajax_get_auth' , 'fictioneer_ajax_get_auth' );
add_action ( 'wp_ajax_nopriv_fictioneer_ajax_get_auth' , 'fictioneer_ajax_get_auth' );
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 ) {
// Define default translations
$strings = array (
'account' => __ ( 'Account' , 'fictioneer' ),
2023-08-04 09:57:48 +02:00
'bookshelf' => __ ( 'Bookshelf' , 'fictioneer' ),
2023-01-21 01:31:34 +01:00
'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' ),
2023-01-23 16:22:18 +01:00
'is_read_later' => _x ( 'Read later' , 'Story is marked to be read later.' , 'fictioneer' ),
2023-01-28 21:37:46 +01:00
'jump_to_comments' => __ ( 'Jump: Comments' , 'fictioneer' ),
'jump_to_bookmark' => __ ( 'Jump: Bookmark' , 'fictioneer' ),
2023-01-21 01:31:34 +01:00
'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' ),
2023-06-18 03:28:44 +02: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-click="logout">Log out?</a></span>' , 'Comment form logged-in note.' , 'fictioneer' ),
2023-01-21 01:31:34 +01: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' ),
);
// 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
$keep = 2 ;
$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' ) {
2023-11-30 16:03:53 +01:00
$story_id = get_post_meta ( $post_id , 'fictioneer_chapter_story' , true );
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
* @ see wp_check_comment_disallowed_list ()
*
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 ) ) {
continue ; }
// 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
2023-01-28 02:17:46 +01:00
$img_search = 'https:[^\"\'|;<>\[\]]+?\.(?:png|jpg|jpeg|gif|webp|svg|avif|tiff).*?' ;
2023-01-21 01:31:34 +01:00
$url_search = '(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?' ;
// 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' ,
" / \ [link.*] \ [img]( $img_search ) \ [ \ /img] \ [ \ /link]/i " ,
" / \ [img]( $img_search ) \ [ \ /img]/i " ,
" / \ [link \ ]( $url_search ) \ [ \ /link \ ]/ " ,
" ( \ [link \ =[ \" ']?( $url_search )[ \" ']? \ ](.+?) \ [/link \ ]) " ,
'/\[anchor]([^\"\'|;<>\[\]]+?)\[\/anchor]/i'
);
// 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> " ,
2023-06-17 10:49:18 +02:00
'<a href="#$1" data-block="center" 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
*
* @ since 5.1 . 1
*
* @ return array Font items ( css , name , and alt ) .
*/
function fictioneer_get_fonts () {
2023-08-25 21:56:36 +02:00
// Make sure 'Open Sans' is always in first or second place
if ( FICTIONEER_PRIMARY_FONT_CSS !== 'Open Sans' ) {
$fonts = array (
[ 'css' => FICTIONEER_PRIMARY_FONT_CSS , 'name' => FICTIONEER_PRIMARY_FONT_NAME ],
[ 'css' => 'Open Sans' , 'name' => _x ( 'Open Sans' , 'Font name.' , 'fictioneer' )]
);
} else {
$fonts = array (
[ 'css' => FICTIONEER_PRIMARY_FONT_CSS , 'name' => FICTIONEER_PRIMARY_FONT_NAME ]
);
}
2023-02-23 17:03:41 +01:00
// Setup default fonts
2023-08-25 21:56:36 +02:00
$fonts = array_merge (
$fonts ,
array (
[ 'css' => '' , 'name' => _x ( 'System Font' , 'Font name.' , 'fictioneer' )],
[ 'css' => 'Lato' , 'name' => _x ( 'Lato' , 'Font name.' , 'fictioneer' )],
[ 'css' => 'Helvetica Neue' , 'name' => _x ( 'Helvetica Neue' , 'Font name.' , 'fictioneer' ), 'alt' => 'Arial' ],
[ 'css' => 'Georgia' , 'name' => _x ( 'Georgia' , 'Font name.' , 'fictioneer' )],
[ 'css' => 'Roboto Mono' , 'name' => _x ( 'Roboto Mono' , 'Font name.' , 'fictioneer' )],
[ 'css' => 'Roboto Serif' , 'name' => _x ( 'Roboto Serif' , 'Font name.' , 'fictioneer' )],
[ 'css' => 'Cormorant Garamond' , 'name' => _x ( 'Cormorant Garamond' , 'Font name.' , 'fictioneer' ), 'alt' => 'Garamond' ],
[ 'css' => 'Crimson Text' , 'name' => _x ( 'Crimson Text' , 'Font name.' , 'fictioneer' )],
[ 'css' => 'OpenDyslexic' , 'name' => _x ( 'Open Dyslexic' , 'Font name.' , 'fictioneer' )]
)
2023-02-23 17:03:41 +01:00
);
// Apply filters and return
return apply_filters ( 'fictioneer_filter_fonts' , $fonts );
}
}
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 (
[ 'css' => 'var(--fg-tinted)' , 'name' => _x ( 'Tinted' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-500)' , 'name' => _x ( 'Baseline' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-600)' , 'name' => _x ( 'Low' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-700)' , 'name' => _x ( 'Lower' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-800)' , 'name' => _x ( 'Lowest' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-400)' , 'name' => _x ( 'High' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-300)' , 'name' => _x ( 'Higher' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => 'var(--fg-200)' , 'name' => _x ( 'Highest' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => '#fff' , 'name' => _x ( 'White' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => '#999' , 'name' => _x ( 'Gray' , 'Chapter font color name.' , 'fictioneer' )],
[ 'css' => '#000' , 'name' => _x ( 'Black' , 'Chapter font color name.' , 'fictioneer' )]
);
// 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
// =============================================================================
2023-08-08 20:33:24 +02:00
// BUILD FRONTEND PROFILE 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 ) {
ob_start ();
// Start HTML ---> ?>
< div class = " notice _<?php echo esc_attr( $type ); ?> " >
< ? php if ( $type === 'warning' ) : ?>
< i class = " fa-solid fa-triangle-exclamation " ></ i >
< ? php endif ; ?>
< div >< ? php echo $message ; ?> </div>
</ div >
< ? php // <--- End HTML
$output = ob_get_clean ();
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 ) );
}
}
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
*
* @ 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
* trigger hooked actions ( if any ) . Default true .
*
* @ return int Count of deleted Transients .
*/
function fictioneer_delete_transients_like ( $partial_key , $fast = true ) {
// 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
$count = $wpdb -> query ( $sql ) / 2 ; // Count Transient and Transient timeout as one
} 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 ) {
if (
2023-08-18 10:17:18 +02:00
( defined ( 'REST_REQUEST' ) && REST_REQUEST ) ||
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' ) ) {
/**
* Return the total word count of all published stories
*
2024-01-26 17:45:59 +01:00
* @ since 4.0 . 0
2023-08-08 20:33:24 +02:00
* @ see fictioneer_get_story_data ()
*/
function fictioneer_get_stories_total_word_count () {
// Look for cached value
$cached_word_count = get_transient ( 'fictioneer_stories_total_word_count' );
// Return cached value if found
2023-08-19 13:33:25 +02:00
if ( $cached_word_count ) {
return $cached_word_count ;
}
2023-08-08 20:33:24 +02:00
// Setup
2024-01-27 21:00:00 +01:00
$word_count = 0 ;
2023-08-08 20:33:24 +02:00
// Query all stories
$stories = get_posts (
array (
'numberposts' => - 1 ,
'post_type' => 'fcn_story' ,
'post_status' => 'publish' ,
2023-11-15 14:40:28 +01:00
'update_post_meta_cache' => true ,
'update_post_term_cache' => false , // Improve performance
2023-11-14 10:07:15 +01:00
'no_found_rows' => true , // Improve performance
2023-08-08 20:33:24 +02:00
)
);
// Sum of all word counts
foreach ( $stories as $story ) {
$story_data = fictioneer_get_story_data ( $story -> ID , false ); // Does not refresh comment count!
2024-01-27 21:00:00 +01:00
$word_count += $story_data [ 'word_count' ];
}
2023-08-08 20:33:24 +02:00
2023-11-30 17:12:48 +01:00
// Cache for next time
2023-08-08 20:33:24 +02:00
set_transient ( 'fictioneer_stories_total_word_count' , $word_count );
// Return newly calculated value
2024-01-27 21:00:00 +01:00
return $word_count ;
2023-08-08 20:33:24 +02:00
}
}
2023-08-13 18:03:17 +02:00
// =============================================================================
// USER SELF-DELETE
// =============================================================================
/**
* Deletes the current user ' s account and anonymized their comments
*
2024-01-26 17:45:59 +01:00
* @ since 5.6 . 0
2023-08-13 18:03:17 +02:00
*
* @ return bool True if the user was successfully deleted , false otherwise .
*/
function fictioneer_delete_my_account () {
// Setup
$current_user = wp_get_current_user ();
// Guard
if (
! $current_user ||
$current_user -> ID === 1 ||
in_array ( 'administrator' , $current_user -> roles ) ||
! current_user_can ( 'fcn_allow_self_delete' )
) {
return false ;
}
// Update comments
$comments = get_comments ( array ( 'user_id' => $current_user -> ID ) );
foreach ( $comments as $comment ) {
wp_update_comment (
array (
'comment_ID' => $comment -> comment_ID ,
'user_ID' => 0 ,
'comment_author' => fcntr ( 'deleted_user' ),
'comment_author_email' => '' ,
'comment_author_IP' => '' ,
'comment_agent' => '' ,
'comment_author_url' => ''
)
);
// Make absolutely sure no comment subscriptions remain
2023-09-18 14:49:36 +02:00
fictioneer_update_comment_meta ( $comment -> comment_ID , 'fictioneer_send_notifications' , false );
2023-08-13 18:03:17 +02:00
// Retain some (redacted) data in case there was a mistake
add_comment_meta ( $comment -> comment_ID , 'fictioneer_deleted_user_id' , $current_user -> ID );
add_comment_meta ( $comment -> comment_ID , 'fictioneer_deleted_username_substr' , substr ( $current_user -> user_login , 0 , 3 ) );
add_comment_meta ( $comment -> comment_ID , 'fictioneer_deleted_email_substr' , substr ( $comment -> comment_author_email , 0 , 3 ) );
}
// Delete user
if ( wp_delete_user ( $current_user -> ID ) ) {
return true ;
}
// Failure
return false ;
}
2023-08-17 01:10:00 +02:00
// =============================================================================
2023-08-21 00:57:56 +02:00
// RESCUE EMPTY ARRAY AS [0]
2023-08-17 01:10:00 +02:00
// =============================================================================
/**
2023-08-17 03:20:27 +02:00
* Returns the given array or an array with a single 0 if empty
2023-08-17 01:10:00 +02:00
*
* Prevents an array from being empty , which is useful for 'post__in' query
* arguments that cannot deal with empty arrays .
*
2024-01-26 17:45:59 +01:00
* @ since 5.6 . 0
2023-08-17 01:10:00 +02:00
*
* @ param array $array The input array to check .
*
* @ return array The original array or [ 0 ] .
*/
2023-08-21 00:57:56 +02:00
function fictioneer_rescue_array_zero ( $array ) {
2023-08-17 01:10:00 +02:00
return empty ( $array ) ? [ 0 ] : $array ;
}
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 );
// Always let owner to pass
if ( get_current_user_id () === absint ( $post -> post_author ) ) {
return true ;
}
// Always let administrators pass
if ( current_user_can ( 'manage_options' ) ) {
return true ;
}
// Check capability for post type
if ( $post -> post_status === 'private' ) {
switch ( $post -> post_type ) {
case 'post' :
return current_user_can ( 'edit_private_posts' );
break ;
case 'page' :
return current_user_can ( 'edit_private_pages' );
break ;
case 'fcn_chapter' :
return current_user_can ( 'read_private_fcn_chapters' );
break ;
case 'fcn_story' :
return current_user_can ( 'read_private_fcn_stories' );
break ;
case 'fcn_recommendation' :
return current_user_can ( 'read_private_fcn_recommendations' );
break ;
case 'fcn_collection' :
return current_user_can ( 'read_private_fcn_collections' );
break ;
default :
return 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
2023-08-17 19:27:54 +02:00
return false ;
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 ] ),
'url' => sanitize_url ( $tuple [ 1 ] )
);
}
// 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
if ( ! function_exists ( 'mb_strimwidth' ) ) {
return mb_strimwidth ( $string , 0 , $length , $ellipsis );
} else {
return strlen ( $string ) > $length ? substr ( $string , 0 , $length ) . $ellipsis : $string ;
}
}
2023-01-21 01:31:34 +01:00
?>