(.*?)/s', '', $content ); return $content; } // ============================================================================= // META FIELD HELPERS // ============================================================================= /** * Returns HTML for a checkbox meta field * * @since 5.7.4 * * @param WP_Post $post The post. * @param string $meta_key The meta key. * @param string $label The checkbox label. * @param array $args { * Optional. An array of additional arguments. * * @type bool $required Whether the field is required. Default false. * } * * @return string The HTML markup for the field. */ function fictioneer_get_metabox_checkbox( $post, $meta_key, $label, $args = [] ) { // Setup $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $attributes = implode( ' ', $args['attributes'] ?? [] ); ob_start(); // Start HTML ---> ?> ID, $meta_key, true ) ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $placeholder = strval( $args['placeholder'] ?? '' ); $type = $args['type'] ?? 'text'; $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $maxlength = isset( $args['maxlength'] ) ? 'maxlength="' . $args['maxlength'] . '"' : ''; $attributes = implode( ' ', $args['attributes'] ?? [] ); ob_start(); // Start HTML ---> ?>
>
>
ID, $meta_key, true ) ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $attributes = implode( ' ', $args['attributes'] ?? [] ); if ( $meta_value ) { $utc_datetime = new DateTime( $meta_value, new DateTimeZone( 'UTC' ) ); $utc_datetime->setTimezone( wp_timezone() ); $meta_value = $utc_datetime->format( 'Y-m-d\TH:i:s' ); } ob_start(); // Start HTML ---> ?>
>
ID, $meta_key, true ) ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $placeholder = strval( $args['placeholder'] ?? '' ); $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $attributes = implode( ' ', $args['attributes'] ?? [] ); ob_start(); // Start HTML ---> ?>
>
>
ID, $meta_key, true ); $array = is_array( $array ) ? $array : []; $list = esc_attr( implode( ', ', $array ) ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $placeholder = strval( $args['placeholder'] ?? '' ); $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $attributes = implode( ' ', $args['attributes'] ?? [] ); ob_start(); // Start HTML ---> ?>
> >
Name). * @param array $args { * Optional. An array of additional arguments. * * @type string $label Label above the field. * @type string $description Description below the field. * @type bool $required Whether the field is required. Default false. * @type string $query_var GET variable for current selection if empty. * } * * @return string The HTML markup for the field. */ function fictioneer_get_metabox_select( $post, $meta_key, $options, $args = [] ) { // Setup $selected = get_post_meta( $post->ID, $meta_key, true ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $attributes = implode( ' ', $args['attributes'] ?? [] ); // Current selection if ( empty( $selected ) ) { $query_var = sanitize_key( $_GET[ $args['query_var'] ?? '' ] ?? '' ); if ( $query_var && array_key_exists( $query_var, $options ) ) { $selected = $query_var; } else { $selected = empty( $selected ) ? array_keys( $options )[0] : $selected; } } // Sort alphabetically if ( $args['asort'] ?? 0 ) { $none_item = isset( $options['0'] ) ? array( '0' => $options['0'] ) : []; unset( $options['0'] ); asort( $options ); $options = $none_item + $options; } ob_start(); // Start HTML ---> ?>
>
ID, $meta_key, true ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $placeholder = strval( $args['placeholder'] ?? '' ); $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $input_classes = strval( $args['input_classes'] ?? '' ); $attributes = implode( ' ', $args['attributes'] ?? [] ); ob_start(); // Start HTML ---> ?>
>
ID, $meta_key, true ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $upload = strval( $args['button'] ?? _x( 'Set image', 'Metabox image upload button.', 'fictioneer' ) ); $replace = _x( 'Replace', 'Metabox image upload button.', 'fictioneer' ); $remove = _x( 'Remove', 'Metabox image remove button.', 'fictioneer' ); $image_url = wp_get_attachment_url( $meta_value ); $image_css = $image_url ? "style='background-image: url(\"{$image_url}\");'" : ''; ob_start(); // Start HTML ---> ?>
>
ID, $meta_key, true ); $file_path = get_attached_file( $meta_value ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $button_upload = strval( $args['button'] ?? _x( 'Add eBook', 'Metabox ebook upload button.', 'fictioneer' ) ); $button_replace = _x( 'Replace', 'Metabox replace button.', 'fictioneer' ); $button_remove = _x( 'Remove', 'Metabox remove button.', 'fictioneer' ); $title = null; $filename = null; $filesize = null; $url = null; $is_set = false; if ( file_exists( $file_path ) ) { $title = get_the_title( $meta_value ); $filename = wp_basename( $file_path ); $filesize = size_format( filesize( $file_path ) ); $url = wp_get_attachment_url( $meta_value ); $is_set = true; } else { $meta_value = null; } ob_start(); // Start HTML ---> ?>
ID, $meta_key, true ); $array = is_array( $array ) ? $array : []; $list = esc_attr( implode( ', ', $array ) ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); ob_start(); // Start HTML ---> ?>
{$name}"; } ?>
ID, $meta_key, true ) ); $meta_value = $meta_value ? $meta_value : FICTIONEER_DEFAULT_CHAPTER_ICON; $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $placeholder = strval( $args['placeholder'] ?? '' ); $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $current_icon_class = fictioneer_get_icon_field( $meta_key, $post->ID ); ob_start(); // Start HTML ---> ?>
>
>
ID, $meta_key, true ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); $required = ( $args['required'] ?? 0 ) ? 'required' : ''; $data_required = $required ? 'data-required="true"' : ''; $settings = array( 'wpautop' => true, 'media_buttons' => false, 'textarea_name' => $meta_key, 'textarea_rows' => 10, 'default_editor' => 'html', 'tinymce' => array( 'toolbar1' => 'bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link', 'toolbar2' => '', 'paste_as_text' => true ), 'quicktags' => array( 'buttons' => 'strong,em,link,block,img,ul,ol,li,close' ) ); ob_start(); // Start HTML ---> ?>
>
ID}" ); $label = strval( $args['label'] ?? '' ); $description = strval( $args['description'] ?? '' ); // Build and return HTML ob_start(); // Start HTML ---> ?>
    ?>
