Add optional age confirmation modal

This commit is contained in:
Tetrakern 2024-01-20 14:41:33 +01:00
parent 812de7aed7
commit 73c366ee0d
15 changed files with 249 additions and 5 deletions

View File

@ -1140,6 +1140,8 @@ define( 'CONSTANT_NAME', value );
| FICTIONEER_FA_CDN | string | Font Awesome CDN URL.
| FICTIONEER_FA_INTEGRITY | string | Font Awesome integrity SHA384 hash.
| FICTIONEER_DISCORD_EMBED_COLOR | string | Color code for Discord notifications. Default `'9692513'`.
| FICTIONEER_TRUNCATION_ELLIPSIS | string | Appended to truncated strings. Default `…`.
| FICTIONEER_AGE_CONFIRMATION_REDIRECT | string | Redirect URL if a visitor reject the age confirmation. Default `https://search.brave.com/`.
| FICTIONEER_COMMENTCODE_TTL | integer | How long guests can see their private/unapproved comments in _seconds_. Default `600`.
| FICTIONEER_AJAX_TTL | integer | How long to cache certain AJAX requests locally in _milliseconds_. Default `60000`.
| FICTIONEER_AJAX_LOGIN_TTL | integer | How long to cache AJAX authentications locally in _milliseconds_. Default `15000`.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -185,6 +185,11 @@ if ( ! defined( 'FICTIONEER_TRUNCATION_ELLIPSIS' ) ) {
define( 'FICTIONEER_TRUNCATION_ELLIPSIS', '…' );
}
// String: Age confirmation redirect
if ( ! defined( 'FICTIONEER_AGE_CONFIRMATION_REDIRECT' ) ) {
define( 'FICTIONEER_AGE_CONFIRMATION_REDIRECT', 'https://search.brave.com/' );
}
/*
* Integers
*/

View File

@ -188,6 +188,20 @@ function fictioneer_output_modals( $args ) {
get_template_part( 'partials/_modal-chapter-changelog' );
}
// Age confirmation modal
if (
get_option( 'fictioneer_enable_site_age_confirmation' ) ||
(
! $is_archive &&
get_option( 'fictioneer_enable_post_age_confirmation' ) &&
in_array( $args['post_type'], ['fcn_story', 'fcn_chapter'] )
)
) {
if ( ! current_user_can( 'edit_fcn_stories' ) ) {
get_template_part( 'partials/_modal-age', null, $args );
}
}
// Action to add modals
do_action( 'fictioneer_modals' );
}

View File

@ -649,6 +649,20 @@ define( 'FICTIONEER_OPTIONS', array(
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
'label' => __( 'Disable Font Awesome integration', 'fictioneer' ),
'default' => 0
),
'fictioneer_enable_site_age_confirmation' => array(
'name' => 'fictioneer_enable_site_age_confirmation',
'group' => 'fictioneer-settings-general-group',
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
'label' => __( 'Enable age confirmation modal for site', 'fictioneer' ),
'default' => 0
),
'fictioneer_enable_post_age_confirmation' => array(
'name' => 'fictioneer_enable_post_age_confirmation',
'group' => 'fictioneer-settings-general-group',
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
'label' => __( 'Enable age confirmation modal for posts', 'fictioneer' ),
'default' => 0
)
),
'integers' => array(
@ -936,6 +950,22 @@ define( 'FICTIONEER_OPTIONS', array(
'label' => __( 'Comma-separated list of allowed <a href="%s" target="_blank" rel="noreferrer">mime types</a> for user roles with the "Upload Restriction". Must be among the allowed mime type and file extensions of WordPress.', 'fictioneer' ),
'default' => FICTIONEER_DEFAULT_UPLOAD_MIME_TYPE_RESTRICTIONS,
'placeholder' => FICTIONEER_DEFAULT_UPLOAD_MIME_TYPE_RESTRICTIONS
),
'fictioneer_phrase_site_age_confirmation' => array(
'name' => 'fictioneer_phrase_site_age_confirmation',
'group' => 'fictioneer-settings-phrases-group',
'sanitize_callback' => 'wp_kses_post',
'label' => __( 'Age confirmation modal content for the site.', 'fictioneer' ),
'default' => __( 'This website is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' ),
'placeholder' => __( 'This website is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' )
),
'fictioneer_phrase_post_age_confirmation' => array(
'name' => 'fictioneer_phrase_post_age_confirmation',
'group' => 'fictioneer-settings-phrases-group',
'sanitize_callback' => 'wp_kses_post',
'label' => __( 'Age confirmation modal content for posts.', 'fictioneer' ),
'default' => __( 'This content is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' ),
'placeholder' => __( 'This content is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' )
)
)
));

