Skip to content

Instantly share code, notes, and snippets.

@nkb-bd
Created October 8, 2025 06:05
Show Gist options
  • Select an option

  • Save nkb-bd/75fb3c112729a769ce8d0ed651d12b7f to your computer and use it in GitHub Desktop.

Select an option

Save nkb-bd/75fb3c112729a769ce8d0ed651d12b7f to your computer and use it in GitHub Desktop.
<?php
/**
* Plugin Name: FluentCRM Page Lock
* Description: Protect WordPress pages with FluentCRM contact verification and tag-based access.
* Version: 1.1.0
* Author: lukman
* Usage as Plugin: Just activate
* Usage in functions.php: require_once 'path/to/fc-page-lock.php';
*/
if (!defined('ABSPATH')) {
exit;
}
class FCPL_Page_Lock_With_Tags
{
const TAG_IDS = [1];
const LOCKED_POST_IDS = [194]; // Add post/page IDs to protect
private static $instance = null;
public static function boot()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct()
{
add_action('plugins_loaded', [$this, 'init']);
}
public function init()
{
if (!defined('FLUENTCRM')) {
return;
}
add_action('template_redirect', [$this, 'check_page_access'], 1);
add_action('wp_ajax_fc_verify_email', [$this, 'ajax_verify_email']);
add_action('wp_ajax_nopriv_fc_verify_email', [$this, 'ajax_verify_email']);
}
/**
* Page Protection
*/
public function check_page_access()
{
// Only check on singular pages
if (!is_singular()) {
return;
}
$post_id = get_the_ID();
$protected_post_ids = self::LOCKED_POST_IDS;
// Check if current page is protected
if (!in_array($post_id, $protected_post_ids)) {
return;
}
// Allow editors and admins to bypass
if (current_user_can('edit_posts')) {
return;
}
// Get current contact
$contact = fluentcrm_get_current_contact();
// Check if user has required tags
$required_tags = self::TAG_IDS;
if ($contact && !empty($required_tags) && $contact->hasAnyTagId($required_tags)) {
return; // Allow access
}
// No access - show verification modal
$this->show_verification_modal();
}
private function show_verification_modal()
{
status_header(403);
nocache_headers();
$title = 'Verify Your Email';
$subtitle = 'Please enter your email to access this content.';
?>
<!DOCTYPE html>
<html <?php
language_attributes(); ?>>
<head>
<meta charset="<?php
bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php
echo esc_html($title); ?></title>
<?php
$this->inline_styles(); ?>
</head>
<body style="margin:0;padding:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)">
<div class="fcpl-modal">
<div class="fcpl-modal-content">
<div class="fcpl-modal-icon">
<svg width="72" height="72" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C9.243 2 7 4.243 7 7v3H6c-1.103 0-2 .897-2 2v8c0 1.103.897 2 2 2h12c1.103 0 2-.897 2-2v-8c0-1.103-.897-2-2-2h-1V7c0-2.757-2.243-5-5-5zm0 2c1.654 0 3 1.346 3 3v3H9V7c0-1.654 1.346-3 3-3zm0 10c.828 0 1.5.672 1.5 1.5S12.828 17 12 17s-1.5-.672-1.5-1.5.672-1.5 1.5-1.5z"
fill="currentColor"/>
</svg>
</div>
<h1 class="fcpl-modal-title"><?php
echo esc_html($title); ?></h1>
<p class="fcpl-modal-subtitle"><?php
echo esc_html($subtitle); ?></p>
<div id="fcpl-message" class="fcpl-message"></div>
<form id="fcpl-email-form">
<div class="fcpl-form-group">
<label>Email Address</label>
<input type="email" id="fcpl-email" required placeholder="">
</div>
<button type="submit" class="el-button fcom_primary_button" id="fcpl-submit">Verify & Continue
</button>
</form>
<a href="<?php
echo home_url('/'); ?>" class="el-button fcom_secondary_button">Home</a>
</div>
</div>
<script>
document.getElementById('fcpl-email-form').addEventListener('submit', async function (e) {
e.preventDefault();
const email = document.getElementById('fcpl-email').value;
const btn = document.getElementById('fcpl-submit');
const msg = document.getElementById('fcpl-message');
btn.disabled = true;
btn.innerHTML = '<span class="fcpl-spinner"></span>Verifying...';
msg.className = 'fcpl-message';
try {
const formData = new FormData();
formData.append('action', 'fc_verify_email');
formData.append('email', email);
formData.append('nonce', '<?php echo wp_create_nonce('fc_verify_email'); ?>');
formData.append('redirect_url', window.location.href);
const response = await fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
msg.textContent = data.data.message;
msg.className = 'fcpl-message fcpl-message-success';
setTimeout(() => {
if (data.data.redirect_url) {
window.location.href = data.data.redirect_url;
} else {
window.location.reload();
}
}, 1000);
} else {
msg.textContent = data.data.message;
msg.className = 'fcpl-message fcpl-message-error';
btn.disabled = false;
btn.innerHTML = 'Verify & Continue';
}
} catch (error) {
msg.textContent = 'An error occurred. Please try again.';
msg.className = 'fcpl-message fcpl-message-error';
btn.disabled = false;
btn.innerHTML = 'Verify & Continue';
}
});
</script>
</body>
</html>
<?php
exit;
}
/**
* AJAX Email Verification
*/
public function ajax_verify_email()
{
// Verify nonce
if (!check_ajax_referer('fc_verify_email', 'nonce', false)) {
wp_send_json_error(['message' => 'Security check failed'], 403);
}
// Sanitize and validate email
$email = sanitize_email($_POST['email'] ?? '');
if (!is_email($email)) {
wp_send_json_error(['message' => 'Invalid email address']);
}
// Get contact
$contact = FluentCrmApi('contacts')->getContact($email);
if (!$contact) {
wp_send_json_error(['message' => 'Email not found']);
}
// Check if contact has required tags
$required_tags = self::TAG_IDS;
if (!empty($required_tags) && !$contact->hasAnyTagId($required_tags)) {
wp_send_json_error(['message' => 'You do not have the required access level']);
}
// Set secure cookie
$hash = fluentCrmGetContactSecureHash($contact->id);
setcookie("fc_hash_secure", $hash, time() + 7776000, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
$_COOKIE['fc_hash_secure'] = $hash;
do_action('fc_page_lock/email_verified', $contact);
// Sanitize redirect URL
$redirect_url = '';
if (!empty($_POST['redirect_url'])) {
$redirect_url = esc_url_raw($_POST['redirect_url']);
// Additional security: ensure redirect is to same site
if (!wp_validate_redirect($redirect_url, false)) {
$redirect_url = '';
}
}
wp_send_json_success([
'message' => 'Verified! Redirecting...',
'redirect_url' => $redirect_url
]);
}
private function inline_styles()
{
?>
<style>
.fcpl-modal {
max-width: 480px;
width: 100%;
background: #fff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, .3);
animation: fcSlideIn .4s
}
@keyframes fcSlideIn {
from {
opacity: 0;
transform: translateY(-30px)
}
to {
opacity: 1;
transform: translateY(0)
}
}
.fcpl-modal-content {
padding: 50px 40px;
text-align: center
}
.fcpl-modal-icon {
margin-bottom: 24px;
color: #667eea
}
.fcpl-modal-title {
font-size: 28px;
margin: 0 0 16px;
color: #1d2327;
font-weight: 600
}
.fcpl-modal-subtitle {
font-size: 16px;
line-height: 1.6;
margin: 0 0 32px;
color: #50575e
}
.fcpl-form-group {
margin-bottom: 20px;
text-align: left
}
.fcpl-form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #1d2327;
font-size: 14px
}
.fcpl-form-group input[type=email] {
width: 100%;
padding: 14px 16px;
border: 2px solid #dcdcde;
border-radius: 8px;
font-size: 16px;
transition: all .3s;
box-sizing: border-box;
font-family: inherit;
height: 52px
}
.fcpl-form-group input[type=email]:focus {
outline: 0;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, .1)
}
.fcpl-modal .el-button {
width: 100%;
padding: 14px 24px !important;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all .3s;
box-sizing: border-box;
font-family: inherit;
text-decoration: none;
display: inline-block;
text-align: center;
height: 52px !important;
line-height: 24px !important;
min-height: 52px !important
}
.fcpl-modal .fcom_secondary_button {
margin-top: 12px
}
.fcpl-message {
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
display: none
}
.fcpl-message-error {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
display: block
}
.fcpl-message-success {
background: #f0fdf4;
color: #16a34a;
border: 1px solid #bbf7d0;
display: block
}
.fcpl-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, .3);
border-top-color: #fff;
border-radius: 50%;
animation: fcSpin .6s linear infinite;
margin-right: 8px;
vertical-align: middle
}
@keyframes fcSpin {
to {
transform: rotate(360deg)
}
}
</style>
<?php
}
}
FCPL_Page_Lock_With_Tags::boot();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment