Created
October 8, 2025 06:05
-
-
Save nkb-bd/75fb3c112729a769ce8d0ed651d12b7f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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