true ), $args['type_args'] ?? [] ); $post_types = get_post_types( $type_args, 'objects' ); // HTML echo '
'; echo "
'; } /** * AJAX: Delegate query and send posts for relationship fields * * @since 5.8.0 */ function fictioneer_ajax_query_relationship_posts() { // Validate $action = sanitize_text_field( $_REQUEST['action'] ?? '' ); $nonce = sanitize_text_field( $_REQUEST['nonce'] ?? '' ); $meta_key = sanitize_text_field( $_REQUEST['key'] ?? '' ); $post_id = absint( $_REQUEST['post_id'] ?? 0 ); $user = wp_get_current_user(); // Abort if... if ( empty( $action ) ) { wp_send_json_error( array( 'error' => __( 'Error: Action parameter missing.', 'fictioneer' ) ) ); } if ( empty( $nonce ) ) { wp_send_json_error( array( 'error' => __( 'Error: Nonce parameter missing.', 'fictioneer' ) ) ); } if ( empty( $meta_key ) ) { wp_send_json_error( array( 'error' => __( 'Error: Key parameter missing.', 'fictioneer' ) ) ); } if ( ! $user->exists() ) { wp_send_json_error( array( 'error' => __( 'Error: Not logged in.', 'fictioneer' ) ) ); } // Story chapter assignment? if ( check_ajax_referer( "relationship_posts_fictioneer_story_chapters_{$post_id}", 'nonce', false ) && $post_id > 0 ) { fictioneer_ajax_get_relationship_chapters( $post_id, $meta_key ); } // Story page assignment? if ( check_ajax_referer( "relationship_posts_fictioneer_story_custom_pages_{$post_id}", 'nonce', false ) && $post_id > 0 ) { fictioneer_ajax_get_relationship_story_pages( $post_id, $meta_key ); } // Collection item assignment? if ( check_ajax_referer( "relationship_posts_fictioneer_collection_items_{$post_id}", 'nonce', false ) && $post_id > 0 ) { fictioneer_ajax_get_relationship_collection( $post_id, $meta_key ); } // Post featured item assignment? if ( check_ajax_referer( "relationship_posts_fictioneer_post_featured_{$post_id}", 'nonce', false ) && $post_id > 0 ) { fictioneer_ajax_get_relationship_featured( $post_id, $meta_key ); } // Nothing worked... wp_send_json_error( array( 'error' => __( 'Error: Invalid request.', 'fictioneer' ) ) ); } add_action( 'wp_ajax_fictioneer_ajax_query_relationship_posts', 'fictioneer_ajax_query_relationship_posts' ); /** * Render HTML for selected story chapters * * @since 5.8.0 * * @param array $selected Currently selected chapters. * @param string $meta_key The meta key. * @param array $args Optional. An array of additional arguments. */ function fictioneer_callback_relationship_chapters( $selected, $meta_key, $args = [] ) { // Build HTML foreach ( $selected as $chapter ) { $title = fictioneer_get_safe_title( $chapter, 'admin-callback-relationship-chapters' ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-values-item']; $label = fictioneer_get_post_status_label( $chapter->post_status ); if ( get_post_meta( $chapter->ID, 'fictioneer_chapter_hidden', true ) ) { $title = "{$title} (" . _x( 'Unlisted', 'Chapter assignment flag.', 'fictioneer' ) . ")"; } if ( $chapter->post_status !== 'publish' ) { $title = "{$title} ({$label})"; } // Start HTML ---> ?>
  • post_type !== 'fcn_story' ) { wp_send_json_error( array( 'error' => __( 'Error: Wrong post type.', 'fictioneer' ) ) ); } if ( ! current_user_can( 'edit_fcn_stories' ) || ( $user->ID != $post->post_author && ! current_user_can( 'edit_others_fcn_stories' ) && ! current_user_can( 'manage_options' ) ) ) { wp_send_json_error( array( 'error' => __( 'Error: Insufficient permissions.', 'fictioneer' ) ) ); } // Query $query_args = array( 'post_type' => 'fcn_chapter', 'post_status' => ['publish', 'private', 'future'], 'orderby' => 'date', 'order' => 'desc', 'posts_per_page' => 10, 'paged' => $page, 's' => $search, 'update_post_meta_cache' => true, 'update_post_term_cache' => false // Improve performance ); if ( FICTIONEER_FILTER_STORY_CHAPTERS ) { $query_args['meta_key'] = 'fictioneer_chapter_story'; $query_args['meta_value'] = $post_id; } $query = new WP_Query( $query_args ); // Setup $nonce = wp_create_nonce( "relationship_posts_{$meta_key}_{$post_id}" ); $output = []; // Build HTML for items foreach ( $query->posts as $chapter ) { // Chapter setup $title = fictioneer_get_safe_title( $chapter, 'admin-ajax-get-relationship-chapters' ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-source-item']; $label = fictioneer_get_post_status_label( $chapter->post_status ); // Update title if necessary if ( get_post_meta( $chapter->ID, 'fictioneer_chapter_hidden', true ) ) { $title = "{$title} (" . _x( 'Unlisted', 'Chapter assignment flag.', 'fictioneer' ) . ")"; } if ( $chapter->post_status !== 'publish' ) { $title = "{$title} ({$label})"; } // Build and append item $item = "
  • {$title}
  • "; $output[] = $item; } // Build HTML for observer if ( $page < $query->max_num_pages ) { $page++; $observer = "
  • '; $observer .= '' . _x( 'Loading', 'AJAX relationship posts loading.', 'fictioneer' ) . '
  • '; $output[] = $observer; } // No results if ( empty( $output ) ) { $no_matches = "
  • "; $no_matches .= _x( 'No matches found.', 'AJAX relationship posts loading.', 'fictioneer' ); $no_matches .= '
  • '; $output[] = $no_matches; } // Response wp_send_json_success( array( 'html' => implode( '', $output ) ) ); } /** * Return HTML story chapter info * * @since 5.8.0 * * @param WP_Post $chapter The chapter post. * * @return string HTML for the chapter info. */ function fictioneer_get_relationship_chapter_details( $chapter ) { // Setup $text_icon = get_post_meta( $chapter->ID, 'fictioneer_chapter_text_icon', true ); $icon = fictioneer_get_icon_field( 'fictioneer_chapter_icon', $chapter->ID ); $rating = get_post_meta( $chapter->ID, 'fictioneer_chapter_rating', true ); $warning = get_post_meta( $chapter->ID, 'fictioneer_chapter_warning', true ); $group = get_post_meta( $chapter->ID, 'fictioneer_chapter_group', true ); $flags = []; $info = []; // Build $info[] = empty( $text_icon ) ? sprintf( '', $icon ) : "{$text_icon}"; if ( get_post_meta( $chapter->ID, 'fictioneer_chapter_hidden', true ) ) { $flags[] = _x( 'Unlisted', 'Chapter assignment flag.', 'fictioneer' ); } if ( get_post_meta( $chapter->ID, 'fictioneer_chapter_no_chapter', true ) ) { $flags[] = _x( 'No Chapter', 'Chapter assignment flag.', 'fictioneer' ); } $info[] = sprintf( _x( 'Status: %s', 'Chapter assignment info.', 'fictioneer' ), fictioneer_get_post_status_label( $chapter->post_status ) ); $info[] = sprintf( _x( 'Date: %1$s at %2$s', 'Chapter assignment info.', 'fictioneer' ), get_the_date( '', $chapter->ID ), get_the_time( '', $chapter->ID ) ); if ( ! empty( $group ) ) { $info[] = sprintf( _x( 'Group: %s', 'Chapter assignment info.', 'fictioneer' ), $group ); } if ( ! empty( $rating ) ) { $info[] = sprintf( _x( 'Age Rating: %s', 'Chapter assignment info.', 'fictioneer' ), $rating ); } if ( ! empty( $flags ) ) { $info[] = sprintf( _x( 'Flags: %s', 'Chapter assignment info.', 'fictioneer' ), implode( ', ', $flags ) ); } if ( ! empty( $warning ) ) { $info[] = sprintf( _x( 'Warning: %s', 'Chapter assignment info.', 'fictioneer' ), $warning ); } // Implode and return return implode( ' • ', $info ); } /** * Render HTML for selected story pages * * @since 5.8.0 * * @param array $selected Currently selected pages. * @param string $meta_key The meta key. * @param array $args Optional. An array of additional arguments. */ function fictioneer_callback_relationship_story_pages( $selected, $meta_key, $args = [] ) { // Build HTML foreach ( $selected as $page ) { $title = fictioneer_get_safe_title( $page, 'admin-callback-relationship-story-pages' ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-values-item']; // Start HTML ---> ?>
  • post_type !== 'fcn_story' ) { wp_send_json_error( array( 'error' => __( 'Error: Wrong post type.', 'fictioneer' ) ) ); } if ( ! current_user_can( 'edit_fcn_stories' ) || ( $user->ID != $post->post_author && ! current_user_can( 'edit_others_fcn_stories' ) && ! current_user_can( 'manage_options' ) ) ) { wp_send_json_error( array( 'error' => __( 'Error: Insufficient permissions.', 'fictioneer' ) ) ); } $forbidden = array_unique( array( get_option( 'fictioneer_user_profile_page', 0 ), get_option( 'fictioneer_bookmarks_page', 0 ), get_option( 'fictioneer_stories_page', 0 ), get_option( 'fictioneer_chapters_page', 0 ), get_option( 'fictioneer_recommendations_page', 0 ), get_option( 'fictioneer_collections_page', 0 ), get_option( 'fictioneer_bookshelf_page', 0 ), get_option( 'fictioneer_404_page', 0 ), get_option( 'page_on_front', 0 ), get_option( 'page_for_posts', 0 ) ) ); // Query $query = new WP_Query( array( 'post_type' => 'page', 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'desc', 'author' => get_post_field( 'post_author', $post_id ), 'post__not_in' => array_map( 'strval', $forbidden ), 'posts_per_page' => 10, 'paged' => $page, 's' => $search, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false // Improve performance ) ); // Setup $nonce = wp_create_nonce( "relationship_posts_{$meta_key}_{$post_id}" ); $output = []; // Build HTML for items foreach ( $query->posts as $item ) { // Chapter setup $title = fictioneer_get_safe_title( $item, 'admin-ajax-get-relationship-story-pages' ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-source-item']; // Build and append item $item = "
  • "; $item .= "{$title}
  • "; $output[] = $item; } // Build HTML for observer if ( $page < $query->max_num_pages ) { $page++; $observer = "
  • '; $observer .= '' . _x( 'Loading', 'AJAX relationship posts loading.', 'fictioneer' ) . '
  • '; $output[] = $observer; } // No results if ( empty( $output ) ) { $no_matches = "
  • "; $no_matches .= _x( 'No matches found.', 'AJAX relationship posts loading.', 'fictioneer' ); $no_matches .= '
  • '; $output[] = $no_matches; } // Response wp_send_json_success( array( 'html' => implode( '', $output ) ) ); } /** * Render HTML for selected collection items * * @since 5.8.0 * * @param array $selected Currently selected items. * @param string $meta_key The meta key. * @param array $args Optional. An array of additional arguments. */ function fictioneer_callback_relationship_collection( $selected, $meta_key, $args = [] ) { foreach ( $selected as $item ) { $title = fictioneer_get_safe_title( $item, 'admin-callback-relationship-collection' ); $label = esc_html( fictioneer_get_post_type_label( $item->post_type ) ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-values-item']; // Start HTML ---> ?>
  • post_type !== 'fcn_collection' ) { wp_send_json_error( array( 'error' => __( 'Error: Wrong post type.', 'fictioneer' ) ) ); } if ( ! current_user_can( 'edit_fcn_collections' ) || ( $user->ID != $post->post_author && ! current_user_can( 'edit_others_fcn_collections' ) && ! current_user_can( 'manage_options' ) ) ) { wp_send_json_error( array( 'error' => __( 'Error: Insufficient permissions.', 'fictioneer' ) ) ); } // Query $query = new WP_Query( array( 'post_type' => $post_type ?: $allowed_post_types, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'desc', 'post__not_in' => array_map( 'strval', $forbidden ), 'posts_per_page' => 10, 'paged' => $page, 's' => $search, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false // Improve performance ) ); // Setup $nonce = wp_create_nonce( "relationship_posts_{$meta_key}_{$post_id}" ); $output = []; // Build HTML for items foreach ( $query->posts as $item ) { // Chapter setup $title = fictioneer_get_safe_title( $item, 'admin-ajax-get-relationship-collection' ); $label = esc_html( fictioneer_get_post_type_label( $item->post_type ) ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-source-item']; // Build and append item $item = "
  • "; $item .= "{$label} {$title}
  • "; $output[] = $item; } // Build HTML for observer if ( $page < $query->max_num_pages ) { $page++; $observer = "
  • '; $observer .= '' . _x( 'Loading', 'AJAX relationship posts loading.', 'fictioneer' ) . '
  • '; $output[] = $observer; } // No results if ( empty( $output ) ) { $no_matches = "
  • "; $no_matches .= _x( 'No matches found.', 'AJAX relationship posts loading.', 'fictioneer' ); $no_matches .= '
  • '; $output[] = $no_matches; } // Response wp_send_json_success( array( 'html' => implode( '', $output ) ) ); } /** * Render HTML for selected featured items * * @since 5.8.0 * * @param array $selected Currently selected items. * @param string $meta_key The meta key. * @param array $args Optional. An array of additional arguments. */ function fictioneer_callback_relationship_featured( $selected, $meta_key, $args = [] ) { foreach ( $selected as $item ) { $title = fictioneer_get_safe_title( $item, 'admin-callback-relationship-featured' ); $label = esc_html( fictioneer_get_post_type_label( $item->post_type ) ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-values-item']; // Start HTML ---> ?>
  • post_type !== 'post' ) { wp_send_json_error( array( 'error' => __( 'Error: Wrong post type.', 'fictioneer' ) ) ); } if ( ! current_user_can( 'edit_posts' ) || ( $user->ID != $post->post_author && ! current_user_can( 'edit_others_posts' ) && ! current_user_can( 'manage_options' ) ) ) { wp_send_json_error( array( 'error' => __( 'Error: Insufficient permissions.', 'fictioneer' ) ) ); } // Query $query = new WP_Query( array( 'post_type' => $post_type ?: $allowed_post_types, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'desc', 'posts_per_page' => 10, 'paged' => $page, 's' => $search, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false // Improve performance ) ); // Setup $nonce = wp_create_nonce( "relationship_posts_{$meta_key}_{$post_id}" ); $output = []; // Build HTML for items foreach ( $query->posts as $item ) { // Chapter setup $title = fictioneer_get_safe_title( $item, 'admin-ajax-get-relationship-featured' ); $label = esc_html( fictioneer_get_post_type_label( $item->post_type ) ); $classes = ['fictioneer-meta-field__relationships-item', 'fictioneer-meta-field__relationships-source-item']; // Build and append item $item = "
  • "; $item .= "{$label} {$title}
  • "; $output[] = $item; } // Build HTML for observer if ( $page < $query->max_num_pages ) { $page++; $observer = "
  • '; $observer .= '' . _x( 'Loading', 'AJAX relationship posts loading.', 'fictioneer' ) . '
  • '; $output[] = $observer; } // No results if ( empty( $output ) ) { $no_matches = "
  • "; $no_matches .= _x( 'No matches found.', 'AJAX relationship posts loading.', 'fictioneer' ); $no_matches .= '
  • '; $output[] = $no_matches; } // Response wp_send_json_success( array( 'html' => implode( '', $output ) ) ); } // ============================================================================= // METABOX CLASSES // ============================================================================= /** * Append classes to the metabox * * @since 5.7.4 * * @param array $classes An array of postbox classes. * * @return array The modified array of postbox classes. */ function fictioneer_append_metabox_classes( $classes ) { global $post; // Add class $classes[] = 'fictioneer-side-metabox'; if ( ! use_block_editor_for_post_type( $post->post_type ) ) { $classes[] = 'fictioneer-side-metabox--classic'; } // Return with added class return $classes; } add_filter( 'postbox_classes_fcn_story_fictioneer-story-meta', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_fcn_story_fictioneer-story-data', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_fcn_story_fictioneer-story-epub', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_fcn_chapter_fictioneer-chapter-meta', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_fcn_chapter_fictioneer-chapter-data', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_post_fictioneer-featured-content', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_fcn_collection_fictioneer-collection-data', 'fictioneer_append_metabox_classes' ); add_filter( 'postbox_classes_fcn_recommendation_fictioneer-recommendation-data', 'fictioneer_append_metabox_classes' ); foreach ( ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_recommendation', 'fcn_collection'] as $type ) { add_filter( "postbox_classes_{$type}_fictioneer-extra", 'fictioneer_append_metabox_classes' ); } foreach ( ['post', 'fcn_story', 'fcn_chapter'] as $type ) { add_filter( "postbox_classes_{$type}_fictioneer-support-links", 'fictioneer_append_metabox_classes' ); } // ============================================================================= // STORY META FIELDS // ============================================================================= /** * Adds story meta metabox * * @since 5.7.4 */ function fictioneer_add_story_meta_metabox() { add_meta_box( 'fictioneer-story-meta', __( 'Story Meta', 'fictioneer' ), 'fictioneer_render_story_meta_metabox', ['fcn_story'], 'side', 'default' ); } add_action( 'add_meta_boxes', 'fictioneer_add_story_meta_metabox' ); /** * Render story meta metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_story_meta_metabox( $post ) { // --- Setup ----------------------------------------------------------------- $nonce = wp_create_nonce( "story_meta_data_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $advanced = get_option( 'fictioneer_enable_advanced_meta_fields' ); $output = []; // --- Add fields ------------------------------------------------------------ $output['fictioneer_story_status'] = fictioneer_get_metabox_select( $post, 'fictioneer_story_status', array( 'Ongoing' => _x( 'Ongoing', 'Story status select option.', 'fictioneer' ), 'Completed' => _x( 'Completed', 'Story status select option.', 'fictioneer' ), 'Oneshot' => _x( 'Oneshot', 'Story status select option.', 'fictioneer' ), 'Hiatus' => _x( 'Hiatus', 'Story status select option.', 'fictioneer' ), 'Canceled' => _x( 'Canceled', 'Story status select option.', 'fictioneer' ) ), array( 'label' => _x( 'Status', 'Story status meta field label.', 'fictioneer' ), 'description' => __( 'Current status of the story.', 'fictioneer' ), 'required' => 1 ) ); $output['fictioneer_story_rating'] = fictioneer_get_metabox_select( $post, 'fictioneer_story_rating', array( 'Everyone' => fcntr( 'Everyone' ), 'Teen' => fcntr( 'Teen' ), 'Mature' => fcntr( 'Mature' ), 'Adult' => fcntr( 'Adult' ) ), array( 'label' => _x( 'Age Rating', 'Story age rating meta field label.', 'fictioneer' ), 'description' => __( 'Select an overall age rating.', 'fictioneer' ), 'required' => 1 ) ); if ( $advanced ) { $output['fictioneer_story_co_authors'] = fictioneer_get_metabox_array( $post, 'fictioneer_story_co_authors', array( 'label' => _x( 'Co-Authors', 'Story co-authors meta field label.', 'fictioneer' ), 'description' => __( 'Comma-separated list of author IDs.', 'fictioneer' ) ) ); } $output['fictioneer_story_copyright_notice'] = fictioneer_get_metabox_text( $post, 'fictioneer_story_copyright_notice', array( 'label' => _x( 'Copyright Notice', 'Story copyright notice meta field label.', 'fictioneer' ), 'description' => __( 'Optional, leave empty to hide.', 'fictioneer' ) ) ); $output['fictioneer_story_topwebfiction_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_story_topwebfiction_link', array( 'label' => _x( 'Top Web Fiction Link', 'Story top web fiction link meta field label.', 'fictioneer' ), 'description' => __( 'URL to Top Web Fiction page.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); $output['flags_heading'] = '
    ' . __( 'Flags', 'Metabox checkbox heading.', 'fictioneer' ) . '
    '; if ( current_user_can( 'fcn_make_sticky', $post->ID ) && FICTIONEER_ENABLE_STICKY_CARDS ) { $output['fictioneer_story_sticky'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_sticky', __( 'Sticky in lists', 'fictioneer' ) ); } $output['fictioneer_story_hidden'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_hidden', __( 'Hide story in lists', 'fictioneer' ) ); $output['fictioneer_story_no_thumbnail'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_no_thumbnail', __( 'Hide thumbnail on story page', 'fictioneer' ) ); $output['fictioneer_story_no_tags'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_no_tags', __( 'Hide taxonomies on story page', 'fictioneer' ) ); if ( ! get_option( 'fictioneer_hide_chapter_icons' ) ) { $output['fictioneer_story_hide_chapter_icons'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_hide_chapter_icons', __( 'Hide chapter icons', 'fictioneer' ) ); } if ( ! get_option( 'fictioneer_disable_chapter_collapsing' ) ) { $output['fictioneer_story_disable_collapse'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_disable_collapse', __( 'Disable collapsing of chapters', 'fictioneer' ) ); } if ( get_option( 'fictioneer_enable_chapter_groups' ) ) { $output['fictioneer_story_disable_groups'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_disable_groups', __( 'Disable chapter groups', 'fictioneer' ) ); } if ( get_option( 'fictioneer_enable_epubs' ) ) { $output['fictioneer_story_no_epub'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_story_no_epub', __( 'Disable ePUB download', 'fictioneer' ) ); } if ( current_user_can( 'fcn_custom_page_css', $post->ID ) ) { $output['fictioneer_story_css'] = fictioneer_get_metabox_textarea( $post, 'fictioneer_story_css', array( 'label' => __( 'Custom Story CSS', 'fictioneer' ), 'description' => __( 'Applied to the story and all chapters.', 'fictioneer' ), 'placeholder' => __( '.selector { ... }', 'fictioneer' ), 'input_classes' => 'fictioneer-meta-field__textarea--code' ) ); } if ( $advanced && current_user_can( 'manage_options' ) ) { $output['fictioneer_story_redirect_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_story_redirect_link', array( 'label' => _x( 'Redirect Link (Beware!)', 'Story redirect link meta field label.', 'fictioneer' ), 'description' => __( 'URL to redirect to when story is opened.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); } // --- Filters --------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_story_meta', $output, $post ); // --- Render ---------------------------------------------------------------- echo implode( '', $output ); // Start HTML ---> ?> ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields ------------------------------------------------------------ // Short description $output['fictioneer_story_short_description'] = fictioneer_get_metabox_editor( $post, 'fictioneer_story_short_description', array( 'label' => _x( 'Short Description', 'Story short description meta field label.', 'fictioneer' ), 'description' => __( 'The first paragraph is used on cards, so keep it nice and concise. The full short description is passed to the Storygraph API.', 'fictioneer' ), 'required' => 1 ) ); // Chapters $chapter_ids = fictioneer_get_story_chapter_ids( $post->ID ); $chapters = empty( $chapter_ids ) ? [] : get_posts( array( 'post_type' => 'fcn_chapter', 'post_status' => 'any', 'post__in' => $chapter_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'posts_per_page' => -1, 'meta_key' => 'fictioneer_chapter_story', 'meta_value' => $post->ID, 'update_post_meta_cache' => true, 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $description = get_option( 'fictioneer_limit_chapter_stories_by_author' ) ? __( 'Select and order chapters assigned to the story (set in the chapter). The author of the chapter and the story must match.', 'fictioneer' ) : __( 'Select and order chapters assigned to the story (set in the chapter).', 'fictioneer' ); $output['fictioneer_story_chapters'] = fictioneer_get_metabox_relationships( $post, 'fictioneer_story_chapters', $chapters, 'fictioneer_callback_relationship_chapters', array( 'label' => _x( 'Chapters', 'Story chapters meta field label.', 'fictioneer' ), 'description' => $description, 'show_info' => 1 ) ); // Custom pages if ( current_user_can( 'fcn_story_pages', $post->ID ) && FICTIONEER_MAX_CUSTOM_PAGES_PER_STORY > 0 ) { $page_ids = get_post_meta( $post->ID, 'fictioneer_story_custom_pages', true ) ?: []; $page_ids = is_array( $page_ids ) ? $page_ids : []; $pages = empty( $page_ids ) ? [] : get_posts( array( 'post_type' => 'page', 'post_status' => 'any', 'post__in' => $page_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'posts_per_page' => -1, 'author' => get_post_field( 'post_author', $post->ID ), 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $output['fictioneer_story_custom_pages'] = fictioneer_get_metabox_relationships( $post, 'fictioneer_story_custom_pages', $pages, 'fictioneer_callback_relationship_story_pages', array( 'label' => _x( 'Custom Pages', 'Story pages meta field label.', 'fictioneer' ), 'description' => sprintf( __( 'Add up to %s pages as tabs to stories. Pages must have a short name and the same author as the story; otherwise, they will not show up.', 'fictioneer' ), FICTIONEER_MAX_CUSTOM_PAGES_PER_STORY ) ) ); } // Global note $output['fictioneer_story_global_note'] = fictioneer_get_metabox_editor( $post, 'fictioneer_story_global_note', array( 'label' => _x( 'Global Note', 'Story global note meta field label.', 'fictioneer' ), 'description' => __( 'Displayed in a box above all chapters; start with "[!password]" to hide in protected chapters. Limited HTML allowed.', 'fictioneer' ) ) ); // Password note $output['fictioneer_story_password_note'] = fictioneer_get_metabox_editor( $post, 'fictioneer_story_password_note', array( 'label' => _x( 'Password Note', 'Story password note meta field label.', 'fictioneer' ), 'description' => __( 'Displayed for password protected content; start with "[!global]" to show on all protected chapters without note. Limited HTML allowed.', 'fictioneer' ) ) ); // --- Filters --------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_story_data', $output, $post ); // --- Render ---------------------------------------------------------------- echo implode( '', $output ); // Start HTML ---> ?> ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields ------------------------------------------------------------ // Custom upload if ( current_user_can( 'fcn_custom_epub_upload', $post->ID ) ) { $output['fictioneer_story_ebook_upload_one'] = fictioneer_get_metabox_ebook( $post, 'fictioneer_story_ebook_upload_one', array( 'label' => _x( 'Custom Upload', 'Story ebook upload meta field label.', 'fictioneer' ), 'description' => __( 'If you do not want to rely on the ePUB converter, you can upload your own ebook. Allowed formats are epub, mobi, pdf, rtf, and txt.', 'fictioneer' ) ) ); } // Preface $output['fictioneer_story_epub_preface'] = fictioneer_get_metabox_editor( $post, 'fictioneer_story_epub_preface', array( 'label' => _x( 'ePUB Preface', 'Story ePUB preface meta field label.', 'fictioneer' ), 'description' => __( 'Required for the download. Inserted as separate page between cover and table of contents. Disclaimers, copyright notes, tags, and other front matter goes here.', 'fictioneer' ) ) ); // Afterword $output['fictioneer_story_epub_afterword'] = fictioneer_get_metabox_editor( $post, 'fictioneer_story_epub_afterword', array( 'label' => _x( 'ePUB Afterword', 'Story ePUB afterword meta field label.', 'fictioneer' ), 'description' => __( 'Inserted after the last chapter. Thanks and personal thoughts on the story go here, for example.', 'fictioneer' ) ) ); // CSS if ( current_user_can( 'fcn_custom_epub_css', $post->ID ) ) { $output['fictioneer_story_epub_custom_css'] = fictioneer_get_metabox_textarea( $post, 'fictioneer_story_epub_custom_css', array( 'label' => _x( 'ePUB CSS', 'Story ePUB CSS meta field label.', 'fictioneer' ), 'description' => __( 'Inject CSS into the ePUB to customize the style for your story. Dangerous.', 'fictioneer' ), 'input_classes' => 'fictioneer-meta-field__textarea--code', 'placeholder' => '.selector { ... }' ) ); } // --- Filters --------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_story_epub', $output, $post ); // --- Render ---------------------------------------------------------------- echo implode( '', $output ); // Start HTML ---> ?> 0; } ); $chapter_ids = array_unique( $chapter_ids ); if ( empty( $chapter_ids ) ) { $fields['fictioneer_story_chapters'] = []; // Ensure empty meta is removed } else { // Make sure only allowed posts are in $chapter_query_args = array( 'post_type' => 'fcn_chapter', 'post__in' => $chapter_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'fields' => 'ids', 'posts_per_page' => -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ); if ( FICTIONEER_FILTER_STORY_CHAPTERS ) { $chapter_query_args['meta_key'] = 'fictioneer_chapter_story'; $chapter_query_args['meta_value'] = $post_id; } $chapters_query = new WP_Query( $chapter_query_args ); $chapter_ids = array_map( 'strval', $chapters_query->posts ); $fields['fictioneer_story_chapters'] = $chapter_ids; } // Remember when chapters have been changed if ( $previous_chapter_ids !== $chapter_ids ) { update_post_meta( $post_id, 'fictioneer_chapters_modified', current_time( 'mysql', true ) ); } // Remember when chapters have been added $chapter_diff = array_diff( $chapter_ids, $previous_chapter_ids ); if ( ! empty( $chapter_diff ) ) { $allowed_statuses = apply_filters( 'fictioneer_filter_chapters_added_statuses', ['publish'], $post_id ); $new_chapters = new WP_Query( array( 'post_type' => 'fcn_chapter', 'post_status' => $allowed_statuses, 'post__in' => $chapter_diff, 'fields' => 'ids', 'posts_per_page' => 10, // Sensible limit in case of bulks 'update_post_meta_cache' => true, 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); if ( $new_chapters->have_posts() ) { $valid_new_chapters = false; foreach ( $new_chapters->posts as $chapter_id ) { if ( ! get_post_meta( $chapter_id, 'fictioneer_chapter_hidden', true ) ) { $valid_new_chapters = true; break; } } if ( $valid_new_chapters ) { update_post_meta( $post_id, 'fictioneer_chapters_added', current_time( 'mysql', true ) ); } } } // Log changes fictioneer_log_story_chapter_changes( $post_id, $chapter_ids, $previous_chapter_ids ); } // Custom pages if ( isset( $_POST['fictioneer_story_custom_pages'] ) && current_user_can( 'fcn_story_pages', $post_id ) ) { $page_ids = $_POST['fictioneer_story_custom_pages']; $page_ids = is_array( $page_ids ) ? $page_ids : [ $page_ids ]; $page_ids = array_map( 'intval', $page_ids ); $page_ids = array_filter( $page_ids, function( $value ) { return $value > 0; } ); if ( empty( $page_ids ) || FICTIONEER_MAX_CUSTOM_PAGES_PER_STORY < 1 ) { $fields['fictioneer_story_custom_pages'] = []; // Ensure empty meta is removed } else { $page_ids = array_unique( $page_ids ); $page_ids = array_slice( $page_ids, 0, FICTIONEER_MAX_CUSTOM_PAGES_PER_STORY ); $pages_query = new WP_Query( array( 'post_type' => 'page', 'post__in' => $page_ids ?: [0], // Must not be empty! 'author' => $post_author_id, // Only allow author's pages 'orderby' => 'post__in', 'fields' => 'ids', 'posts_per_page' => FICTIONEER_MAX_CUSTOM_PAGES_PER_STORY, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $fields['fictioneer_story_custom_pages'] = array_map( 'strval', $pages_query->posts ); } } // Global note if ( isset( $_POST['fictioneer_story_global_note'] ) ) { $fields['fictioneer_story_global_note'] = fictioneer_sanitize_editor( $_POST['fictioneer_story_global_note'] ); } // Password note if ( isset( $_POST['fictioneer_story_password_note'] ) ) { $fields['fictioneer_story_password_note'] = fictioneer_sanitize_editor( $_POST['fictioneer_story_password_note'] ); } // ePUBs... if ( get_option( 'fictioneer_enable_epubs' ) ) { // Custom upload if ( isset( $_POST['fictioneer_story_ebook_upload_one'] ) && current_user_can( 'fcn_custom_epub_upload', $post_id ) ) { $ebook_id = absint( $_POST['fictioneer_story_ebook_upload_one'] ); if ( $ebook_id > 0 && wp_get_attachment_url( $ebook_id ) ) { $fields['fictioneer_story_ebook_upload_one'] = $ebook_id; } else { $fields['fictioneer_story_ebook_upload_one'] = 0; } } // ePUB preface if ( isset( $_POST['fictioneer_story_epub_preface'] ) ) { $fields['fictioneer_story_epub_preface'] = fictioneer_sanitize_editor( $_POST['fictioneer_story_epub_preface'] ); } // ePUB afterword if ( isset( $_POST['fictioneer_story_epub_afterword'] ) ) { $fields['fictioneer_story_epub_afterword'] = fictioneer_sanitize_editor( $_POST['fictioneer_story_epub_afterword'] ); } // Custom ePUB CSS if ( isset( $_POST['fictioneer_story_epub_custom_css'] ) && current_user_can( 'fcn_custom_epub_css', $post_id ) ) { $fields['fictioneer_story_epub_custom_css'] = fictioneer_sanitize_css( $_POST['fictioneer_story_epub_custom_css'] ); } } // --- Cleanup ----------------------------------------------------------------- global $wpdb; // Orphaned values $wpdb->query( $wpdb->prepare(" DELETE FROM $wpdb->postmeta WHERE meta_key = %s AND (meta_value = '' OR meta_value IS NULL OR meta_value = '0')", 'fictioneer_story_ebook_upload_one' ) ); // --- Filters --------------------------------------------------------------- $fields = apply_filters( 'fictioneer_filter_metabox_updates_story', $fields, $post_id ); // --- Save ------------------------------------------------------------------ foreach ( $fields as $key => $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } } add_action( 'save_post', 'fictioneer_save_story_metaboxes' ); // ============================================================================= // CHAPTER META FIELDS // ============================================================================= /** * Adds chapter meta metabox * * @since 5.7.4 */ function fictioneer_add_chapter_meta_metabox() { add_meta_box( 'fictioneer-chapter-meta', __( 'Chapter Meta', 'fictioneer' ), 'fictioneer_render_chapter_meta_metabox', ['fcn_chapter'], 'side', 'default' ); } add_action( 'add_meta_boxes', 'fictioneer_add_chapter_meta_metabox' ); /** * Render chapter meta metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_chapter_meta_metabox( $post ) { // --- Setup ----------------------------------------------------------------- $nonce = wp_create_nonce( "chapter_meta_data_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields ------------------------------------------------------------ if ( ! get_option( 'fictioneer_hide_chapter_icons' ) ) { $output['fictioneer_chapter_icon'] = fictioneer_get_metabox_icons( $post, 'fictioneer_chapter_icon', array( 'label' => _x( 'Icon', 'Chapter icon meta field label.', 'fictioneer' ), 'description' => sprintf( __( 'You can use all free Font Awesome icons.', 'fictioneer' ), 'https://fontawesome.com/search' ), 'placeholder' => FICTIONEER_DEFAULT_CHAPTER_ICON ) ); } if ( get_option( 'fictioneer_enable_advanced_meta_fields' ) && ! get_option( 'fictioneer_hide_chapter_icons' ) ) { $output['fictioneer_chapter_text_icon'] = fictioneer_get_metabox_text( $post, 'fictioneer_chapter_text_icon', array( 'label' => _x( 'Text Icon', 'Chapter text icon meta field label.', 'fictioneer' ), 'description' => __( 'Text string as icon; mind the limited space.', 'fictioneer' ) ) ); } if ( get_option( 'fictioneer_enable_advanced_meta_fields' ) ) { $output['fictioneer_chapter_short_title'] = fictioneer_get_metabox_text( $post, 'fictioneer_chapter_short_title', array( 'label' => _x( 'Short Title', 'Chapter short title meta field label.', 'fictioneer' ), 'description' => __( 'Shorter title, such as "Arc 15, Ch. 17". Not used by default, intended for child themes.', 'fictioneer' ) ) ); } if ( get_option( 'fictioneer_enable_advanced_meta_fields' ) ) { $output['fictioneer_chapter_prefix'] = fictioneer_get_metabox_text( $post, 'fictioneer_chapter_prefix', array( 'label' => _x( 'Prefix', 'Chapter prefix meta field label.', 'fictioneer' ), 'description' => __( 'Prefix, such as "Prologue" or "Act I ~ 01".', 'fictioneer' ) ) ); } if ( get_option( 'fictioneer_enable_advanced_meta_fields' ) ) { $output['fictioneer_chapter_co_authors'] = fictioneer_get_metabox_array( $post, 'fictioneer_chapter_co_authors', array( 'label' => _x( 'Co-Authors', 'Chapter co-authors meta field label.', 'fictioneer' ), 'description' => __( 'Comma-separated list of author IDs.', 'fictioneer' ) ) ); } $output['fictioneer_chapter_rating'] = fictioneer_get_metabox_select( $post, 'fictioneer_chapter_rating', array( '0' => __( '— Unset —', 'fictioneer' ), 'Everyone' => fcntr( 'Everyone' ), 'Teen' => fcntr( 'Teen' ), 'Mature' => fcntr( 'Mature' ), 'Adult' => fcntr( 'Adult' ) ), array( 'label' => _x( 'Age Rating', 'Chapter age rating meta field label.', 'fictioneer' ), 'description' => __( 'Optional age rating only for the chapter.', 'fictioneer' ) ) ); $output['fictioneer_chapter_warning'] = fictioneer_get_metabox_text( $post, 'fictioneer_chapter_warning', array( 'label' => _x( 'Warning', 'Chapter warning meta field label.', 'fictioneer' ), 'description' => __( 'Warning shown in chapter lists.', 'fictioneer' ), 'maxlength' => 48 ) ); $output['fictioneer_chapter_warning_notes'] = fictioneer_get_metabox_textarea( $post, 'fictioneer_chapter_warning_notes', array( 'label' => __( 'Warning Notes', 'fictioneer' ), 'description' => __( 'Warning note shown above chapters.', 'fictioneer' ) ) ); $output['flags_heading'] = '
    ' . __( 'Flags', 'Metabox checkbox heading.', 'fictioneer' ) . '
    '; $output['fictioneer_chapter_hidden'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_chapter_hidden', __( 'Unlisted (but accessible with link)', 'fictioneer' ) ); $output['fictioneer_chapter_no_chapter'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_chapter_no_chapter', __( 'Do not count as chapter', 'fictioneer' ) ); $output['fictioneer_chapter_hide_title'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_chapter_hide_title', __( 'Hide title in chapter', 'fictioneer' ) ); $output['fictioneer_chapter_hide_support_links'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_chapter_hide_support_links', __( 'Hide support links', 'fictioneer' ) ); if ( FICTIONEER_ENABLE_PARTIAL_CACHING && current_user_can( 'manage_options' ) ) { $output['fictioneer_chapter_disable_partial_caching'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_chapter_disable_partial_caching', __( 'Disable partial caching of content', 'fictioneer' ) ); } // --- Filters --------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_chapter_meta', $output, $post ); // --- Render ---------------------------------------------------------------- echo implode( '', $output ); // Start HTML ---> ?> ID}" ); // Accounts for manual wp_update_post() calls! $post_author_id = get_post_field( 'post_author', $post->ID ); $current_story_id = get_post_meta( $post->ID, 'fictioneer_chapter_story', true ); $output = []; // --- Add fields ------------------------------------------------------------ // Story $stories = array( '0' => _x( '— Unassigned —', 'Chapter story select option.', 'fictioneer' ) ); $description = __( 'Select the story this chapter belongs to; assign and order in the story.', 'fictioneer' ); $author_warning = ''; $selectable_stories_args = array( 'post_type' => 'fcn_story', 'post_status' => ['publish', 'private'], 'posts_per_page'=> -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ); if ( get_option( 'fictioneer_limit_chapter_stories_by_author' ) ) { $selectable_stories_args['author'] = $post_author_id; } $selectable_stories = new WP_Query( $selectable_stories_args ); if ( $selectable_stories->have_posts() ) { foreach ( $selectable_stories->posts as $story ) { if ( $story->post_status !== 'publish' ) { $stories[ $story->ID ] = sprintf( _x( '%s (%s)', 'Chapter story meta field option with status label.', 'fictioneer' ), fictioneer_get_safe_title( $story->ID, 'admin-render-chapter-data-metabox-selectable-suffix' ), fictioneer_get_post_status_label( $story->post_status ) ); } else { $stories[ $story->ID ] = fictioneer_get_safe_title( $story->ID, 'admin-render-chapter-data-metabox-selectable' ); } } } if ( $current_story_id ) { // Check unmatched story assignment... if ( ! array_key_exists( $current_story_id, $stories ) ) { $other_author_id = get_post_field( 'post_author', $current_story_id ); $suffix = []; // Other author if ( $other_author_id != $post_author_id ) { $suffix['author'] = get_the_author_meta( 'display_name', $other_author_id ); $author_warning = __( 'Warning: The selected story belongs to another author. If you change the selection, you cannot go back.', 'fictioneer' ); } // Other status if ( get_post_status( $current_story_id ) === 'publish' ) { $suffix['status'] = fictioneer_get_post_status_label( $story->post_status ); } // Prepare suffix if ( ! empty( $suffix ) ) { $suffix = ' (' . implode( ' | ', $suffix ) . ')'; } $stories[ $current_story_id ] = sprintf( _x( '%s %s', 'Chapter story meta field mismatched option with author and/or status label.', 'fictioneer' ), fictioneer_get_safe_title( $current_story_id, 'admin-render-chapter-data-metabox-current-suffix' ), $suffix ); } } if ( get_option( 'fictioneer_enable_chapter_appending' ) ) { $description = __( 'Select the story this chapter belongs to; chapters are automatically appended to the story (but not removed).', 'fictioneer' ); } $output['fictioneer_chapter_story'] = fictioneer_get_metabox_select( $post, 'fictioneer_chapter_story', $stories, array( 'label' => _x( 'Story', 'Chapter story meta field label.', 'fictioneer' ), 'description' => $description . ' ' . $author_warning, 'attributes' => array( 'data-action="select-story"', "data-target='chapter_groups_for_{$post->ID}'" ), 'asort' => 1, 'query_var' => 'story_id' ) ); // Card/List title $output['fictioneer_chapter_list_title'] = fictioneer_get_metabox_text( $post, 'fictioneer_chapter_list_title', array( 'label' => _x( 'Card/List Title', 'Chapter card/list title meta field label.', 'fictioneer' ), 'description' => __( 'Alternative title used in cards and lists. Useful for long titles that get truncated on small screens.', 'fictioneer' ) ) ); // Group $output['fictioneer_chapter_group'] = fictioneer_get_metabox_text( $post, 'fictioneer_chapter_group', array( 'label' => _x( 'Group', 'Chapter group meta field label.', 'fictioneer' ), 'description' => __( 'Organize chapters into groups; mind the order and spelling (case-sensitive). Only rendered if there are at least two, unassigned chapters are grouped under "Unassigned".', 'fictioneer' ), 'attributes' => array( "list='chapter_groups_for_{$post->ID}'" ) ) ); // Foreword $output['fictioneer_chapter_foreword'] = fictioneer_get_metabox_editor( $post, 'fictioneer_chapter_foreword', array( 'label' => _x( 'Foreword', 'Chapter foreword meta field label.', 'fictioneer' ), 'description' => __( 'Displayed in a box above the chapter; start with "[!show]" to show in protected chapters. Limited HTML allowed.', 'fictioneer' ) ) ); // Afterword $output['fictioneer_chapter_afterword'] = fictioneer_get_metabox_editor( $post, 'fictioneer_chapter_afterword', array( 'label' => _x( 'Afterword', 'Chapter afterword meta field label.', 'fictioneer' ), 'description' => __( 'Displayed in a box below the chapter; start with "[!show]" to show in protected chapters. Limited HTML allowed.', 'fictioneer' ) ) ); // Password note $output['fictioneer_chapter_password_note'] = fictioneer_get_metabox_editor( $post, 'fictioneer_chapter_password_note', array( 'label' => _x( 'Password Note', 'Chapter password note meta field label.', 'fictioneer' ), 'description' => __( 'Displayed for password protected content. Limited HTML allowed.', 'fictioneer' ) ) ); // --- Filters --------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_chapter_data', $output, $post ); // --- Render ---------------------------------------------------------------- echo implode( '', $output ); // Start HTML ---> ?> $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } } add_action( 'save_post', 'fictioneer_save_chapter_metaboxes' ); // ============================================================================= // EXTRA META FIELDS // ============================================================================= /** * Adds extra metabox * * @since 5.7.4 */ function fictioneer_add_extra_metabox() { add_meta_box( 'fictioneer-extra', __( 'Extra Meta', 'fictioneer' ), 'fictioneer_render_extra_metabox', ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_recommendation', 'fcn_collection'], 'side', 'default' ); } add_action( 'add_meta_boxes', 'fictioneer_add_extra_metabox' ); /** * Render extra metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_extra_metabox( $post ) { // --- Setup ----------------------------------------------------------------- $nonce = wp_create_nonce( "advanced_meta_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $output = []; $author_id = $post->post_author ?: get_current_user_id(); // --- Add fields ------------------------------------------------------------ // Landscape image $output['fictioneer_landscape_image'] = fictioneer_get_metabox_image( $post, 'fictioneer_landscape_image', array( 'label' => __( 'Landscape Image', 'fictioneer' ), 'description' => __( 'Used where the image is wider than high.', 'fictioneer' ), 'button' => __( 'Set landscape image', 'fictioneer' ) ) ); // Custom page header if ( current_user_can( 'fcn_custom_page_header', $post->ID ) ) { $output['fictioneer_custom_header_image'] = fictioneer_get_metabox_image( $post, 'fictioneer_custom_header_image', array( 'label' => __( 'Header Image', 'fictioneer' ), 'description' => __( 'Replaces the default header image.', 'fictioneer' ), 'button' => __( 'Set header image', 'fictioneer' ), ) ); } // Custom page CSS if ( current_user_can( 'fcn_custom_page_css', $post->ID ) ) { $output['fictioneer_custom_css'] = fictioneer_get_metabox_textarea( $post, 'fictioneer_custom_css', array( 'label' => __( 'Custom Page CSS', 'fictioneer' ), 'description' => __( 'Only applied to the page.', 'fictioneer' ), 'placeholder' => __( '.selector { ... }', 'fictioneer' ), 'input_classes' => 'fictioneer-meta-field__textarea--code' ) ); } if ( $post->post_type === 'page' ) { // Short name $output['fictioneer_short_name'] = fictioneer_get_metabox_text( $post, 'fictioneer_short_name', array( 'label' => _x( 'Short Name', 'Page short name meta field label.', 'fictioneer' ), 'description' => __( 'Required for the tab view in stories.', 'fictioneer' ) ) ); // Filter & Search ID if ( current_user_can( 'install_plugins' ) ) { $output['fictioneer_filter_and_search_id'] = fictioneer_get_metabox_text( $post, 'fictioneer_filter_and_search_id', array( 'label' => _x( 'Filter & Search ID', 'Page filter ans search meta field label.', 'fictioneer' ), 'description' => __( 'ID for a filter and/or search plugins.', 'fictioneer' ) ) ); } // Story ID if ( current_user_can( 'manage_options' ) ) { $output['fictioneer_template_story_id'] = fictioneer_get_metabox_text( $post, 'fictioneer_template_story_id', array( 'label' => _x( 'Story Id', 'Page story ID meta field label.', 'fictioneer' ), 'description' => __( 'Used by the "Story Page/Mirror" templates.', 'fictioneer' ) ) ); } // Checkbox: Show story header if ( current_user_can( 'manage_options' ) ) { $output['fictioneer_template_show_story_header'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_template_show_story_header', __( 'Show story header', 'fictioneer' ) ); } } // Story blogs if ( $post->post_type === 'post' ) { $user_stories = get_posts( array( 'post_type' => 'fcn_story', 'post_status' => ['publish', 'private'], 'author' => $author_id, 'posts_per_page' => -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); if ( ! empty( $user_stories ) ) { $blog_options = array( '0' => __( '— Story —', 'fictioneer' ) ); foreach ( $user_stories as $story ) { $blog_options[ $story->ID ] = $story->post_title; } $output['fictioneer_post_story_blogs'] = fictioneer_get_metabox_tokens( $post, 'fictioneer_post_story_blogs', $blog_options, array( 'label' => _x( 'Story Blogs', 'Story blogs meta field label.', 'fictioneer' ), 'description' => __( 'Select where the post should appear.', 'fictioneer' ) ) ); } } // Patreon $can_patreon = current_user_can( 'fcn_assign_patreon_tiers' ) || current_user_can( 'manage_options' ); if ( $can_patreon && get_option( 'fictioneer_enable_patreon_locks' ) ) { $patreon_client_id = get_option( 'fictioneer_patreon_client_id' ); $patreon_client_secret = get_option( 'fictioneer_patreon_client_secret' ); if ( get_option( 'fictioneer_enable_oauth' ) && $patreon_client_id && $patreon_client_secret ) { $patreon_tiers = get_option( 'fictioneer_connection_patreon_tiers' ); $patreon_tiers = is_array( $patreon_tiers ) ? $patreon_tiers : []; if ( ! empty( $patreon_tiers ) ) { $tier_options = array( '0' => __( '— Tier —', 'fictioneer' ) ); foreach ( $patreon_tiers as $tier ) { $tier_options[ $tier['id'] ] = sprintf( _x( '%s (%s)', 'Patreon tier meta field token (title and amount_cents).', 'fictioneer' ), $tier['title'], $tier['amount_cents'] ); } // Tiers $output['fictioneer_patreon_lock_tiers'] = fictioneer_get_metabox_tokens( $post, 'fictioneer_patreon_lock_tiers', $tier_options, array( 'label' => _x( 'Patreon Tiers', 'Patreon tiers meta field label.', 'fictioneer' ), 'description' => __( 'Select which tiers ignore the password.', 'fictioneer' ), 'names' => $tier_options ) ); // Threshold $output['fictioneer_patreon_lock_amount'] = fictioneer_get_metabox_number( $post, 'fictioneer_patreon_lock_amount', array( 'label' => _x( 'Patreon Amount Cents', 'Patreon amount cents meta field label.', 'fictioneer' ), 'description' => __( 'Pledge threshold to ignore the password.', 'fictioneer' ), 'attributes' => array( 'min="0"' ) ) ); } } } // Password expiration datetime if ( current_user_can( 'manage_options' ) || current_user_can( 'fcn_expire_passwords' ) ) { $output['fictioneer_post_password_expiration_date'] = fictioneer_get_metabox_datetime( $post, 'fictioneer_post_password_expiration_date', array( 'label' => _x( 'Expire Post Password', 'Password expiration meta field label.', 'fictioneer' ), 'description' => __( 'Removes the password after the date.', 'fictioneer' ), ) ); } // Checkbox: Disable new comments $flag_count = 0; $output['flags_heading'] = '
    ' . _x( 'Flags', 'Metabox checkbox heading.', 'fictioneer' ) . '
    '; if ( in_array( $post->post_type, ['post', 'page', 'fcn_story', 'fcn_chapter'] ) ) { $output['fictioneer_disable_commenting'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_disable_commenting', __( 'Disable new comments', 'fictioneer' ) ); $flag_count++; } // Checkbox: Disable new comments if ( current_user_can( 'manage_options' ) && get_theme_mod( 'sidebar_style', 'none' ) !== 'none' ) { $output['fictioneer_disable_sidebar'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_disable_sidebar', __( 'Disable sidebar', 'fictioneer' ) ); $flag_count++; } // Checkbox: Disable new comments if ( current_user_can( 'manage_options' ) ) { $output['fictioneer_disable_page_padding'] = fictioneer_get_metabox_checkbox( $post, 'fictioneer_disable_page_padding', __( 'Disable page padding', 'fictioneer' ) ); $flag_count++; } // Remove Flags heading if no flags are rendered if ( ! $flag_count ) { unset( $output['flags_heading'] ); } // --- Filters --------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_advanced', $output, $post ); // --- Render ---------------------------------------------------------------- echo implode( '', $output ); // Start HTML ---> ?> $post_author_id, 'post_type' => 'fcn_story', 'post_status' => ['publish', 'private'], 'post__in' => $story_blogs ?: [0], // Must not be empty! 'fields' => 'ids', 'posts_per_page'=> -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $story_blogs = $blog_story_query->posts; $story_blogs = array_map( 'strval', $story_blogs ); // Safer to match with LIKE in SQL } $fields['fictioneer_post_story_blogs'] = $story_blogs; } // Patreon $can_patreon = current_user_can( 'fcn_assign_patreon_tiers' ) || current_user_can( 'manage_options' ); if ( $can_patreon && get_option( 'fictioneer_enable_patreon_locks' ) ) { $patreon_client_id = get_option( 'fictioneer_patreon_client_id' ); $patreon_client_secret = get_option( 'fictioneer_patreon_client_secret' ); $patreon_tiers = get_option( 'fictioneer_connection_patreon_tiers' ); $patreon_tiers = is_array( $patreon_tiers ) ? $patreon_tiers : []; if ( get_option( 'fictioneer_enable_oauth' ) && $patreon_client_id && $patreon_client_secret && $patreon_tiers ) { // Tiers if ( isset( $_POST['fictioneer_patreon_lock_tiers'] ) ) { $selected_patreon_tiers = fictioneer_explode_list( $_POST['fictioneer_patreon_lock_tiers'] ); if ( ! empty( $patreon_tiers ) ) { $selected_patreon_tiers = array_intersect( $selected_patreon_tiers, array_keys( $patreon_tiers ) ); $selected_patreon_tiers = array_map( 'absint', $selected_patreon_tiers ); $selected_patreon_tiers = array_unique( $selected_patreon_tiers ); $selected_patreon_tiers = array_map( 'strval', $selected_patreon_tiers ); // Safer to match with LIKE in SQL } $fields['fictioneer_patreon_lock_tiers'] = $selected_patreon_tiers; } // Threshold if ( isset( $_POST['fictioneer_patreon_lock_amount'] ) ) { $fields['fictioneer_patreon_lock_amount'] = absint( $_POST['fictioneer_patreon_lock_amount'] ); } } } // Password expiration datetime $can_expire_passwords = current_user_can( 'manage_options' ) || current_user_can( 'fcn_expire_passwords' ); if ( $can_expire_passwords && isset( $_POST['fictioneer_post_password_expiration_date'] ) ) { $expiration_date = $_POST['fictioneer_post_password_expiration_date']; if ( ! empty( $expiration_date ) ) { $local_datetime = new DateTime( $expiration_date, wp_timezone() ); $local_datetime->setTimezone( new DateTimeZone( 'UTC' ) ); $fields['fictioneer_post_password_expiration_date'] = $local_datetime->format( 'Y-m-d H:i:s' ); } else { $fields['fictioneer_post_password_expiration_date'] = 0; } } // Checkbox: Disable new comments if ( isset( $_POST['fictioneer_disable_commenting'] ) && in_array( $post_type, ['post', 'page', 'fcn_story', 'fcn_chapter'] ) ) { $fields['fictioneer_disable_commenting'] = fictioneer_sanitize_checkbox( $_POST['fictioneer_disable_commenting'] ); } // Checkbox: Disable sidebar if ( isset( $_POST['fictioneer_disable_sidebar'] ) && current_user_can( 'manage_options' ) ) { $fields['fictioneer_disable_sidebar'] = fictioneer_sanitize_checkbox( $_POST['fictioneer_disable_sidebar'] ); } // Checkbox: Disable padding if ( isset( $_POST['fictioneer_disable_page_padding'] ) && current_user_can( 'manage_options' ) ) { $fields['fictioneer_disable_page_padding'] = fictioneer_sanitize_checkbox( $_POST['fictioneer_disable_page_padding'] ); } // --- Filters ----------------------------------------------------------------- $fields = apply_filters( 'fictioneer_filter_metabox_updates_advanced', $fields, $post_id ); // --- Save -------------------------------------------------------------------- foreach ( $fields as $key => $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } } add_action( 'save_post', 'fictioneer_save_extra_metabox' ); // ============================================================================= // SUPPORT LINKS META FIELDS // ============================================================================= /** * Adds support links metabox * * @since 5.7.4 */ function fictioneer_add_support_links_metabox() { add_meta_box( 'fictioneer-support-links', __( 'Support Links', 'fictioneer' ), 'fictioneer_render_support_links_metabox', ['post', 'fcn_story', 'fcn_chapter'], 'side', 'low' ); } add_action( 'add_meta_boxes', 'fictioneer_add_support_links_metabox' ); /** * Render support links metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_support_links_metabox( $post ) { // --- Setup ------------------------------------------------------------------- $nonce = wp_create_nonce( "support_links_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields -------------------------------------------------------------- $output['fictioneer_patreon_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_patreon_link', array( 'label' => _x( 'Patreon Link', 'Patreon link meta field label.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); $output['fictioneer_kofi_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_kofi_link', array( 'label' => _x( 'Ko-fi Link', 'Ko-fi link meta field label.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); $output['fictioneer_subscribestar_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_subscribestar_link', array( 'label' => _x( 'SubscribeStar Link', 'SubscribeStar link meta field label.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); $output['fictioneer_paypal_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_paypal_link', array( 'label' => _x( 'Paypal Link', 'Paypal link meta field label.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); $output['fictioneer_donation_link'] = fictioneer_get_metabox_url( $post, 'fictioneer_donation_link', array( 'label' => _x( 'Donation Link', 'Donation link meta field label.', 'fictioneer' ), 'placeholder' => 'https://...' ) ); // --- Filters ----------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_support_links', $output, $post ); // --- Render ------------------------------------------------------------------ echo implode( '', $output ); // Start HTML ---> ?> $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } } add_action( 'save_post', 'fictioneer_save_support_links_metabox' ); // ============================================================================= // BLOG POST META FIELDS // ============================================================================= /** * Adds featured content side metabox * * @since 5.7.4 */ function fictioneer_add_featured_content_metabox() { add_meta_box( 'fictioneer-featured-content', __( 'Featured Content', 'fictioneer' ), 'fictioneer_render_featured_content_metabox', 'post', 'normal', 'default' ); } add_action( 'add_meta_boxes', 'fictioneer_add_featured_content_metabox' ); /** * Render featured content metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_featured_content_metabox( $post ) { // --- Setup ------------------------------------------------------------------- $nonce = wp_create_nonce( "post_data_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields -------------------------------------------------------------- // Featured items $item_ids = get_post_meta( $post->ID, 'fictioneer_post_featured', true ) ?: []; $item_ids = is_array( $item_ids ) ? $item_ids : []; $items = empty( $item_ids ) ? [] : get_posts( array( 'post_type' => ['post', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'], 'post_status' => 'any', 'post__in' => $item_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'posts_per_page' => -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $output['fictioneer_post_featured'] = fictioneer_get_metabox_relationships( $post, 'fictioneer_post_featured', $items, 'fictioneer_callback_relationship_featured', array( 'description' => __( 'Select posts to be shown as cards below the content.', 'fictioneer' ), 'post_types' => array( 'excluded' => ['attachment', 'page'] ) ) ); // --- Filters ----------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_post_data', $output, $post ); // --- Render ------------------------------------------------------------------ echo implode( '', $output ); // Start HTML ---> ?> 0; } ); $item_ids = array_unique( $item_ids ); if ( empty( $item_ids ) ) { $fields['fictioneer_post_featured'] = []; // Ensure empty meta is removed } else { // Make sure only allowed posts are in $items_query = new WP_Query( array( 'post_type' => ['post', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'], 'post_status' => 'publish', 'post__in' => $item_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'fields' => 'ids', 'posts_per_page' => -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $fields['fictioneer_post_featured'] = array_map( 'strval', $items_query->posts ); } } // --- Filters ----------------------------------------------------------------- $fields = apply_filters( 'fictioneer_filter_metabox_updates_post', $fields, $post_id ); // --- Save -------------------------------------------------------------------- foreach ( $fields as $key => $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } // --- After update ------------------------------------------------------------ // Update relationship registry if ( isset( $_POST['fictioneer_post_featured'] ) ) { fictioneer_update_post_relationship_registry( $post_id ); } } add_action( 'save_post', 'fictioneer_save_post_metaboxes' ); /** * Update relationship registry for 'post' post types * * @since 5.0.0 * * @param int $post_id The post ID. */ function fictioneer_update_post_relationship_registry( $post_id ) { // Setup $registry = fictioneer_get_relationship_registry(); $featured = get_post_meta( $post_id, 'fictioneer_post_featured', true ); // Update relationships $registry[ $post_id ] = []; if ( is_array( $featured ) && ! empty( $featured ) ) { foreach ( $featured as $featured_id ) { $registry[ $post_id ][ $featured_id ] = 'is_featured'; if ( ! isset( $registry[ $featured_id ] ) ) { $registry[ $featured_id ] = []; } $registry[ $featured_id ][ $post_id ] = 'featured_by'; } } else { $featured = []; } // Check for and remove outdated direct references foreach ( $registry as $key => $entry ) { // Skip if... if ( absint( $key ) < 1 || ! is_array( $entry ) || in_array( $key, $featured ) ) { continue; } // Unset if in array unset( $registry[ $key ][ $post_id ] ); // Remove node if empty if ( empty( $registry[ $key ] ) ) { unset( $registry[ $key ] ); } } // Update database fictioneer_save_relationship_registry( $registry ); } // ============================================================================= // COLLECTION META FIELDS // ============================================================================= /** * Adds collection data metabox * * @since 5.7.4 */ function fictioneer_add_collection_data_metabox() { add_meta_box( 'fictioneer-collection-data', __( 'Collection Data', 'fictioneer' ), 'fictioneer_render_collection_data_metabox', 'fcn_collection', 'normal', 'default' ); } add_action( 'add_meta_boxes', 'fictioneer_add_collection_data_metabox' ); /** * Render collection data metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_collection_data_metabox( $post ) { // --- Setup ------------------------------------------------------------------- $nonce = wp_create_nonce( "collection_data_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields -------------------------------------------------------------- // Card/List title $output['fictioneer_collection_list_title'] = fictioneer_get_metabox_text( $post, 'fictioneer_collection_list_title', array( 'label' => _x( 'Card/List Title', 'Collection card/list title meta field label.', 'fictioneer' ), 'description' => __( 'Alternative title used in cards and lists. Useful for long titles that get truncated on small screens.', 'fictioneer' ) ) ); // Short description $output['fictioneer_collection_description'] = fictioneer_get_metabox_editor( $post, 'fictioneer_collection_description', array( 'label' => _x( 'Short Description', 'Collection short description meta field label.', 'fictioneer' ), 'description' => __( 'The short description is used on cards, so best keep it under 100 words or 400 characters.', 'fictioneer' ), 'required' => 1 ) ); // Collection items $item_ids = get_post_meta( $post->ID, 'fictioneer_collection_items', true ) ?: []; $item_ids = is_array( $item_ids ) ? $item_ids : []; $items = empty( $item_ids ) ? [] : get_posts( array( 'post_type' => ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'], 'post_status' => 'any', 'post__in' => $item_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'posts_per_page' => -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $output['fictioneer_collection_items'] = fictioneer_get_metabox_relationships( $post, 'fictioneer_collection_items', $items, 'fictioneer_callback_relationship_collection', array( 'label' => _x( 'Collection Items', 'Collection Items meta field label.', 'fictioneer' ), 'description' => __( 'Select the posts you want to be featured. If you add a story, do not add its chapters separately.', 'fictioneer' ), 'post_types' => array( 'excluded' => ['attachment'] ) ) ); // --- Filters ----------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_collection_data', $output, $post ); // --- Render ------------------------------------------------------------------ echo implode( '', $output ); // Start HTML ---> ?> 0; } ); $item_ids = array_unique( $item_ids ); $forbidden = array_unique( array( get_option( 'fictioneer_user_profile_page', 0 ), get_option( 'fictioneer_bookmarks_page', 0 ), get_option( 'fictioneer_stories_page', 0 ), get_option( 'fictioneer_chapters_page', 0 ), get_option( 'fictioneer_recommendations_page', 0 ), get_option( 'fictioneer_collections_page', 0 ), get_option( 'fictioneer_bookshelf_page', 0 ), get_option( 'fictioneer_404_page', 0 ), get_option( 'page_on_front', 0 ), get_option( 'page_for_posts', 0 ) ) ); $item_ids = array_diff( $item_ids, array_map( 'strval', $forbidden ) ); if ( empty( $item_ids ) ) { $fields['fictioneer_collection_items'] = []; // Ensure empty meta is removed } else { // Make sure only allowed posts are in $items_query = new WP_Query( array( 'post_type' => ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'], 'post_status' => 'publish', 'post__in' => $item_ids ?: [0], // Must not be empty! 'orderby' => 'post__in', 'fields' => 'ids', 'posts_per_page' => -1, 'update_post_meta_cache' => false, // Improve performance 'update_post_term_cache' => false, // Improve performance 'no_found_rows' => true // Improve performance ) ); $fields['fictioneer_collection_items'] = array_map( 'strval', $items_query->posts ); } } // --- Filters ----------------------------------------------------------------- $fields = apply_filters( 'fictioneer_filter_metabox_updates_collection', $fields, $post_id ); // --- Save -------------------------------------------------------------------- foreach ( $fields as $key => $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } } add_action( 'save_post', 'fictioneer_save_collection_metaboxes' ); // ============================================================================= // RECOMMENDATION META FIELDS // ============================================================================= /** * Adds recommendation data metabox * * @since 5.7.4 */ function fictioneer_add_recommendation_data_metabox() { add_meta_box( 'fictioneer-recommendation-data', __( 'Recommendation Data', 'fictioneer' ), 'fictioneer_render_recommendation_data_metabox', 'fcn_recommendation', 'normal', 'default' ); } add_action( 'add_meta_boxes', 'fictioneer_add_recommendation_data_metabox' ); /** * Render recommendation data metabox * * @since 5.7.4 * * @param WP_Post $post The current post object. */ function fictioneer_render_recommendation_data_metabox( $post ) { // --- Setup ------------------------------------------------------------------- $nonce = wp_create_nonce( "recommendation_data_{$post->ID}" ); // Accounts for manual wp_update_post() calls! $output = []; // --- Add fields -------------------------------------------------------------- // One sentence $output['fictioneer_recommendation_one_sentence'] = fictioneer_get_metabox_text( $post, 'fictioneer_recommendation_one_sentence', array( 'label' => _x( 'One Sentence', 'Recommendation one sentence meta field label.', 'fictioneer' ), 'description' => __( 'Elevator pitch with 150 characters are less. For example: "Rebellious corporate heiress and her genius friend commit high-tech heists in a doomed city."', 'fictioneer' ), 'required' => 1 ) ); // Author $output['fictioneer_recommendation_author'] = fictioneer_get_metabox_text( $post, 'fictioneer_recommendation_author', array( 'label' => _x( 'Author', 'Recommendation author meta field label.', 'fictioneer' ), 'description' => __( 'The name of the author. Separate multiple authors with commas.', 'fictioneer' ), 'required' => 1 ) ); // Primary URL $output['fictioneer_recommendation_primary_url'] = fictioneer_get_metabox_url( $post, 'fictioneer_recommendation_primary_url', array( 'label' => _x( 'Primary Link', 'Recommendation primary link meta field label.', 'fictioneer' ), 'description' => __( "Primary link to the recommendation or author's website.", 'fictioneer' ), 'required' => 1 ) ); // URLs $output['fictioneer_recommendation_urls'] = fictioneer_get_metabox_textarea( $post, 'fictioneer_recommendation_urls', array( 'label' => _x( 'Additional Links', 'Recommendation additional links meta field label.', 'fictioneer' ), 'description' => __( 'Links to the story, one link per line and mind the whitespaces. Format: "Link Name | https://www.address.abc" (without quotes).', 'fictioneer' ) ) ); // Support $output['fictioneer_recommendation_support'] = fictioneer_get_metabox_textarea( $post, 'fictioneer_recommendation_support', array( 'label' => _x( 'Support', 'Recommendation support meta field label.', 'fictioneer' ), 'description' => __( 'Link to the author\'s Patreon, Ko-Fi, etc. One link per line and mind the whitespaces. Format: "Link Name | https://www.address.abc" (without quotes).', 'fictioneer' ) ) ); // --- Filters ----------------------------------------------------------------- $output = apply_filters( 'fictioneer_filter_metabox_recommendation_data', $output, $post ); // --- Render ------------------------------------------------------------------ echo implode( '', $output ); // Start HTML ---> ?> $value ) { fictioneer_update_post_meta( $post_id, $key, $value ?? 0 ); // Add, update, or delete (if falsy) } } add_action( 'save_post', 'fictioneer_save_recommendation_metaboxes' ); // ============================================================================= // PATREON LIST VIEW AND BULK EDIT // ============================================================================= /** * Hide Patreon columns in list table views by default * * @since 5.17.0 * * @param array $hidden Array of IDs of columns hidden by default. * @param WP_Screen $screen WP_Screen object of the current screen. * * @return array Updated array of IDs. */ function fictioneer_default_hide_patreon_posts_columns( $hidden, $screen ) { if ( in_array( $screen->post_type, ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'] ) ) { $hidden[] = 'fictioneer_patreon_lock_tiers'; $hidden[] = 'fictioneer_patreon_lock_amount'; } return $hidden; } add_filter( 'default_hidden_columns', 'fictioneer_default_hide_patreon_posts_columns', 10, 2 ); /** * Add Patreon columns to list table views * * @since 5.17.0 * * @param array $post_columns An associative array of column headings. * * @return array Updated associative array of column headings. */ function fictioneer_add_posts_columns_patreon( $post_columns ) { $post_columns[ 'fictioneer_patreon_lock_tiers' ] = _x( 'Patreon Tiers', 'Patreon tiers list table column title.', 'fictioneer' ); $post_columns[ 'fictioneer_patreon_lock_amount' ] = _x( 'Patreon Cents', 'Patreon amount cents list table column title.', 'fictioneer' ); return $post_columns; } /** * Output Patreon values in list table views * * @since 5.17.0 * * @param string $column_name The name of the column to display. * @param int $post_id The current post ID. */ function fictioneer_manage_posts_column_patreon( $column_name, $post_id ) { // Setup $post = get_post( $post_id ); $class = $post->post_password ? 'has-password' : 'no-password'; // Output switch( $column_name ) { case 'fictioneer_patreon_lock_tiers': static $patreon_tiers = null; if ( ! $patreon_tiers ) { $patreon_tiers = get_option( 'fictioneer_connection_patreon_tiers' ); $patreon_tiers = is_array( $patreon_tiers ) ? $patreon_tiers : []; } $post_tiers = get_post_meta( $post_id, 'fictioneer_patreon_lock_tiers', true ) ?: []; $post_tiers = is_array( $post_tiers ) ? $post_tiers : []; foreach ( $post_tiers as $key => $tier ) { $tier_data = $patreon_tiers[ $tier ] ?? 0; if ( $tier_data ) { $post_tiers[ $key ] = $tier_data['title'] === 'Free' ? fcntr( 'free_patreon_tier' ) : $tier_data['title']; } } echo $post_tiers ? '' . implode( ', ', $post_tiers ) . '' : '—'; break; case 'fictioneer_patreon_lock_amount': $amount = get_post_meta( $post_id, 'fictioneer_patreon_lock_amount', true ); echo $amount ? '' . $amount . '' : '—'; break; } } /** * Add Patreon tiers to bulk edit * * @since 5.17.0 * * @param string $column_name Name of the column to edit. * @param string $post_type The post type slug. */ function fictioneer_add_patreon_bulk_edit_tiers( $column_name, $post_type ) { // Check column and post type if ( $column_name !== 'fictioneer_patreon_lock_tiers' || ! in_array( $post_type, ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'] ) ) { return; } // Setup $patreon_tiers = get_option( 'fictioneer_connection_patreon_tiers' ); $patreon_tiers = is_array( $patreon_tiers ) ? $patreon_tiers : []; // Tiers? if ( ! $patreon_tiers ) { return; } // Prepare options $tier_options = []; foreach ( $patreon_tiers as $tier ) { $tier_options[ $tier['id'] ] = sprintf( _x( '%s (%s)', 'Patreon tier meta field token (title and amount_cents).', 'fictioneer' ), $tier['title'], $tier['amount_cents'] ); } // Start HTML ---> ?>
    $tier ) : ?>
    ?>
    ?>

    _remove to remove current values.', 'fictioneer' ); ?>