Add native story metabox

This commit is contained in:
Tetrakern 2023-09-20 00:18:30 +02:00
parent d3b6e51b5b
commit 7c7659f486
5 changed files with 640 additions and 19 deletions

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,12 @@ require_once __DIR__ . '/_cleanup.php';
require_once __DIR__ . '/settings/_settings.php';
/**
* Add meta fields to editor.
*/
require_once __DIR__ . '/_meta_fields.php';
/**
* Extend user profile in admin panel.
*/

View File

@ -0,0 +1,441 @@
<?php
// =============================================================================
// META FIELD HELPERS
// =============================================================================
function fictioneer_get_metabox_checkbox( $post, $meta_key, $label, $args = [] ) {
// Setup
$required = ( $args['required'] ?? 0 ) ? 'required' : '';
$data_required = $required ? 'data-required="true"' : '';
ob_start();
// Start HTML ---> ?>
<label class="fictioneer-metabox-checkbox" for="<?php echo $meta_key; ?>" <?php echo $data_required; ?>>
<div class="fictioneer-metabox-checkbox__checkbox">
<input type="hidden" name="<?php echo $meta_key; ?>" value="0" autocomplete="off">
<input type="checkbox" id="<?php echo $meta_key; ?>" name="<?php echo $meta_key; ?>" value="1" autocomplete="off" <?php checked( get_post_meta( $post->ID, $meta_key, true ), 1 ); ?> <?php echo $required; ?>>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" focusable="false"><path d="M16.7 7.1l-6.3 8.5-3.3-2.5-.9 1.2 4.5 3.4L17.9 8z"></path></svg>
</div>
<div class="fictioneer-metabox-checkbox__label"><?php echo $label; ?></div>
</label>
<?php // <--- End HTML
return ob_get_clean();
}
function fictioneer_get_metabox_text( $post, $meta_key, $label, $description = null, $args = [] ) {
// Setup
$meta_value = esc_attr( get_post_meta( $post->ID, $meta_key, true ) );
$required = ( $args['required'] ?? 0 ) ? 'required' : '';
$data_required = $required ? 'data-required="true"' : '';
ob_start();
// Start HTML ---> ?>
<div class="fictioneer-metabox-text" <?php echo $data_required; ?>>
<label class="fictioneer-metabox-text__label" for="<?php echo $meta_key; ?>"><?php echo $label; ?></label>
<input type="hidden" name="<?php echo $meta_key; ?>" value="0" autocomplete="off">
<input type="text" id="<?php echo $meta_key; ?>" class="fictioneer-metabox-text__input" name="<?php echo $meta_key; ?>" value="<?php echo $meta_value; ?>" autocomplete="off" <?php echo $required; ?>>
<?php if ( $description ) : ?>
<div class="fictioneer-metabox-text__description"><?php echo $description; ?></div>
<?php endif; ?>
</div>
<?php // <--- End HTML
return ob_get_clean();
}
function fictioneer_get_metabox_url( $post, $meta_key, $label, $description = null, $args = [] ) {
// Setup
$meta_value = esc_attr( get_post_meta( $post->ID, $meta_key, true ) );
$required = ( $args['required'] ?? 0 ) ? 'required' : '';
$data_required = $required ? 'data-required="true"' : '';
ob_start();
// Start HTML ---> ?>
<div class="fictioneer-metabox-url" <?php echo $data_required; ?>>
<label class="fictioneer-metabox-url__label" for="<?php echo $meta_key; ?>"><?php echo $label; ?></label>
<input type="hidden" name="<?php echo $meta_key; ?>" value="0" autocomplete="off">
<div class="fictioneer-metabox-url__wrapper">
<span class="fictioneer-metabox-url__icon dashicons dashicons-admin-site"></span>
<input type="url" id="<?php echo $meta_key; ?>" class="fictioneer-metabox-url__input" name="<?php echo $meta_key; ?>" value="<?php echo $meta_value; ?>" autocomplete="off" <?php echo $required; ?>>
</div>
<?php if ( $description ) : ?>
<div class="fictioneer-metabox-url__description"><?php echo $description; ?></div>
<?php endif; ?>
</div>
<?php // <--- End HTML
return ob_get_clean();
}
function fictioneer_get_metabox_array( $post, $meta_key, $label, $description = null, $args = [] ) {
// Setup
$array = get_post_meta( $post->ID, $meta_key, true );
$array = is_array( $array ) ? $array : [];
$list = esc_attr( implode( ', ', $array ) );
$required = ( $args['required'] ?? 0 ) ? 'required' : '';
$data_required = $required ? 'data-required="true"' : '';
ob_start();
// Start HTML ---> ?>
<div class="fictioneer-metabox-text" <?php echo $data_required; ?>>
<label class="fictioneer-metabox-text__label" for="<?php echo $meta_key; ?>"><?php echo $label; ?></label>
<input type="hidden" name="<?php echo $meta_key; ?>" value="0" autocomplete="off">
<input type="text" id="<?php echo $meta_key; ?>" class="fictioneer-metabox-text__input" name="<?php echo $meta_key; ?>" value="<?php echo $list; ?>" autocomplete="off" <?php echo $required; ?>>
<?php if ( $description ) : ?>
<div class="fictioneer-metabox-text__description"><?php echo $description; ?></div>
<?php endif; ?>
</div>
<?php // <--- End HTML
return ob_get_clean();
}
function fictioneer_get_metabox_select( $post, $meta_key, $options, $label, $description = null, $args = [] ) {
// Setup
$selected = get_post_meta( $post->ID, $meta_key, true );
$selected = empty( $selected ) ? array_keys( $options )[0] : $selected;
$required = ( $args['required'] ?? 0 ) ? 'required' : '';
$data_required = $required ? 'data-required="true"' : '';
ob_start();
// Start HTML ---> ?>
<div class="fictioneer-metabox-select" <?php echo $data_required; ?>>
<label class="fictioneer-metabox-select__label" for="<?php echo $meta_key; ?>"><?php echo $label; ?></label>
<input type="hidden" name="<?php echo $meta_key; ?>" value="0" autocomplete="off">
<div class="fictioneer-metabox-select__wrapper">
<select id="<?php echo $meta_key; ?>" class="fictioneer-metabox-select__select" name="<?php echo $meta_key; ?>" autocomplete="off" <?php echo $required; ?>><?php
foreach ( $options as $value => $translation ) {
$value = esc_attr( $value );
$translation = esc_html( $translation );
echo "<option value='{$value}' " . selected( $selected, $value, false ) . ">{$translation}</option>";
}
?></select>
</div>
<?php if ( $description ) : ?>
<div class="fictioneer-metabox-select__description"><?php echo $description; ?></div>
<?php endif; ?>
</div>
<?php // <--- End HTML
return ob_get_clean();
}
function fictioneer_get_metabox_textarea( $post, $meta_key, $label, $description = null, $args = [] ) {
// Setup
$meta_value = get_post_meta( $post->ID, $meta_key, true );
$required = ( $args['required'] ?? 0 ) ? 'required' : '';
$data_required = $required ? 'data-required="true"' : '';
ob_start();
// Start HTML ---> ?>
<div class="fictioneer-metabox-textarea" <?php echo $data_required; ?>>
<label class="fictioneer-metabox-textarea__label" for="<?php echo $meta_key; ?>"><?php echo $label; ?></label>
<input type="hidden" name="<?php echo $meta_key; ?>" value="0" autocomplete="off">
<div class="fictioneer-metabox-textarea__wrapper">
<textarea id="<?php echo $meta_key; ?>" class="fictioneer-metabox-textarea__textarea" name="<?php echo $meta_key; ?>" autocomplete="off" <?php echo $required; ?>><?php echo $meta_value; ?></textarea>
</div>
<?php if ( $description ) : ?>
<div class="fictioneer-metabox-textarea__description"><?php echo $description; ?></div>
<?php endif; ?>
</div>
<?php // <--- End HTML
return ob_get_clean();
}
// =============================================================================
// STORY META FIELDS
// =============================================================================
/**
* Adds story metabox
*
* @since Fictioneer 5.7.4
*/
function fictioneer_add_story_meta_metabox() {
add_meta_box(
'fictioneer-story-meta',
__( 'Story Meta', 'fictioneer' ),
'fictioneer_render_story_metabox',
['fcn_story'],
'side',
'high'
);
}
add_action( 'add_meta_boxes', 'fictioneer_add_story_meta_metabox' );
/**
* Append classes to the story metabox
*
* @since Fictioneer 5.7.4
*
* @param array $classes An array of postbox classes.
*
* @return array The modified array of postbox classes.
*/
function fictioneer_append_story_metabox_classes( $classes ) {
// Add class
$classes[] = 'fictioneer-side-metabox';
// Return with added class
return $classes;
}
add_filter( 'postbox_classes_fcn_story_fictioneer-story-meta', 'fictioneer_append_story_metabox_classes' );
/**
* Render story metabox
*
* @since Fictioneer 5.7.4
*
* @param WP_Post $post The current post object.
*/
function fictioneer_render_story_metabox( $post ) {
// --- Setup -------------------------------------------------------------------
$nonce = wp_create_nonce( 'fictioneer_metabox_nonce' );
$output = [];
// --- Render nonce ------------------------------------------------------------
// Start HTML ---> ?>
<input type="hidden" name="fictioneer_metabox_nonce" value="<?php echo esc_attr( $nonce ); ?>" autocomplete="off">
<?php // <--- End HTML
// --- 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' )
),
_x( 'Status', 'Story status meta field label.', 'fictioneer' ),
__( 'Current status of the story.', 'fictioneer' ),
array( 'required' => 1 )
);
$output['fictioneer_story_rating'] = fictioneer_get_metabox_select(
$post,
'fictioneer_story_rating',
array(
'Everyone' => _x( 'Everyone', 'Story age rating select option.', 'fictioneer' ),
'Teen' => _x( 'Teen', 'Story age rating select option.', 'fictioneer' ),
'Mature' => _x( 'Mature', 'Story age rating select option.', 'fictioneer' ),
'Adult' => _x( 'Adult', 'Story age rating select option.', 'fictioneer' )
),
_x( 'Age Rating', 'Story age rating meta field label.', 'fictioneer' ),
__( 'Select an overall age rating.', 'fictioneer' ),
array( 'required' => 1 )
);
$output['fictioneer_story_co_authors'] = fictioneer_get_metabox_array(
$post,
'fictioneer_story_co_authors',
_x( 'Co-Authors', 'Story co-authors meta field label.', 'fictioneer' ),
__( 'Comma-separated list of author IDs.', 'fictioneer' )
);
$output['fictioneer_story_copyright_notice'] = fictioneer_get_metabox_text(
$post,
'fictioneer_story_copyright_notice',
_x( 'Copyright Notice', 'Story copyright notice meta field label.', 'fictioneer' ),
__( 'Optional, leave empty to hide.', 'fictioneer' )
);
$output['fictioneer_story_topwebfiction_link'] = fictioneer_get_metabox_url(
$post,
'fictioneer_story_topwebfiction_link',
_x( 'Top Web Fiction Link', 'Story top web fiction link meta field label.', 'fictioneer' ),
__( 'URL to Top Web Fiction page.', '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 tags 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',
__( 'Custom Story CSS', 'fictioneer' )
);
}
// --- Filters -----------------------------------------------------------------
$output = apply_filters( 'fictioneer_filter_story_sidebar_meta_fields', $output, $post );
// --- Render ------------------------------------------------------------------
echo implode( '', $output );
}
/**
* Save story metabox data
*
* @since Fictioneer 4.0
*
* @param int $post_id The post ID.
*/
function fictioneer_save_story_metabox( $post_id ) {
// --- Verify ------------------------------------------------------------------
if (
! wp_verify_nonce( ( $_POST['fictioneer_metabox_nonce'] ?? '' ), 'fictioneer_metabox_nonce' ) ||
fictioneer_multi_save_guard( $post_id ) ||
get_post_type( $post_id ) !== 'fcn_story'
) {
return;
}
// --- Permissions? ------------------------------------------------------------
if (
! current_user_can( 'edit_fcn_stories', $post_id ) ||
( get_post_status( $post_id ) === 'publish' && ! current_user_can( 'edit_published_fcn_stories', $post_id ) )
) {
return;
}
// --- Sanitize and add data ---------------------------------------------------
$allowed_statuses = ['Ongoing', 'Completed', 'Oneshot', 'Hiatus', 'Canceled'];
$status = fictioneer_sanitize_selection( $_POST['fictioneer_story_status'] ?? '', $allowed_statuses, $allowed_statuses[0] );
$allowed_ratings = ['Everyone', 'Teen', 'Mature', 'Adult'];
$rating = fictioneer_sanitize_selection( $_POST['fictioneer_story_rating'] ?? '', $allowed_ratings, $allowed_ratings[0] );
$fields = array(
'fictioneer_story_status' => $status,
'fictioneer_story_rating' => $rating,
'fictioneer_story_copyright_notice' => sanitize_text_field( $_POST['fictioneer_story_copyright_notice'] ?? '' ),
'fictioneer_story_hidden' => fictioneer_sanitize_checkbox( $_POST['fictioneer_story_hidden'] ?? 0 ),
'fictioneer_story_no_thumbnail' => fictioneer_sanitize_checkbox( $_POST['fictioneer_story_no_thumbnail'] ?? 0 ),
'fictioneer_story_no_tags' => fictioneer_sanitize_checkbox( $_POST['fictioneer_story_no_tags'] ?? 0 ),
);
$twf_url = sanitize_url( $_POST['fictioneer_story_topwebfiction_link'] ?? '' );
$twf_url = filter_var( $twf_url, FILTER_VALIDATE_URL ) ? $twf_url : '';
$twf_url = strpos( $twf_url, 'https://topwebfiction.com/') === 0 ? $twf_url : '';
$fields['fictioneer_story_topwebfiction_link'] = $twf_url;
$co_authors = fictioneer_explode_list( $_POST['fictioneer_story_co_authors'] ?? '' );
$co_authors = array_map( 'absint', $co_authors );
$fields['fictioneer_story_co_authors'] = array_unique( $co_authors );
if ( current_user_can( 'fcn_make_sticky', $post_id ) && FICTIONEER_ENABLE_STICKY_CARDS ) {
$fields['fictioneer_story_sticky'] = fictioneer_sanitize_checkbox( $_POST['fictioneer_story_sticky'] ?? 0 );
}
if ( get_option( 'fictioneer_enable_epubs' ) ) {
$fields['fictioneer_story_no_epub'] = fictioneer_sanitize_checkbox( $_POST['fictioneer_story_no_epub'] ?? 0 );
}
if ( ! get_option( 'fictioneer_disable_chapter_collapsing' ) ) {
$fields['fictioneer_story_disable_collapse'] =
fictioneer_sanitize_checkbox( $_POST['fictioneer_story_disable_collapse'] ?? 0 );
}
if ( ! get_option( 'fictioneer_hide_chapter_icons' ) ) {
$fields['fictioneer_story_hide_chapter_icons'] =
fictioneer_sanitize_checkbox( $_POST['fictioneer_story_hide_chapter_icons'] ?? 0 );
}
if ( get_option( 'fictioneer_enable_chapter_groups' ) ) {
$fields['fictioneer_story_disable_groups'] =
fictioneer_sanitize_checkbox( $_POST['fictioneer_story_disable_groups'] ?? 0 );
}
if ( current_user_can( 'fcn_custom_page_css', $post_id ) ) {
$css = sanitize_textarea_field( $_POST['fictioneer_story_css'] ?? '' );
$css = preg_match( '/<\/?\w+/', $css ) ? '' : $css;
$fields['fictioneer_story_css'] = str_replace( '<', '', $css );
}
// --- Filters -----------------------------------------------------------------
$fields = apply_filters( 'fictioneer_filter_story_sidebar_meta_updates', $fields );
// --- Save --------------------------------------------------------------------
foreach ( $fields as $key => $value ) {
fictioneer_update_post_meta( $post_id, $key, $value ?? 0 );
}
}
add_action( 'save_post', 'fictioneer_save_story_metabox' );
?>

