Security-only release per ADR-0001. Closes all six ship-blockers from the post-v1.5.0 audit: PSEC-001 (no rate limiting), PSEC-002 (account enumeration via separate error messages), PSEC-003 (registration auto-login with no email verification), PSEC-004 (ungated "My Data" templates), PSEC-005 (CSV formula injection on subscriber export), PSEC-006 (no honeypot/CAPTCHA on public forms). Adds inc/config.php (single source of truth for all security thresholds, filterable via peptide_starter_security_config), inc/rate-limiter.php (Peptide_Starter_Rate_Limiter + Cloudflare-aware client IP resolver, transient-backed, never stores raw IPs), and inc/email-verification.php (43-char token, 24h TTL, /verify rewrite, rate-limited resend endpoint). Auth handlers rewritten to: unify all failure messages, rate-limit before any expensive check, drop registrations with a filled honeypot silently, stop auto-login on register, send a verification email instead. The require_login() helper lives in inc/helpers.php and is applied at the top of page-tracker.php, page-subject-log.php, and page-protocol-builder.php; page-calculator.php stays open (no PII, no writes). Newsletter signup gains: consent checkbox, privacy link, honeypot, autoload=false on the wp_options blob, unsubscribe token stored per row, /newsletter-unsubscribe route, merged success+duplicate into a single "ok" state. Admin export runs every cell through peptide_starter_csv_safe() which prefixes leading =/+/-/@/tab/CR with a quote. Contact handler hardens against email-header injection (rejects name values with \r \n , < >), rate-limits, honors honeypot, tags mail failures with a request ID for correlation without logging message content. Adds Tools → Mail Test (inc/mail-diagnostic.php): a permanent wp_mail probe so we can check Hostinger outbound deliverability any time it looks flaky. Adds a one-time admin_init migration that enrols pre-v1.5.1 subscribers into the verification flow (currently a no-op — zero users — but guarded for the future). Other cleanup from the audit: fallback primary menu moved out of header.php into inc/helpers.php (redeclaration risk), placeholder footer language links replaced with "Translations coming soon", footer copyright now reads through the customizer getter, settings-panel gains a proper Tab/Shift+Tab focus trap and saves/restores body overflow, documentation heading IDs slugified from heading text with collision counters, JS files standardized on const/let. Six new PHPUnit suites cover rate limiter lifecycle, auth handler error unification and rate limits, email verification token generation and expiry, contact handler header injection + rate limit + honeypot, newsletter signup consent + CSV safety, and require_login gate behavior. ARCHITECTURE.md and CONVENTIONS.md updated in the same commit — new module layout, data flows, security patterns for rate-limiting, honeypotting, gating, and CSV-safe export. Version bumped to 1.5.1 in style.css and functions.php. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
161 lines
8.3 KiB
PHP
161 lines
8.3 KiB
PHP
<?php
|
|
/**
|
|
* The header for Peptide Starter theme
|
|
*
|
|
* Updated navigation: Home | Tools ▾ | My Data ▾ | Resources ▾ | [Sign In] ⚙ 🌐
|
|
* Sign In / User menu is state-aware (logged in vs logged out).
|
|
*
|
|
* @see functions.php — nav menu registration, walker
|
|
* @see assets/js/navigation.js — mobile menu toggle
|
|
* @see assets/js/settings-panel.js — settings icon trigger
|
|
* @see template-parts/settings-panel.php — settings slide-out
|
|
*
|
|
* @package peptide-starter
|
|
*/
|
|
|
|
?><!doctype html>
|
|
<html <?php language_attributes(); ?>>
|
|
<head>
|
|
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="profile" href="https://gmpg.org/xfn/11">
|
|
<?php wp_head(); ?>
|
|
</head>
|
|
|
|
<body <?php body_class(); ?>>
|
|
<?php wp_body_open(); ?>
|
|
|
|
<a href="#main" class="skip-link"><?php esc_html_e( 'Skip to main content', 'peptide-starter' ); ?></a>
|
|
|
|
<header class="site-header">
|
|
<div class="site-header-wrapper">
|
|
<!-- Logo -->
|
|
<div class="site-logo-wrapper">
|
|
<?php peptide_starter_the_custom_logo(); ?>
|
|
</div>
|
|
|
|
<!-- Primary Navigation -->
|
|
<nav class="primary-navigation">
|
|
<?php
|
|
wp_nav_menu(
|
|
array(
|
|
'theme_location' => 'primary',
|
|
'fallback_cb' => 'peptide_starter_primary_menu_fallback',
|
|
'walker' => new Peptide_Starter_Nav_Walker(),
|
|
'depth' => 2,
|
|
'container' => false,
|
|
'items_wrap' => '<ul>%3$s</ul>',
|
|
)
|
|
);
|
|
?>
|
|
</nav>
|
|
|
|
<!-- Navigation Overlay (for mobile) -->
|
|
<div class="nav-overlay"></div>
|
|
|
|
<!-- Header Icons -->
|
|
<div class="header-icons">
|
|
<!-- Sign In / User Menu (state-aware) -->
|
|
<?php if ( is_user_logged_in() ) : ?>
|
|
<?php $current_user = wp_get_current_user(); ?>
|
|
<div class="ps-user-menu">
|
|
<button class="ps-user-menu__toggle" aria-expanded="false" aria-haspopup="true">
|
|
<span class="ps-user-menu__name"><?php echo esc_html( $current_user->display_name ); ?></span>
|
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
<path d="M3 5L6 8L9 5"/>
|
|
</svg>
|
|
</button>
|
|
<div class="ps-user-menu__dropdown" aria-label="<?php esc_attr_e( 'User menu', 'peptide-starter' ); ?>">
|
|
<a href="<?php echo esc_url( home_url( '/profile' ) ); ?>"><?php esc_html_e( 'My Profile', 'peptide-starter' ); ?></a>
|
|
<a href="<?php echo esc_url( home_url( '/tracker' ) ); ?>"><?php esc_html_e( 'Tracker', 'peptide-starter' ); ?></a>
|
|
<a href="<?php echo esc_url( home_url( '/subject-log' ) ); ?>"><?php esc_html_e( 'Subject Log', 'peptide-starter' ); ?></a>
|
|
<a href="<?php echo esc_url( wp_logout_url( home_url( '/' ) ) ); ?>"><?php esc_html_e( 'Sign Out', 'peptide-starter' ); ?></a>
|
|
</div>
|
|
</div>
|
|
<?php else : ?>
|
|
<a href="<?php echo esc_url( home_url( '/auth' ) ); ?>" class="ps-btn ps-btn-primary ps-btn-sm ps-header-cta">
|
|
<?php esc_html_e( 'Sign In', 'peptide-starter' ); ?>
|
|
</a>
|
|
<?php endif; ?>
|
|
|
|
<!-- Settings Icon -->
|
|
<button class="header-icon-btn" id="ps-settings-toggle" aria-label="<?php esc_attr_e( 'Support & settings', 'peptide-starter' ); ?>">
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="10" cy="10" r="3"/>
|
|
<path d="M16.474 12.32a1.26 1.26 0 00.252 1.39l.046.046a1.527 1.527 0 11-2.16 2.16l-.046-.046a1.27 1.27 0 00-2.15.9v.13a1.527 1.527 0 11-3.055 0v-.07a1.26 1.26 0 00-.825-1.152 1.26 1.26 0 00-1.39.252l-.046.046a1.527 1.527 0 11-2.16-2.16l.046-.046a1.27 1.27 0 00-.9-2.15h-.13a1.527 1.527 0 110-3.055h.07a1.26 1.26 0 001.152-.825 1.26 1.26 0 00-.252-1.39l-.046-.046a1.527 1.527 0 112.16-2.16l.046.046a1.26 1.26 0 001.39.252h.06a1.26 1.26 0 00.764-1.152v-.13a1.527 1.527 0 113.055 0v.07a1.27 1.27 0 002.15.9l.046-.046a1.527 1.527 0 112.16 2.16l-.046.046a1.26 1.26 0 00-.252 1.39v.06a1.26 1.26 0 001.152.764h.13a1.527 1.527 0 110 3.055h-.07a1.26 1.26 0 00-1.152.825z"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Language Placeholder -->
|
|
<span class="ps-lang-placeholder" aria-label="<?php esc_attr_e( 'Language', 'peptide-starter' ); ?>" title="<?php esc_attr_e( 'Language selection coming soon', 'peptide-starter' ); ?>">
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="10" cy="10" r="8"/>
|
|
<line x1="2" y1="10" x2="18" y2="10"/>
|
|
<path d="M10 2a14.5 14.5 0 014 8 14.5 14.5 0 01-4 8 14.5 14.5 0 01-4-8 14.5 14.5 0 014-8z"/>
|
|
</svg>
|
|
<span class="ps-lang-code">EN</span>
|
|
</span>
|
|
|
|
<!-- Search Icon -->
|
|
<button class="header-icon-btn search-toggle" aria-label="<?php esc_attr_e( 'Search', 'peptide-starter' ); ?>">
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M9 17C13.4183 17 17 13.4183 17 9C17 4.58172 13.4183 1 9 1C4.58172 1 1 4.58172 1 9C1 13.4183 4.58172 17 9 17Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<path d="M19 19L14.65 14.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Dark Mode Toggle -->
|
|
<button class="header-icon-btn dark-mode-toggle" aria-label="<?php esc_attr_e( 'Toggle dark mode', 'peptide-starter' ); ?>">
|
|
<svg class="sun-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
|
<circle cx="10" cy="10" r="4" stroke="currentColor" stroke-width="2"/>
|
|
<path d="M10 1V3M10 17V19M19 10H17M3 10H1M16.657 16.657L15.243 15.243M4.757 4.757L3.343 3.343M16.657 3.343L15.243 4.757M4.757 15.243L3.343 16.657" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
<svg class="moon-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M17.293 13.293A7.002 7.002 0 101.725 2.725a7 7 0 0115.568 10.568z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Cart Icon (WooCommerce) -->
|
|
<?php if ( class_exists( 'WooCommerce' ) ) { ?>
|
|
<button class="header-icon-btn cart-toggle" aria-label="<?php esc_attr_e( 'View cart', 'peptide-starter' ); ?>">
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M1 1H3.27924M3.27924 1L5.20081 13.5631C5.35072 14.4571 5.80765 15.2614 6.4543 15.8285C7.10095 16.3956 7.92274 16.6882 8.76721 16.6882H16.7257M16.7257 16.6882C17.3169 16.6882 17.7793 17.1506 17.7793 17.7418C17.7793 18.333 17.3169 18.7955 16.7257 18.7955M16.7257 16.6882C15.6515 16.6882 14.4632 16.6882 8.76721 16.6882M8.76721 16.6882C8.17601 16.6882 7.71365 17.1506 7.71365 17.7418C7.71365 18.333 8.17601 18.7955 8.76721 18.7955M5.20081 13.5631H16.7257" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
<?php } ?>
|
|
|
|
<!-- Mobile Menu Toggle -->
|
|
<button class="menu-toggle" aria-label="<?php esc_attr_e( 'Toggle menu', 'peptide-starter' ); ?>">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M3 6H21M3 12H21M3 18H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Search Overlay (Peptide Search AI) -->
|
|
<div class="search-overlay" id="search-overlay" aria-hidden="true">
|
|
<div class="search-overlay-inner">
|
|
<button class="search-overlay-close" aria-label="<?php esc_attr_e( 'Close search', 'peptide-starter' ); ?>">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
<div class="search-overlay-content">
|
|
<?php
|
|
if ( shortcode_exists( 'peptide_search' ) ) {
|
|
echo do_shortcode( '[peptide_search placeholder="Search for a peptide (e.g., BPC-157, Thymosin Beta-4)..."]' );
|
|
} else {
|
|
get_search_form();
|
|
}
|
|
?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
// Nav fallback lives in inc/helpers.php as of v1.5.1 to prevent redeclaration
|
|
// if header.php is ever included more than once in a request.
|
|
?>
|