posts ) && function_exists( 'update_post_author_caches' ) ) { update_post_author_caches( $result->posts ); } return $result; } } // ============================================================================= // SHORTCODE-BASED RELATIONSHIPS // ============================================================================= /** * Register relationships for posts with certain shortcodes * * @since 5.0.0 * * @param int $post_id The ID of the saved post. * @param WP_Post $post The saved post object. */ function fictioneer_update_shortcode_relationships( $post_id, $post ) { // Prevent multi-fire if ( fictioneer_multi_save_guard( $post_id ) ) { return; } // Setup $registry = fictioneer_get_relationship_registry(); // Look for blog shortcode if ( str_contains( $post->post_content, 'fictioneer_blog' ) ) { $registry['always'][ $post_id ] = 'shortcode'; } else { unset( $registry['always'][ $post_id ] ); } // Look for story data shortcode if ( str_contains( $post->post_content, 'fictioneer_story_data' ) || str_contains( $post->post_content, 'fictioneer_story_comments' ) || str_contains( $post->post_content, 'fictioneer_story_actions' ) || str_contains( $post->post_content, 'fictioneer_story_section' ) || str_contains( $post->post_content, 'fictioneer_subscribe_button' ) ) { $registry['always'][ $post_id ] = 'shortcode'; } else { unset( $registry['always'][ $post_id ] ); } // Look for article cards shortcode if ( str_contains( $post->post_content, 'fictioneer_article_cards' ) ) { $registry['always'][ $post_id ] = 'shortcode'; } else { unset( $registry['always'][ $post_id ] ); } // Look for showcase shortcode if ( str_contains( $post->post_content, 'fictioneer_showcase' ) ) { $registry['always'][ $post_id ] = 'shortcode'; } else { unset( $registry['always'][ $post_id ] ); } // Look for post-related shortcode if ( str_contains( $post->post_content, 'fictioneer_latest_posts' ) ) { $registry['ref_posts'][ $post_id ] = 'shortcode'; } else { unset( $registry['ref_posts'][ $post_id ] ); } // Look for chapter-related shortcodes if ( str_contains( $post->post_content, 'fictioneer_chapter_list' ) || str_contains( $post->post_content, 'fictioneer_latest_chapters' ) ) { $registry['ref_chapters'][ $post_id ] = 'shortcode'; } else { unset( $registry['ref_chapters'][ $post_id ] ); } // Look for story-related shortcodes if ( str_contains( $post->post_content, 'fictioneer_latest_stories' ) || str_contains( $post->post_content, 'fictioneer_latest_updates' ) ) { $registry['ref_stories'][ $post_id ] = 'shortcode'; } else { unset( $registry['ref_stories'][ $post_id ] ); } // Look for recommendation-related shortcodes if ( str_contains( $post->post_content, 'fictioneer_latest_recommendations' ) ) { $registry['ref_recommendations'][ $post_id ] = 'shortcode'; } else { unset( $registry['ref_recommendations'][ $post_id ] ); } // Update database fictioneer_save_relationship_registry( $registry ); } if ( FICTIONEER_RELATIONSHIP_PURGE_ASSIST ) { add_action( 'save_post', 'fictioneer_update_shortcode_relationships', 10, 2 ); } // ============================================================================= // GET SHORTCODE DEFAULT ARGS // ============================================================================= /** * Returns sanitized arguments extracted from shortcode attributes * * @since 5.7.3 * * @param array $attr Attributes passed to the shortcode. * @param int $def_count Default for the 'count' argument. * * @return array The extracted arguments. */ function fictioneer_get_default_shortcode_args( $attr, $def_count = -1 ) { //--- Sanitize attributes ---------------------------------------------------- $attr = is_array( $attr ) ? array_map( 'sanitize_text_field', $attr ) : sanitize_text_field( $attr ); //--- Extract arguments ------------------------------------------------------ $seamless_default = get_theme_mod( 'card_image_style', 'default' ) === 'seamless'; $thumbnail_default = get_theme_mod( 'card_image_style', 'default' ) !== 'none'; $uid = wp_unique_id( 'shortcode-id-' ); $args = array( 'uid' => $uid, 'type' => $attr['type'] ?? 'default', 'count' => max( -1, intval( $attr['count'] ?? $def_count ) ), 'offset' => max( 0, intval( $attr['offset'] ?? 0 ) ), 'order' => $attr['order'] ?? '', 'orderby' => $attr['orderby'] ?? '', 'page' => max( 1, get_query_var( 'page' ) ?: get_query_var( 'paged' ) ), 'posts_per_page' => absint( $attr['per_page'] ?? 0 ) ?: get_option( 'posts_per_page' ), 'post_ids' => fictioneer_explode_list( $attr['post_ids'] ?? '' ), 'author' => sanitize_title( $attr['author'] ?? '' ), 'author_ids' => fictioneer_explode_list( $attr['author_ids'] ?? '' ), 'excluded_authors' => fictioneer_explode_list( $attr['exclude_author_ids'] ?? '' ), 'excluded_tags' => fictioneer_explode_list( $attr['exclude_tag_ids'] ?? '' ), 'excluded_cats' => fictioneer_explode_list( $attr['exclude_cat_ids'] ?? '' ), 'taxonomies' => fictioneer_get_shortcode_taxonomies( $attr ), 'relation' => strtolower( $attr['rel'] ?? 'and' ) === 'or' ? 'OR' : 'AND', 'ignore_sticky' => filter_var( $attr['ignore_sticky'] ?? 0, FILTER_VALIDATE_BOOLEAN ), 'ignore_protected' => filter_var( $attr['ignore_protected'] ?? 0, FILTER_VALIDATE_BOOLEAN ), 'only_protected' => filter_var( $attr['only_protected'] ?? 0, FILTER_VALIDATE_BOOLEAN ), 'vertical' => filter_var( $attr['vertical'] ?? 0, FILTER_VALIDATE_BOOLEAN ), 'seamless' => filter_var( $attr['seamless'] ?? $seamless_default, FILTER_VALIDATE_BOOLEAN ), 'aspect_ratio' => sanitize_css_aspect_ratio( $attr['aspect_ratio'] ?? '' ), 'thumbnail' => filter_var( $attr['thumbnail'] ?? $thumbnail_default, FILTER_VALIDATE_BOOLEAN ), 'lightbox' => filter_var( $attr['lightbox'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'words' => filter_var( $attr['words'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'date' => filter_var( $attr['date'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'date_format' => sanitize_text_field( $attr['date_format'] ?? '' ), 'nested_date_format' => sanitize_text_field( $attr['nested_date_format'] ?? '' ), 'footer' => filter_var( $attr['footer'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_author' => filter_var( $attr['footer_author'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_chapters' => filter_var( $attr['footer_chapters'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_words' => filter_var( $attr['footer_words'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_date' => filter_var( $attr['footer_date'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_comments' => filter_var( $attr['footer_comments'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_status' => filter_var( $attr['footer_status'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'footer_rating' => filter_var( $attr['footer_rating'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'classes' => esc_attr( wp_strip_all_tags( $attr['classes'] ?? $attr['class'] ?? '' ) ) . " {$uid}", 'infobox' => filter_var( $attr['infobox'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'source' => filter_var( $attr['source'] ?? 1, FILTER_VALIDATE_BOOLEAN ), 'splide' => sanitize_text_field( $attr['splide'] ?? '' ), 'cache' => filter_var( $attr['cache'] ?? 1, FILTER_VALIDATE_BOOLEAN ) ); //--- Fixes ------------------------------------------------------------------ // Update count if limited to post IDs if ( ! empty( $args['post_ids'] ) ) { $args['count'] = count( $args['post_ids'] ); } // Prepare Splide JSON if ( ! empty( $args['splide'] ) ) { $args['splide'] = str_replace( "'", '"', $args['splide'] ); if ( ! fictioneer_is_valid_json( $args['splide'] ) ) { $args['splide'] = false; } else { $splide = json_decode( $args['splide'], true ); // Turn arrows off by default if ( ! preg_match( '/"arrows"\s*:\s*true/', $args['splide'] ) ) { $splide['arrows'] = false; } // Change default arrow SVG path if ( ! isset( $splide['arrowPath'] ) ) { $splide['arrowPath'] = 'M31.89 18.24c0.98 0.98 0.98 2.56 0 3.54l-15 15c-0.98 0.98-2.56 0.98-3.54 0s-0.98-2.56 0-3.54L26.45 20 13.23 6.76c-0.98-0.98-0.98-2.56 0-3.54s2.56-0.98 3.54 0l15 15'; } $args['splide'] = json_encode( $splide ); } } //--- Finish ----------------------------------------------------------------- return $args; } // ============================================================================= // GET SHORTCODE TAXONOMIES // ============================================================================= /** * Extract taxonomies from shortcode attributes * * @since 5.2.0 * * @param array $attr Attributes of the shortcode. * * @return array Array of found taxonomies. */ function fictioneer_get_shortcode_taxonomies( $attr ) { // Setup $taxonomies = []; // Tags if ( ! empty( $attr['tags'] ) ) { $taxonomies['tags'] = fictioneer_explode_list( $attr['tags'] ); } // Categories if ( ! empty( $attr['categories'] ) ) { $taxonomies['categories'] = fictioneer_explode_list( $attr['categories'] ); } // Fandoms if ( ! empty( $attr['fandoms'] ) ) { $taxonomies['fandoms'] = fictioneer_explode_list( $attr['fandoms'] ); } // Characters if ( ! empty( $attr['characters'] ) ) { $taxonomies['characters'] = fictioneer_explode_list( $attr['characters'] ); } // Genres if ( ! empty( $attr['genres'] ) ) { $taxonomies['genres'] = fictioneer_explode_list( $attr['genres'] ); } // Return return $taxonomies; } // ============================================================================= // GET SHORTCODE TAX QUERY // ============================================================================= /** * Get shortcode Tax Query * * @since 5.2.0 * * @param array $args Arguments of the shortcode partial. * * @return array Tax Query. */ function fictioneer_get_shortcode_tax_query( $args ) { // Setup $tax_query = []; // Are there taxonomies? if ( ! empty( $args['taxonomies'] ) ) { // Relationship? if ( count( $args['taxonomies'] ) > 1 ) { $tax_query['relation'] = $args['relation']; } // Tags? if ( ! empty( $args['taxonomies']['tags'] ) ) { $tax_query[] = array( 'taxonomy' => 'post_tag', 'field' => 'name', 'terms' => $args['taxonomies']['tags'] ); } // Categories? if ( ! empty( $args['taxonomies']['categories'] ) ) { $tax_query[] = array( 'taxonomy' => 'category', 'field' => 'name', 'terms' => $args['taxonomies']['categories'] ); } // Fandoms? if ( ! empty( $args['taxonomies']['fandoms'] ) ) { $tax_query[] = array( 'taxonomy' => 'fcn_fandom', 'field' => 'name', 'terms' => $args['taxonomies']['fandoms'] ); } // Characters? if ( ! empty( $args['taxonomies']['characters'] ) ) { $tax_query[] = array( 'taxonomy' => 'fcn_character', 'field' => 'name', 'terms' => $args['taxonomies']['characters'] ); } // Genres? if ( ! empty( $args['taxonomies']['genres'] ) ) { $tax_query[] = array( 'taxonomy' => 'fcn_genre', 'field' => 'name', 'terms' => $args['taxonomies']['genres'] ); } } // Return return $tax_query; } // ============================================================================= // SPLIDE // ============================================================================= /** * Returns inline script to initialize Splide ASAP * * Note: The script tag is only returned once in case multiple sliders * are active since only one is needed. * * @since 5.25.0 * @since 5.26.1 - Use wp_print_inline_script_tag(). * * @return string The inline script. */ function fictioneer_get_splide_inline_init() { static $done = null; if ( $done ) { return ''; } $done = true; return wp_get_inline_script_tag( 'document.addEventListener("DOMContentLoaded",()=>{document.querySelectorAll(".splide:not(.no-auto-splide, .is-initialized)").forEach(e=>{e.querySelector(".splide__list")&&"undefined"!=typeof Splide&&(e.classList.remove("_splide-placeholder"),new Splide(e).mount())})});', array( 'id' => 'fictioneer-iife-splide', 'class' => 'temp-script', 'type' => 'text/javascript', 'data-jetpack-boost' => 'ignore', 'data-no-optimize' => '1', 'data-no-defer' => '1', 'data-no-minify' => '1' ) ); } // ============================================================================= // SHOWCASE SHORTCODE // ============================================================================= /** * Shortcode to display showcase * * @since 5.0.0 * * @param string $attr['for'] What the showcase is for. Allowed are chapters, * collections, recommendations, and stories. * @param string|null $attr['count'] Optional. Maximum number of items. Default 8. * @param string|null $attr['author'] Optional. Limit posts to a specific author. * @param string|null $attr['order'] Optional. Order direction. Default 'DESC'. * @param string|null $attr['orderby'] Optional. Order argument. Default 'date'. * @param string|null $attr['post_ids'] Optional. Limit posts to specific post IDs. * @param string|null $attr['ignore_protected'] Optional. Whether to ignore protected posts. Default false. * @param string|null $attr['only_protected'] Optional. Whether to query only protected posts. Default false. * @param string|null $attr['author_ids'] Optional. Only include posts by these author IDs. * @param string|null $attr['exclude_author_ids'] Optional. Exclude posts with these author IDs. * @param string|null $attr['exclude_tag_ids'] Optional. Exclude posts with these tags. * @param string|null $attr['exclude_cat_ids'] Optional. Exclude posts with these categories. * @param string|null $attr['categories'] Optional. Limit posts to specific category names. * @param string|null $attr['tags'] Optional. Limit posts to specific tag names. * @param string|null $attr['fandoms'] Optional. Limit posts to specific fandom names. * @param string|null $attr['genres'] Optional. Limit posts to specific genre names. * @param string|null $attr['characters'] Optional. Limit posts to specific character names. * @param string|null $attr['rel'] Optional. Relationship between taxonomies. Default 'AND'. * @param string|null $attr['vertical'] Optional. Whether to show the vertical variant. * @param string|null $attr['seamless'] Optional. Whether to render the image seamless. Default false (Customizer). * @param string|null $attr['aspect_ratio'] Optional. Aspect ratio of the item. Default empty. * @param string|null $attr['height'] Optional. Override the item height. Default empty. * @param string|null $attr['min_width'] Optional. Override the item minimum width. Default empty. * @param string|null $attr['lightbox'] Optional. Whether the thumbnail is opened in the lightbox. Default true. * @param string|null $attr['thumbnail'] Optional. Whether to show the thumbnail. Default true (Customizer). * @param string|null $attr['class'] Optional. Additional CSS classes, separated by whitespace. * @param string|null $args['splide'] Optional. Configuration JSON for the Splide slider. Default empty. * @param string|null $args['quality'] Optional. Size of the images. Default 'medium'. * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_showcase( $attr ) { // Abort if... if ( empty( $attr['for'] ) ) { return ''; } // Defaults $args = fictioneer_get_default_shortcode_args( $attr, 8 ); // Height/Width/Quality $args['height'] = sanitize_text_field( $attr['height'] ?? '' ); $args['min_width'] = sanitize_text_field( $attr['min_width'] ?? '' ); $args['quality'] = sanitize_text_field( $attr['quality'] ?? 'medium' ); // Specifics $args['no_cap'] = filter_var( $attr['no_cap'] ?? 0, FILTER_VALIDATE_BOOLEAN ); switch ( $attr['for'] ) { case 'collections': $args['post_type'] = 'fcn_collection'; break; case 'chapters': $args['post_type'] = 'fcn_chapter'; break; case 'stories': $args['post_type'] = 'fcn_story'; break; case 'recommendations': $args['post_type'] = 'fcn_recommendation'; break; } // Abort if... if ( ! isset( $args['post_type'] ) ) { return ''; } // Extra classes if ( $args['splide'] ?? 0 ) { $args['classes'] .= ' splide _splide-placeholder'; } // Transient? $transient_enabled = fictioneer_enable_shortcode_transients( 'fictioneer_showcase' ); if ( $transient_enabled && $args['cache'] ) { $base = serialize( $args ) . serialize( $attr ); $type = $args['post_type']; $transient_key = "fictioneer_shortcode_showcase_{$type}_html_" . md5( $base ); $transient = get_transient( $transient_key ); if ( ! empty( $transient ) ) { return $transient; } } // Buffer ob_start(); get_template_part( 'partials/_showcase', null, $args ); $html = fictioneer_minify_html( ob_get_clean() ); if ( ( $args['splide'] ?? 0 ) && strpos( $args['classes'], 'no-auto-splide' ) === false ) { $html .= fictioneer_get_splide_inline_init(); } if ( $transient_enabled && $args['cache'] ) { set_transient( $transient_key, $html, FICTIONEER_SHORTCODE_TRANSIENT_EXPIRATION ); } // Return minified buffer return $html; } add_shortcode( 'fictioneer_showcase', 'fictioneer_shortcode_showcase' ); // ============================================================================= // LATEST CHAPTERS SHORTCODE // ============================================================================= /** * Shortcode to show latest chapters * * @since 3.0 * * @param string|null $attr['count'] Optional. Maximum number of items. Default 4. * @param string|null $attr['author'] Optional. Limit posts to a specific author. * @param string|null $attr['type'] Optional. Choose between 'default', 'simple', and 'compact'. * @param string|null $attr['order'] Optional. Order argument. Default 'DESC'. * @param string|null $attr['orderby'] Optional. Orderby argument. Default 'date'. * @param string|null $attr['spoiler'] Optional. Whether to show spoiler content. * @param string|null $attr['source'] Optional. Whether to show the author and story. * @param string|null $attr['post_ids'] Optional. Limit posts to specific post IDs. * @param string|null $attr['ignore_protected'] Optional. Whether to ignore protected posts. Default false. * @param string|null $attr['only_protected'] Optional. Whether to query only protected posts. Default false. * @param string|null $attr['author_ids'] Optional. Only include posts by these author IDs. * @param string|null $attr['exclude_author_ids'] Optional. Exclude posts with these author IDs. * @param string|null $attr['exclude_tag_ids'] Optional. Exclude posts with these tags. * @param string|null $attr['exclude_cat_ids'] Optional. Exclude posts with these categories. * @param string|null $attr['categories'] Optional. Limit posts to specific category names. * @param string|null $attr['tags'] Optional. Limit posts to specific tag names. * @param string|null $attr['fandoms'] Optional. Limit posts to specific fandom names. * @param string|null $attr['genres'] Optional. Limit posts to specific genre names. * @param string|null $attr['characters'] Optional. Limit posts to specific character names. * @param string|null $attr['rel'] Optional. Relationship between taxonomies. Default 'AND'. * @param string|null $attr['vertical'] Optional. Whether to show the vertical variant. * @param string|null $attr['seamless'] Optional. Whether to render the image seamless. Default false (Customizer). * @param string|null $attr['aspect_ratio'] Optional. Aspect ratio for the image. Only with vertical. * @param string|null $attr['lightbox'] Optional. Whether the thumbnail is opened in the lightbox. Default true. * @param string|null $attr['thumbnail'] Optional. Whether to show the thumbnail. Default true (Customizer). * @param string|null $attr['date_format'] Optional. String to override the date format. Default empty. * @param string|null $attr['footer'] Optional. Whether to show the footer (if any). Default true. * @param string|null $attr['footer_author'] Optional. Whether to show the chapter author. Default true. * @param string|null $attr['footer_date'] Optional. Whether to show the chapter date. Default true. * @param string|null $attr['footer_words'] Optional. Whether to show the chapter word count. Default true. * @param string|null $attr['footer_comments'] Optional. Whether to show the chapter comment count. Default true. * @param string|null $attr['footer_status'] Optional. Whether to show the chapter status. Default true. * @param string|null $attr['footer_rating'] Optional. Whether to show the story/chapter age rating. Default true. * @param string|null $attr['class'] Optional. Additional CSS classes, separated by whitespace. * @param string|null $args['splide'] Configuration JSON for the Splide slider. Default empty. * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_latest_chapters( $attr ) { // Defaults $args = fictioneer_get_default_shortcode_args( $attr, 4 ); // Specifics $args['simple'] = false; $args['spoiler'] = filter_var( $attr['spoiler'] ?? 0, FILTER_VALIDATE_BOOLEAN ); // Type $type = sanitize_text_field( $attr['type'] ?? 'default' ); // Extra classes if ( $args['splide'] ?? 0 ) { $args['classes'] .= ' splide _splide-placeholder'; } // Transient? $transient_enabled = fictioneer_enable_shortcode_transients( 'fictioneer_latest_chapters' ); if ( $transient_enabled && $args['cache'] ) { $base = serialize( $args ) . serialize( $attr ); $transient_key = "fictioneer_shortcode_latest_chapters_{$type}_html_" . md5( $base ); $transient = get_transient( $transient_key ); if ( ! empty( $transient ) ) { return $transient; } } // Buffer ob_start(); switch ( $type ) { case 'compact': get_template_part( 'partials/_latest-chapters-compact', null, $args ); break; case 'list': get_template_part( 'partials/_latest-chapters-list', null, $args ); break; default: $args['simple'] = $type == 'simple'; get_template_part( 'partials/_latest-chapters', null, $args ); } $html = fictioneer_minify_html( ob_get_clean() ); if ( ( $args['splide'] ?? 0 ) && strpos( $args['classes'], 'no-auto-splide' ) === false ) { $html .= fictioneer_get_splide_inline_init(); } if ( $transient_enabled && $args['cache'] ) { set_transient( $transient_key, $html, FICTIONEER_SHORTCODE_TRANSIENT_EXPIRATION ); } // Return minified buffer return $html; } add_shortcode( 'fictioneer_latest_chapters', 'fictioneer_shortcode_latest_chapters' ); // ============================================================================= // LATEST STORIES SHORTCODE // ============================================================================= /** * Shortcode to show latest stories * * @since 3.0 * * @param string|null $attr['count'] Optional. Maximum number of items. Default 4. * @param string|null $attr['author'] Optional. Limit posts to a specific author. * @param string|null $attr['type'] Optional. Choose between 'default' and 'compact'. * @param string|null $attr['order'] Optional. Order argument. Default 'DESC'. * @param string|null $attr['orderby'] Optional. Orderby argument. Default 'date'. * @param string|null $attr['post_ids'] Optional. Limit posts to specific post IDs. * @param string|null $attr['ignore_protected'] Optional. Whether to ignore protected posts. Default false. * @param string|null $attr['only_protected'] Optional. Whether to query only protected posts. Default false. * @param string|null $attr['author_ids'] Optional. Only include posts by these author IDs. * @param string|null $attr['exclude_author_ids'] Optional. Exclude posts with these author IDs. * @param string|null $attr['exclude_tag_ids'] Optional. Exclude posts with these tags. * @param string|null $attr['exclude_cat_ids'] Optional. Exclude posts with these categories. * @param string|null $attr['categories'] Optional. Limit posts to specific category names. * @param string|null $attr['tags'] Optional. Limit posts to specific tag names. * @param string|null $attr['fandoms'] Optional. Limit posts to specific fandom names. * @param string|null $attr['genres'] Optional. Limit posts to specific genre names. * @param string|null $attr['characters'] Optional. Limit posts to specific character names. * @param string|null $attr['rel'] Optional. Relationship between taxonomies. Default 'AND'. * @param string|null $attr['vertical'] Optional. Whether to show the vertical variant. * @param string|null $attr['seamless'] Optional. Whether to render the image seamless. Default false (Customizer). * @param string|null $attr['aspect_ratio'] Optional. Aspect ratio for the image. Only with vertical. * @param string|null $attr['lightbox'] Optional. Whether the thumbnail is opened in the lightbox. Default true. * @param string|null $attr['thumbnail'] Optional. Whether to show the thumbnail. Default true (Customizer). * @param string|null $attr['date_format'] Optional. String to override the date format. Default empty. * @param string|null $attr['terms'] Optional. Either 'inline', 'pills', 'none', or 'false' (only in list type). * Default 'inline'. * @param string|null $attr['max_terms'] Optional. Maximum number of shown taxonomies. Default 10. * @param string|null $attr['footer'] Optional. Whether to show the footer (if any). Default true. * @param string|null $attr['footer_author'] Optional. Whether to show the story author. Default true. * @param string|null $attr['footer_date'] Optional. Whether to show the story date. Default true. * @param string|null $attr['footer_words'] Optional. Whether to show the story word count. Default true. * @param string|null $attr['footer_chapters'] Optional. Whether to show the story chapter count. Default true. * @param string|null $attr['footer_status'] Optional. Whether to show the story status. Default true. * @param string|null $attr['footer_rating'] Optional. Whether to show the story age rating. Default true. * @param string|null $attr['footer_comments'] Optional. Whether to show the post comment count. Default false. * @param string|null $attr['class'] Optional. Additional CSS classes, separated by whitespace. * @param string|null $args['splide'] Configuration JSON for the Splide slider. Default empty. * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_latest_stories( $attr ) { // Defaults $args = fictioneer_get_default_shortcode_args( $attr, 4 ); // Type $type = sanitize_text_field( $attr['type'] ?? 'default' ); // Comments $args['footer_comments'] = filter_var( $attr['footer_comments'] ?? 0, FILTER_VALIDATE_BOOLEAN ); // Terms $args['terms'] = fictioneer_sanitize_query_var( $attr['terms'] ?? 0, ['inline', 'pills', 'none', 'false'], 'inline' ); $args['max_terms'] = absint( ( $attr['max_terms'] ?? 10 ) ?: 10 ); // Extra classes if ( $args['splide'] ?? 0 ) { $args['classes'] .= ' splide _splide-placeholder'; } // Transient? $transient_enabled = fictioneer_enable_shortcode_transients( 'fictioneer_latest_stories' ); if ( $transient_enabled && $args['cache'] ) { $base = serialize( $args ) . serialize( $attr ); $transient_key = "fictioneer_shortcode_latest_stories_{$type}_html_" . md5( $base ); $transient = get_transient( $transient_key ); if ( ! empty( $transient ) ) { return $transient; } } // Buffer ob_start(); switch ( $type ) { case 'compact': get_template_part( 'partials/_latest-stories-compact', null, $args ); break; case 'list': get_template_part( 'partials/_latest-stories-list', null, $args ); break; default: get_template_part( 'partials/_latest-stories', null, $args ); } $html = fictioneer_minify_html( ob_get_clean() ); if ( ( $args['splide'] ?? 0 ) && strpos( $args['classes'], 'no-auto-splide' ) === false ) { $html .= fictioneer_get_splide_inline_init(); } if ( $transient_enabled && $args['cache'] ) { set_transient( $transient_key, $html, FICTIONEER_SHORTCODE_TRANSIENT_EXPIRATION ); } // Return minified buffer return $html; } add_shortcode( 'fictioneer_latest_stories', 'fictioneer_shortcode_latest_stories' ); // ============================================================================= // LATEST UPDATES SHORTCODE // ============================================================================= /** * Shortcode to show latest story updates * * @since 4.3.0 * * @param string|null $attr['count'] Optional. Maximum number of items. Default 4. * @param string|null $attr['author'] Optional. Limit posts to a specific author. * @param string|null $attr['type'] Optional. Choose between 'default', 'simple', 'single', and 'compact'. * @param string|null $attr['single'] Optional. Whether to show only one chapter item. Default false. * @param string|null $attr['post_ids'] Optional. Limit posts to specific post IDs. * @param string|null $attr['ignore_protected'] Optional. Whether to ignore protected posts. Default false. * @param string|null $attr['only_protected'] Optional. Whether to query only protected posts. Default false. * @param string|null $attr['exclude_tag_ids'] Optional. Exclude posts with these tags. * @param string|null $attr['author_ids'] Optional. Only include posts by these author IDs. * @param string|null $attr['exclude_author_ids'] Optional. Exclude posts with these author IDs. * @param string|null $attr['exclude_cat_ids'] Optional. Exclude posts with these categories. * @param string|null $attr['categories'] Optional. Limit posts to specific category names. * @param string|null $attr['tags'] Optional. Limit posts to specific tag names. * @param string|null $attr['fandoms'] Optional. Limit posts to specific fandom names. * @param string|null $attr['genres'] Optional. Limit posts to specific genre names. * @param string|null $attr['characters'] Optional. Limit posts to specific character names. * @param string|null $attr['rel'] Optional. Relationship between taxonomies. Default 'AND'. * @param string|null $attr['vertical'] Optional. Whether to show the vertical variant. * @param string|null $attr['seamless'] Optional. Whether to render the image seamless. Default false (Customizer). * @param string|null $attr['aspect_ratio'] Optional. Aspect ratio for the image. Only with vertical. * @param string|null $attr['lightbox'] Optional. Whether the thumbnail is opened in the lightbox. Default true. * @param string|null $attr['thumbnail'] Optional. Whether to show the thumbnail. Default true (Customizer). * @param string|null $attr['words'] Optional. Whether to show the word count of chapter items. Default true. * @param string|null $attr['date'] Optional. Whether to show the date of chapter items. Default true. * @param string|null $attr['date_format'] Optional. String to override the date format. Default empty. * @param string|null $attr['nested_date_format'] Optional. String to override any nested date formats. Default empty. * @param string|null $attr['terms'] Optional. Either 'inline', 'pills', 'none', or 'false' (only in list type). * Default 'inline'. * @param string|null $attr['max_terms'] Optional. Maximum number of shown taxonomies. Default 10. * @param string|null $attr['footer'] Optional. Whether to show the footer (if any). Default true. * @param string|null $attr['footer_author'] Optional. Whether to show the story author. Default true. * @param string|null $attr['footer_date'] Optional. Whether to show the story date. Default true. * @param string|null $attr['footer_words'] Optional. Whether to show the story word count. Default true. * @param string|null $attr['footer_chapters'] Optional. Whether to show the story chapter count. Default true. * @param string|null $attr['footer_status'] Optional. Whether to show the story status. Default true. * @param string|null $attr['footer_rating'] Optional. Whether to show the story/chapter age rating. Default true. * @param string|null $attr['footer_comments'] Optional. Whether to show the post comment count. Default false. * @param string|null $attr['class'] Optional. Additional CSS classes, separated by whitespace. * @param string|null $args['splide'] Configuration JSON for the Splide slider. Default empty. * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_latest_story_updates( $attr ) { // Defaults $args = fictioneer_get_default_shortcode_args( $attr, 4 ); $args['single'] = filter_var( $attr['single'] ?? 0, FILTER_VALIDATE_BOOLEAN ); // Comments $args['footer_comments'] = filter_var( $attr['footer_comments'] ?? 0, FILTER_VALIDATE_BOOLEAN ); // Type $type = sanitize_text_field( $attr['type'] ?? 'default' ); // Terms $args['terms'] = fictioneer_sanitize_query_var( $attr['terms'] ?? 0, ['inline', 'pills', 'none', 'false'], 'inline' ); $args['max_terms'] = absint( ( $attr['max_terms'] ?? 10 ) ?: 10 ); // Extra classes if ( $args['splide'] ?? 0 ) { $args['classes'] .= ' splide _splide-placeholder '; } // Transient? $transient_enabled = fictioneer_enable_shortcode_transients( 'fictioneer_latest_updates' ); if ( $transient_enabled && $args['cache'] ) { $base = serialize( $args ) . serialize( $attr ); $transient_key = "fictioneer_shortcode_latest_updates_{$type}_html_" . md5( $base ); $transient = get_transient( $transient_key ); if ( ! empty( $transient ) ) { return $transient; } } // Buffer ob_start(); switch ( $type ) { case 'compact': get_template_part( 'partials/_latest-updates-compact', null, $args ); break; case 'list': get_template_part( 'partials/_latest-updates-list', null, $args ); break; default: get_template_part( 'partials/_latest-updates', null, $args ); } $html = fictioneer_minify_html( ob_get_clean() ); if ( ( $args['splide'] ?? 0 ) && strpos( $args['classes'], 'no-auto-splide' ) === false ) { $html .= fictioneer_get_splide_inline_init(); } if ( $transient_enabled && $args['cache'] ) { set_transient( $transient_key, $html, FICTIONEER_SHORTCODE_TRANSIENT_EXPIRATION ); } // Return minified buffer return $html; } add_shortcode( 'fictioneer_latest_updates', 'fictioneer_shortcode_latest_story_updates' ); // ============================================================================= // LATEST RECOMMENDATIONS SHORTCODE // ============================================================================= /** * Shortcode to show latest recommendations * * @since 4.0.0 * * @param string|null $attr['count'] Optional. Maximum number of items. Default 4. * @param string|null $attr['author'] Optional. Limit posts to a specific author. * @param string|null $attr['type'] Optional. Choose between 'default' and 'compact'. * @param string|null $attr['order'] Optional. Order argument. Default 'DESC'. * @param string|null $attr['orderby'] Optional. Orderby argument. Default 'date'. * @param string|null $attr['post_ids'] Optional. Limit posts to specific post IDs. * @param string|null $attr['ignore_protected'] Optional. Whether to ignore protected posts. Default false. * @param string|null $attr['only_protected'] Optional. Whether to query only protected posts. Default false. * @param string|null $attr['author_ids'] Optional. Only include posts by these author IDs. * @param string|null $attr['exclude_author_ids'] Optional. Exclude posts with these author IDs. * @param string|null $attr['exclude_tag_ids'] Optional. Exclude posts with these tags. * @param string|null $attr['exclude_cat_ids'] Optional. Exclude posts with these categories. * @param string|null $attr['categories'] Optional. Limit posts to specific category names. * @param string|null $attr['tags'] Optional. Limit posts to specific tag names. * @param string|null $attr['fandoms'] Optional. Limit posts to specific fandom names. * @param string|null $attr['genres'] Optional. Limit posts to specific genre names. * @param string|null $attr['characters'] Optional. Limit posts to specific character names. * @param string|null $attr['rel'] Optional. Relationship between taxonomies. Default 'AND'. * @param string|null $attr['vertical'] Optional. Whether to show the vertical variant. * @param string|null $attr['seamless'] Optional. Whether to render the image seamless. Default false (Customizer). * @param string|null $attr['aspect_ratio'] Optional. Aspect ratio for the image. Only with vertical. * @param string|null $attr['lightbox'] Optional. Whether the thumbnail is opened in the lightbox. Default true. * @param string|null $attr['thumbnail'] Optional. Whether to show the thumbnail. Default true (Customizer). * @param string|null $attr['class'] Optional. Additional CSS classes, separated by whitespace. * @param string|null $args['splide'] Configuration JSON for the Splide slider. Default empty. * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_latest_recommendations( $attr ) { // Defaults $args = fictioneer_get_default_shortcode_args( $attr, 4 ); // Type $type = sanitize_text_field( $attr['type'] ?? 'default' ); // Terms $args['terms'] = fictioneer_sanitize_query_var( $attr['terms'] ?? 0, ['inline', 'pills', 'none', 'false'], 'inline' ); $args['max_terms'] = absint( ( $attr['max_terms'] ?? 10 ) ?: 10 ); // Extra classes if ( $args['splide'] ?? 0 ) { $args['classes'] .= ' splide _splide-placeholder'; } // Transient? $transient_enabled = fictioneer_enable_shortcode_transients( 'fictioneer_latest_recommendations' ); if ( $transient_enabled && $args['cache'] ) { $base = serialize( $args ) . serialize( $attr ); $transient_key = "fictioneer_shortcode_latest_recommendations_{$type}_html_" . md5( $base ); $transient = get_transient( $transient_key ); if ( ! empty( $transient ) ) { return $transient; } } // Buffer ob_start(); switch ( $type ) { case 'compact': get_template_part( 'partials/_latest-recommendations-compact', null, $args ); break; default: get_template_part( 'partials/_latest-recommendations', null, $args ); } $html = fictioneer_minify_html( ob_get_clean() ); if ( ( $args['splide'] ?? 0 ) && strpos( $args['classes'], 'no-auto-splide' ) === false ) { $html .= fictioneer_get_splide_inline_init(); } if ( $transient_enabled && $args['cache'] ) { set_transient( $transient_key, $html, FICTIONEER_SHORTCODE_TRANSIENT_EXPIRATION ); } // Return minified buffer return $html; } add_shortcode( 'fictioneer_latest_recommendations', 'fictioneer_shortcode_latest_recommendations' ); // ============================================================================= // LATEST POST SHORTCODE // ============================================================================= /** * Shortcode to show the latest post * * @since 4.0.0 * * @param string|null $attr['count'] Optional. Maximum number of items. Default 1. * @param string|null $attr['author'] Optional. Limit posts to a specific author. * @param string|null $attr['post_ids'] Optional. Limit posts to specific post IDs. * @param string|null $attr['ignore_protected'] Optional. Whether to ignore protected posts. Default false. * @param string|null $attr['only_protected'] Optional. Whether to query only protected posts. Default false. * @param string|null $attr['author_ids'] Optional. Only include posts by these author IDs. * @param string|null $attr['exclude_author_ids'] Optional. Exclude posts with these author IDs. * @param string|null $attr['exclude_tag_ids'] Optional. Exclude posts with these tags. * @param string|null $attr['exclude_cat_ids'] Optional. Exclude posts with these categories. * @param string|null $attr['categories'] Optional. Limit posts to specific category names. * @param string|null $attr['tags'] Optional. Limit posts to specific tag names. * @param string|null $attr['rel'] Optional. Relationship between taxonomies. Default 'AND'. * @param string|null $attr['class'] Optional. Additional CSS classes, separated by whitespace. * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_latest_posts( $attr ) { // Defaults $args = fictioneer_get_default_shortcode_args( $attr, 1 ); // Transient? $transient_enabled = fictioneer_enable_shortcode_transients( 'fictioneer_latest_posts' ); if ( $transient_enabled && $args['cache'] ) { $base = serialize( $args ) . serialize( $attr ); $transient_key = "fictioneer_shortcode_latest_posts_html_" . md5( $base ); $transient = get_transient( $transient_key ); if ( ! empty( $transient ) ) { return $transient; } } // Buffer ob_start(); get_template_part( 'partials/_latest-posts', null, $args ); $html = fictioneer_minify_html( ob_get_clean() ); if ( $transient_enabled && $args['cache'] ) { set_transient( $transient_key, $html, FICTIONEER_SHORTCODE_TRANSIENT_EXPIRATION ); } // Return minified buffer return $html; } add_shortcode( 'fictioneer_latest_posts', 'fictioneer_shortcode_latest_posts' ); // ============================================================================= // BOOKMARKS SHORTCODE // ============================================================================= /** * Shortcode to show bookmarks * * @since 4.0.0 * * @param string|null $attr['count'] Optional. Maximum number of items. Default -1 (all). * @param string|null $attr['show_empty'] Optional. Whether to show the "no bookmarks" message. Default false. * @param string|null $attr['seamless'] Optional. Whether to render the image seamless. Default false (Customizer). * @param string|null $attr['thumbnail'] Optional. Whether to show the thumbnail. Default true (Customizer). * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_bookmarks( $attr ) { // Sanitize attributes $attr = is_array( $attr ) ? array_map( 'sanitize_text_field', $attr ) : sanitize_text_field( $attr ); // Setup $seamless_default = get_theme_mod( 'card_image_style', 'default' ) === 'seamless'; $thumbnail_default = get_theme_mod( 'card_image_style', 'default' ) !== 'none'; // Buffer ob_start(); get_template_part( 'partials/_bookmarks', null, array( 'count' => max( -1, intval( $attr['count'] ?? -1 ) ), 'show_empty' => $attr['show_empty'] ?? false, 'seamless' => filter_var( $attr['seamless'] ?? $seamless_default, FILTER_VALIDATE_BOOLEAN ), 'thumbnail' => filter_var( $attr['thumbnail'] ?? $thumbnail_default, FILTER_VALIDATE_BOOLEAN ) )); // Return minified buffer return fictioneer_minify_html( ob_get_clean() ); } add_shortcode( 'fictioneer_bookmarks', 'fictioneer_shortcode_bookmarks' ); // ============================================================================= // COOKIES SHORTCODE // ============================================================================= /** * Shortcode to show cookie consent actions * * Renders buttons to handle your consent and stored cookies. * * @since 4.7.0 * * @return string The captured shortcode HTML. */ function fictioneer_shortcode_cookie_buttons( $attr ) { ob_start(); // Start HTML ---> ?>
heading above list. * * @return string The captured HTML. */ function fictioneer_shortcode_chapter_list_empty( $attr ) { ob_start(); // Start HTML ---> ?>