View File

@ -1135,11 +1135,10 @@ if ( ! current_user_can( 'manage_options' ) ) {
function fictioneer_remove_custom_page_css_inputs( $fields ) {
// Return modified fields array (fictioneer_story_css, fictioneer_custom_css)
return fictioneer_acf_remove_fields( ['field_636d81d34cab1', 'field_621b5610818d2'], $fields );
return fictioneer_acf_remove_fields( ['field_621b5610818d2'], $fields );
}
if ( ! current_user_can( 'fcn_custom_page_css' ) ) {
add_filter( 'acf/update_value/name=fictioneer_story_css', 'fictioneer_acf_prevent_value_update', 10, 3 );
add_filter( 'acf/update_value/name=fictioneer_custom_css', 'fictioneer_acf_prevent_value_update', 10, 3 );
add_filter( 'acf/pre_render_fields', 'fictioneer_remove_custom_page_css_inputs' );
}
@ -1169,20 +1168,6 @@ if ( ! current_user_can( 'manage_options' ) ) {
// === FCN_MAKE_STICKY =======================================================
/**
* Hide the 'sticky in lists' checkbox
*
* @since 5.6.0
*/
function fictioneer_hide_story_sticky_checkbox() {
global $post_type;
if ( $post_type === 'fcn_story' ) {
echo '<style type="text/css">[data-name="fictioneer_story_sticky"] {display: none !important;}</style>';
}
}
/**
* Unstick a post
*
@ -1199,8 +1184,6 @@ if ( ! current_user_can( 'manage_options' ) ) {
}
if ( ! current_user_can( 'fcn_make_sticky' ) ) {
add_filter( 'acf/update_value/name=fictioneer_story_sticky', '__return_zero' ); // Must be 0!
add_action( 'admin_head', 'fictioneer_hide_story_sticky_checkbox' ); // Field must be in form, value used in queries!
add_action( 'post_stuck', 'fictioneer_prevent_post_sticky' );
}

View File

@ -155,6 +155,197 @@ td.comment {
}
}
.fictioneer-side-metabox[id] .inside {
padding: 0 16px 16px;
}
.fictioneer-side-metabox [data-required] {
label {
&::after {
content: ' *';
color: #cc1818;
}
}
}
.fictioneer-metabox-checkbox {
cursor: pointer;
display: flex;
align-items: flex-start;
gap: 12px;
width: fit-content;
&:not(:last-child) {
margin-bottom: 8px;
}
+ [class*="fictioneer-metabox-"] {
margin-top: 16px;
}
&:focus-within {
.fictioneer-metabox-checkbox__checkbox {
outline: 2px solid var(--wp-admin-theme-color);
outline-offset: 1.5px;
}
}
&__checkbox {
position: relative;
flex: 0 0 auto;
border-radius: 2px;
height: 24px;
width: 24px;
@include bp(600px) {
height: 20px;
width: 20px;
}
input[type="checkbox"] {
appearance: none;
display: block;
background: #fff;
border: 1px solid #1e1e1e;
border-radius: 2px;
box-shadow: none;
margin: 0;
height: 24px;
width: 24px;
@include bp(600px) {
height: 20px;
width: 20px;
}
&::before {
content: none !important;
}
&:checked {
background: var(--wp-admin-theme-color) !important;
border-color: var(--wp-admin-theme-color) !important;
}
&:not(:checked) + svg {
opacity: 0;
}
}
svg {
pointer-events: none;
fill: #fff;
position: absolute;
z-index: 1;
top: 0;
left: 0;
@include bp(600px) {
top: -2px;
left: -2px;
}
}
}
&__label {
margin-top: 2.5px;
@include bp(600px) {
margin-top: 1px;
}
}
}
.fictioneer-metabox-text,
.fictioneer-metabox-url,
.fictioneer-metabox-select,
.fictioneer-metabox-textarea {
&:not(:last-child) {
margin-bottom: 16px;
}
&__wrapper {
position: relative;
}
&__label {
display: block;
font-size: 11px;
font-weight: 500;
line-height: 1.4;
text-transform: uppercase;
padding: 0px;
margin-bottom: 8px;
}
&__description {
color: rgb(117, 117, 117);
font-size: 12px;
margin: 6px 0 0;
}
&__input:is(input) {
display: block;
font-size: 16px;
line-height: normal;
padding: 6px 8px;
margin: 0;
border: 1px solid #949494;
border-radius: 2px;
width: 100%;
box-shadow: 0 0 0 transparent;
@include bp(600px) {
font-size: 13px;
line-height: normal;
}
}
}
.fictioneer-metabox-url {
&__icon {
position: absolute;
top: 50%;
left: 6px;
font-size: 16px;
height: 16px;
width: 16px;
opacity: .5;
transform: translateY(calc(-50% + 1px));
}
&__input:is(input) {
padding-left: 24px;
}
}
.fictioneer-metabox-select {
&__select:is(select) {
box-sizing: border-box;
display: block;
font-size: 16px;
margin: 0;
width: 100%;
@include bp(600px) {
font-size: 13px;
}
}
}
.fictioneer-metabox-textarea {
&__textarea:is(textarea) {
display: block;
font-size: 12px;
line-height: 1.4;
padding: 6px 8px;
border-radius: 2px;
height: 82px;
width: 100%;
resize: vertical;
}
}
// =============================================================================
// FICTIONEER SEO METABOX
// =============================================================================