View File

@ -80,6 +80,15 @@
?>
</div>
<div class="fictioneer-card__row">
<?php
fictioneer_settings_label_checkbox(
'fictioneer_enable_site_age_confirmation',
__( 'Require age confirmation for the whole site.', 'fictioneer' )
);
?>
</div>
<div class="fictioneer-card__row">
<?php
fictioneer_settings_label_checkbox(
@ -141,6 +150,15 @@
<p><?php _e( 'Updating these settings may require the theme caches to be purged. You can do that under Tools.', 'fictioneer' ); ?></p>
</div>
<div class="fictioneer-card__row">
<?php
fictioneer_settings_label_checkbox(
'fictioneer_enable_post_age_confirmation',
__( 'Require age confirmation for <b>adult-rated</b> content.', 'fictioneer' )
);
?>
</div>
<div class="fictioneer-card__row">
<?php
fictioneer_settings_label_checkbox(

View File

@ -107,6 +107,50 @@
</div>
</div>
<div class="fictioneer-card">
<div class="fictioneer-card__wrapper">
<h3 class="fictioneer-card__header"><?php _e( 'Age Confirmation Modal: Site', 'fictioneer' ); ?></h3>
<div class="fictioneer-card__content">
<?php
$default = esc_html( __( 'This website is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' ) );
?>
<div class="fictioneer-card__row">
<textarea class="fictioneer-textarea" name="fictioneer_phrase_site_age_confirmation" rows="4" placeholder="<?php echo $default; ?>"><?php echo esc_attr( get_option( 'fictioneer_phrase_site_age_confirmation' ) ); ?></textarea>
<p class="fictioneer-sub-label"><?php _e( 'HTML allowed.', 'fictioneer' ); ?></p>
</div>
<div class="fictioneer-card__row">
<code><?php echo $default; ?></code>
</div>
</div>
</div>
</div>
<div class="fictioneer-card">
<div class="fictioneer-card__wrapper">
<h3 class="fictioneer-card__header"><?php _e( 'Age Confirmation Modal: Posts', 'fictioneer' ); ?></h3>
<div class="fictioneer-card__content">
<?php
$default = esc_html( __( 'This content is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' ) );
?>
<div class="fictioneer-card__row">
<textarea class="fictioneer-textarea" name="fictioneer_phrase_post_age_confirmation" rows="4" placeholder="<?php echo $default; ?>"><?php echo esc_attr( get_option( 'fictioneer_phrase_post_age_confirmation' ) ); ?></textarea>
<p class="fictioneer-sub-label"><?php _e( 'HTML allowed.', 'fictioneer' ); ?></p>
</div>
<div class="fictioneer-card__row">
<code><?php echo $default; ?></code>
</div>
</div>
</div>
</div>
<?php do_action( 'fictioneer_admin_settings_phrases' ); ?>
</div>

2
js/laws.min.js vendored
View File

@ -1 +1 @@
const fcn_hasConsent=fcn_getCookie("fcn_cookie_consent")??"",fcn_consentBanner=_$$$("consent-banner");function fcn_loadConsentBanner(){fcn_consentBanner.classList.remove("hidden"),_$$$("consent-accept-button")?.addEventListener("click",(()=>{fcn_setCookie("fcn_cookie_consent","full"),fcn_consentBanner.classList.add("hidden")})),_$$$("consent-reject-button")?.addEventListener("click",(()=>{fcn_setCookie("fcn_cookie_consent","necessary"),fcn_consentBanner.classList.add("hidden")}))}fcn_consentBanner&&""===fcn_hasConsent?setTimeout((()=>{fcn_loadConsentBanner()}),2e3):fcn_consentBanner.remove();
const fcn_hasConsent=fcn_getCookie("fcn_cookie_consent")??"",fcn_consentBanner=_$$$("consent-banner");function fcn_loadConsentBanner(){fcn_consentBanner.classList.remove("hidden"),_$$$("consent-accept-button")?.addEventListener("click",(()=>{fcn_setCookie("fcn_cookie_consent","full"),fcn_consentBanner.classList.add("hidden")})),_$$$("consent-reject-button")?.addEventListener("click",(()=>{fcn_setCookie("fcn_cookie_consent","necessary"),fcn_consentBanner.classList.add("hidden")}))}function fcn_showAgeConfirmationModal(){const n=_$(".story__article, .chapter__article")?.dataset.ageRating;if(n&&"adult"!==n)return void _$$$("age-confirmation-modal")?.remove();const e=_$$$("age-confirmation-leave");fcn_theRoot.classList.add("age-modal-open"),_$$$("age-confirmation-modal").classList.add("_open"),_$$$("age-confirmation-confirm")?.addEventListener("click",(n=>{fcn_theRoot.classList.remove("age-modal-open"),n.currentTarget.closest(".modal").remove(),localStorage.setItem("fcnAgeConfirmation","1")})),e?.addEventListener("click",(()=>{window.location.href=e.dataset.redirect??"https://search.brave.com/",localStorage.removeItem("fcnAgeConfirmation")}))}fcn_consentBanner&&""===fcn_hasConsent?setTimeout((()=>{fcn_loadConsentBanner()}),2e3):fcn_consentBanner.remove(),_$$$("age-confirmation-modal")&&"1"!==localStorage.getItem("fcnAgeConfirmation")?setTimeout((()=>{fcn_showAgeConfirmationModal()}),2e3):_$$$("age-confirmation-modal")?.remove();

47
partials/_modal-age.php Normal file
View File

@ -0,0 +1,47 @@
<?php
/**
* Partial: Age Confirmation Modal
*
* @package WordPress
* @subpackage Fictioneer
* @since 5.9.0
*
* @internal $args['post_id'] The current post ID.
* @internal $args['post_type'] The current post type.
*/
?>
<?php
// No direct access!
defined( 'ABSPATH' ) OR exit;
// Setup
$site_modal = get_option( 'fictioneer_enable_site_age_confirmation' );
?>
<div id="age-confirmation-modal" class="modal age-confirmation">
<div class="modal__wrapper">
<h4 class="modal__header"><?php _e( 'Age Confirmation', 'fictioneer' ); ?></h4>
<?php if ( $site_modal ) : ?>
<div class="modal__row modal__description _large"><?php
echo get_option( 'fictioneer_phrase_site_age_confirmation' ) ?: __( 'This website is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' );
?></div>
<?php else : ?>
<div class="modal__row modal__description _large"><?php
echo get_option( 'fictioneer_phrase_post_age_confirmation' ) ?: __( 'This content is intended for an adult audience. Please confirm that you are of legal age (18+) or leave the website.', 'fictioneer' );
?></div>
<?php endif; ?>
<div class="modal__actions _age-confirmation">
<button type="reset" id="age-confirmation-leave" data-redirect="<?php esc_attr_e( FICTIONEER_AGE_CONFIRMATION_REDIRECT ); ?>" class="button"><?php _ex( 'Leave', 'Age confirmation modal button.', 'fictioneer' ); ?></button>
<button type="submit" id="age-confirmation-confirm" class="button"><?php _ex( 'Confirm', 'Age confirmation modal button.', 'fictioneer' ); ?></button>
</div>
</div>
</div>

View File

@ -44,6 +44,7 @@ get_header( null, $header_args );
$chapter_ids = [];
$password_class = ! empty( $post->post_password ) ? 'password' : '';
$title = fictioneer_get_safe_title( $post->ID );
$age_rating = get_post_meta( $post->ID, 'fictioneer_chapter_rating', true );
$this_breadcrumb = [ $title, get_the_permalink() ];
$story_id = get_post_meta( $post->ID, 'fictioneer_chapter_story', true );
@ -58,6 +59,10 @@ get_header( null, $header_args );
if ( $story_post ) {
$story_data = fictioneer_get_story_data( $story_id, false ); // Does not refresh comment count!
$chapter_ids = $story_data['chapter_ids'];
if ( empty( $age_rating ) ) {
$age_rating = $story_data['rating'];
}
}
// Chapter navigation
@ -120,7 +125,7 @@ get_header( null, $header_args );
</div>
<?php endif; ?>
<article id="ch-<?php the_ID(); ?>" data-author-id="<?php echo get_the_author_meta( 'ID' ); ?>" class="chapter__article padding-left padding-right <?php echo post_password_required() ? '_password' : ''; ?>">
<article id="ch-<?php the_ID(); ?>" data-author-id="<?php echo get_the_author_meta( 'ID' ); ?>" class="chapter__article padding-left padding-right <?php echo post_password_required() ? '_password' : ''; ?>" data-age-rating="<?php echo strtolower( $age_rating ); ?>">
<div class="chapter__actions chapter__actions--top">
<div class="chapter__actions-container chapter__actions-left"><?php

View File

@ -54,7 +54,7 @@ get_header( null, $header_args );
);
?>
<article id="<?php the_ID(); ?>" class="story__article <?php if ( ! $can_checkmarks ) echo '_no-checkmarks'; ?>">
<article id="<?php the_ID(); ?>" class="story__article <?php if ( ! $can_checkmarks ) echo '_no-checkmarks'; ?>" data-age-rating="<?php echo strtolower( $story['rating'] ); ?>">
<?php
// Render article header

View File

@ -36,3 +36,52 @@ function fcn_loadConsentBanner() {
fcn_consentBanner.classList.add('hidden');
});
}
// =============================================================================
// AGE CONFIRMATION
// =============================================================================
if (_$$$('age-confirmation-modal') && localStorage.getItem('fcnAgeConfirmation') !== '1') {
// Delay to avoid impacting web vitals
setTimeout(() => {
fcn_showAgeConfirmationModal();
}, 2000);
} else {
_$$$('age-confirmation-modal')?.remove();
}
/**
* Show age confirmation modal if required.
*
* @since 5.9.0
*/
function fcn_showAgeConfirmationModal() {
// Adult story or chapter?
const rating = _$('.story__article, .chapter__article')?.dataset.ageRating;
if (rating && rating !== 'adult') {
_$$$('age-confirmation-modal')?.remove();
return;
}
// Setup
const leave = _$$$('age-confirmation-leave');
// Disable site and show modal
fcn_theRoot.classList.add('age-modal-open');
_$$$('age-confirmation-modal').classList.add('_open');
// Confirm button
_$$$('age-confirmation-confirm')?.addEventListener('click', event => {
fcn_theRoot.classList.remove('age-modal-open');
event.currentTarget.closest('.modal').remove();
localStorage.setItem('fcnAgeConfirmation', '1');
});
// Leave button
leave?.addEventListener('click', () => {
window.location.href = leave.dataset.redirect ?? 'https://search.brave.com/';
localStorage.removeItem('fcnAgeConfirmation');
});
}

View File

@ -1,3 +1,16 @@
:root.age-modal-open {
overflow: hidden;
body {
overflow: hidden;
}
:is(.site, .mobile-menu, .consent-banner) {
pointer-events: none;
filter: blur(5px) brightness(0.5);
}
}
.site {
position: relative;
left: 0;

View File

@ -6,6 +6,10 @@
line-height: 1.3;
contain: content; // Improve performance
&.age-confirmation {
--modal-width: 450px;
}
&:not(._open) {
display: none;
content-visibility: hidden;
@ -225,6 +229,10 @@
margin-bottom: 0;
}
}
&._large {
font-size: var(--fs-xs);
}
}
&__textarea {
@ -249,6 +257,15 @@
justify-content: flex-end;
padding: 0 12px 12px;
&._age-confirmation {
gap: 9px; // 9px + 3px margin
padding-top: 12px;
.button {
flex: 1 1 auto;
}
}
.button {
&:not(:first-child) {
margin-left: 3px;