This commit is contained in:
Jeremy Rangel
2025-01-09 23:23:49 -08:00
commit d672947964
6 changed files with 895 additions and 0 deletions

View File

@ -0,0 +1,314 @@
<?php
if (!defined('ABSPATH')) exit;
class LCP_Paywall_Admin {
private $rules;
public function __construct() {
$this->rules = new LCP_Paywall_Rules();
add_action('admin_menu', array($this, 'add_menu_pages'));
add_action('admin_init', array($this, 'register_settings'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
add_action('wp_ajax_lcp_save_rules', array($this, 'ajax_save_rules'));
add_action('wp_ajax_lcp_get_taxonomy_terms', array($this, 'ajax_get_taxonomy_terms'));
add_action('wp_ajax_lcp_save_settings', array($this, 'ajax_save_settings'));
}
public function add_menu_pages() {
add_menu_page(
'LCP Paywall',
'LCP Paywall',
'manage_options',
'lcp-paywall',
array($this, 'render_rules_page'),
'dashicons-lock',
30
);
add_submenu_page(
'lcp-paywall',
'Paywall Settings',
'Settings',
'manage_options',
'lcp-paywall-settings',
array($this, 'render_settings_page')
);
}
public function register_settings() {
register_setting('lcp_paywall_options', 'lcp_paywall_rules');
register_setting('lcp_paywall_settings', 'lcp_paywall_settings');
}
public function enqueue_admin_assets($hook) {
if (!in_array($hook, array('toplevel_page_lcp-paywall', 'lcp-paywall_page_lcp-paywall-settings'))) {
return;
}
wp_enqueue_style('lcp-paywall-admin');
// Enqueue WordPress editor assets
if ($hook === 'lcp-paywall_page_lcp-paywall-settings') {
wp_enqueue_editor();
}
// Enqueue Sortable.js
wp_enqueue_script(
'sortablejs',
'https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js',
array(),
'1.15.0',
true
);
wp_enqueue_script(
'lcp-paywall-admin',
LCP_PAYWALL_URL . 'assets/js/admin.js',
array('sortablejs'),
LCP_PAYWALL_VERSION,
true
);
wp_localize_script('lcp-paywall-admin', 'lcpPaywall', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('lcp_paywall_nonce'),
'operators' => $this->rules->get_operators(),
'conditions' => $this->rules->get_conditions(),
'valueTypes' => $this->rules->get_value_types(),
));
}
public function render_rules_page() {
if (!current_user_can('manage_options')) {
return;
}
$post_types = get_post_types(array('public' => true), 'objects');
$current_rules = $this->rules->get_rules();
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<div id="lcp-paywall-rules-manager">
<?php foreach ($post_types as $post_type):
$settings = $this->rules->get_default_post_type_settings($post_type->name);
?>
<div class="lcp-post-type-rules" data-post-type="<?php echo esc_attr($post_type->name); ?>">
<h2><?php echo esc_html($post_type->labels->name); ?></h2>
<div class="post-type-settings">
<div class="setting-group">
<label>Default Lock Status:</label>
<select class="default-lock-status" name="default_lock_status">
<option value="locked" <?php selected($settings['default_lock_status'], 'locked'); ?>>Locked</option>
<option value="unlocked" <?php selected($settings['default_lock_status'], 'unlocked'); ?>>Unlocked</option>
</select>
</div>
<div class="setting-group">
<label>Enable Metered Access:</label>
<label class="switch">
<input type="checkbox" class="is-metered" name="is_metered" <?php checked($settings['is_metered']); ?>>
<span class="slider"></span>
</label>
</div>
<div class="setting-group">
<label>Free Posts Count:</label>
<input type="number" class="metered-free-posts" name="metered_free_posts"
min="0" step="1" value="<?php echo esc_attr($settings['metered_free_posts']); ?>">
</div>
<div class="setting-group">
<label>Metered Interval (days):</label>
<input type="number" class="metered-interval" name="metered_interval"
min="0" step="1" value="<?php echo esc_attr($settings['metered_interval']); ?>">
</div>
</div>
<div class="lcp-rules-container">
<?php
if (isset($current_rules[$post_type->name]['rules'])) {
foreach ($current_rules[$post_type->name]['rules'] as $rule) {
$this->render_rule_template($post_type, $rule);
}
}
?>
</div>
<div class="post-type-actions">
<button type="button" class="button add-rule" data-post-type="<?php echo esc_attr($post_type->name); ?>">
Add Rule
</button>
<button type="button" class="button button-primary save-post-type-rules" data-post-type="<?php echo esc_attr($post_type->name); ?>">
Save <?php echo esc_html($post_type->labels->name); ?> Rules
</button>
</div>
</div>
<?php endforeach; ?>
<div class="lcp-rule-template" style="display: none;">
<?php $this->render_rule_template(null); ?>
</div>
</div>
</div>
<?php
}
public function render_settings_page() {
if (!current_user_can('manage_options')) {
return;
}
$settings = get_option('lcp_paywall_settings', array());
$nag_message = isset($settings['nag_message']) ? $settings['nag_message'] : '';
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<div id="lcp-paywall-settings">
<h2>Paywall Nag Message</h2>
<p>Configure the message that will be shown to users when content is locked.</p>
<div class="nag-message-editor">
<?php
wp_editor(
$nag_message,
'lcp_nag_message',
array(
'textarea_name' => 'lcp_nag_message',
'media_buttons' => true,
'textarea_rows' => 10,
'teeny' => false
)
);
?>
</div>
<p class="submit">
<button type="button" class="button button-primary" id="save-settings">Save Settings</button>
</p>
</div>
</div>
<?php
}
private function render_rule_template($post_type, $rule = null) {
?>
<div class="lcp-rule">
<div class="rule-header">
<span class="dashicons dashicons-menu"></span>
<select class="rule-action">
<option value="lock" <?php selected(isset($rule['action']) && $rule['action'] === 'lock'); ?>>Lock</option>
<option value="unlock" <?php selected(isset($rule['action']) && $rule['action'] === 'unlock'); ?>>Unlock</option>
</select>
<button type="button" class="button delete-rule">
<span class="dashicons dashicons-trash"></span>
</button>
</div>
<div class="rule-body">
<select class="rule-condition">
<option value="user" <?php selected(isset($rule['condition']) && $rule['condition'] === 'user'); ?>>User</option>
<option value="post" <?php selected(isset($rule['condition']) && $rule['condition'] === 'post'); ?>>Post</option>
</select>
<select class="rule-operator">
<option value="=" <?php selected(isset($rule['operator']) && $rule['operator'] === '='); ?>>=</option>
<option value="!=" <?php selected(isset($rule['operator']) && $rule['operator'] === '!='); ?>>!=</option>
<option value="in" <?php selected(isset($rule['operator']) && $rule['operator'] === 'in'); ?>>in</option>
<option value="notin" <?php selected(isset($rule['operator']) && $rule['operator'] === 'notin'); ?>>not in</option>
<option value="is" <?php selected(isset($rule['operator']) && $rule['operator'] === 'is'); ?>>is</option>
<option value="isnot" <?php selected(isset($rule['operator']) && $rule['operator'] === 'isnot'); ?>>is not</option>
<option value="olderthan" <?php selected(isset($rule['operator']) && $rule['operator'] === 'olderthan'); ?>>older than</option>
<option value="newerthan" <?php selected(isset($rule['operator']) && $rule['operator'] === 'newerthan'); ?>>newer than</option>
<option value="hassub" <?php selected(isset($rule['operator']) && $rule['operator'] === 'hassub'); ?>>has subscription</option>
</select>
<select class="rule-value-type">
<option value="logged_in" <?php selected(isset($rule['value_type']) && $rule['value_type'] === 'logged_in'); ?>>Logged In</option>
<option value="taxonomy" <?php selected(isset($rule['value_type']) && $rule['value_type'] === 'taxonomy'); ?>>Taxonomy</option>
</select>
<?php if ($post_type): ?>
<select class="rule-taxonomy" style="display: <?php echo (isset($rule['value_type']) && $rule['value_type'] === 'taxonomy') ? 'inline-block' : 'none'; ?>">
<?php foreach ($this->rules->get_taxonomies_for_post_type($post_type->name) as $taxonomy): ?>
<option value="<?php echo esc_attr($taxonomy->name); ?>" <?php selected(isset($rule['taxonomy']) && $rule['taxonomy'] === $taxonomy->name); ?>>
<?php echo esc_html($taxonomy->label); ?>
</option>
<?php endforeach; ?>
</select>
<select class="rule-term" style="display: <?php echo (isset($rule['value_type']) && $rule['value_type'] === 'taxonomy') ? 'inline-block' : 'none'; ?>">
<?php
if (isset($rule['taxonomy'])) {
foreach ($this->rules->get_terms_for_taxonomy($rule['taxonomy']) as $term): ?>
<option value="<?php echo esc_attr($term->term_id); ?>" <?php selected(isset($rule['term']) && $rule['term'] === $term->term_id); ?>>
<?php echo esc_html($term->name); ?>
</option>
<?php endforeach;
}
?>
</select>
<?php endif; ?>
</div>
</div>
<?php
}
public function ajax_save_rules() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Unauthorized');
}
check_ajax_referer('lcp_paywall_nonce', 'nonce');
$rules = isset($_POST['rules']) ? json_decode(stripslashes($_POST['rules']), true) : array();
if ($this->rules->save_rules($rules)) {
wp_send_json_success('Rules saved successfully');
} else {
wp_send_json_error('Failed to save rules');
}
}
public function ajax_get_taxonomy_terms() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Unauthorized');
}
check_ajax_referer('lcp_paywall_nonce', 'nonce');
$taxonomy = isset($_POST['taxonomy']) ? sanitize_text_field($_POST['taxonomy']) : '';
if (!$taxonomy) {
wp_send_json_error('Invalid taxonomy');
}
$terms = $this->rules->get_terms_for_taxonomy($taxonomy);
wp_send_json_success($terms);
}
public function ajax_save_settings() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Unauthorized');
}
check_ajax_referer('lcp_paywall_nonce', 'nonce');
$nag_message = isset($_POST['nag_message']) ? wp_kses_post(stripslashes($_POST['nag_message'])) : '';
$settings = array(
'nag_message' => $nag_message
);
if (update_option('lcp_paywall_settings', $settings)) {
wp_send_json_success('Settings saved successfully');
} else {
wp_send_json_error('Failed to save settings');
}
}
}

View File

@ -0,0 +1,77 @@
<?php
if (!defined('ABSPATH')) exit;
class LCP_Paywall_Rules {
private $option_name = 'lcp_paywall_rules';
public function get_rules() {
return get_option($this->option_name, array());
}
public function get_rules_for_post_type($post_type) {
$rules = $this->get_rules();
return isset($rules[$post_type]) ? $rules[$post_type] : array();
}
public function save_rules($rules) {
// Ensure settings exist for each post type
foreach ($rules as $post_type => $data) {
if (!isset($data['settings'])) {
$rules[$post_type]['settings'] = $this->get_default_post_type_settings($post_type);
}
}
return update_option($this->option_name, $rules);
}
public function get_operators() {
return array(
'=' => 'Equals',
'!=' => 'Not Equals',
'in' => 'In',
'notin' => 'Not In',
'is' => 'Is',
'isnot' => 'Is Not',
'olderthan' => 'Older Than',
'newerthan' => 'Newer Than',
'hassub' => 'Has Subscription'
);
}
public function get_conditions() {
return array(
'user' => 'User',
'post' => 'Post'
);
}
public function get_value_types() {
return array(
'logged_in' => 'Logged In',
'taxonomy' => 'Taxonomy'
);
}
public function get_taxonomies_for_post_type($post_type) {
return get_object_taxonomies($post_type, 'objects');
}
public function get_terms_for_taxonomy($taxonomy) {
$terms = get_terms(array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
));
return is_wp_error($terms) ? array() : $terms;
}
public function get_default_post_type_settings($post_type) {
$rules = $this->get_rules();
return isset($rules[$post_type]['settings']) ? $rules[$post_type]['settings'] : array(
'default_lock_status' => 'locked',
'is_metered' => false,
'metered_free_posts' => 0,
'metered_interval' => 0
);
}
}

View File

@ -0,0 +1,141 @@
<?php
if (!defined('ABSPATH')) exit;
class LCP_Paywall {
private $rules;
private $admin;
public function init() {
$this->rules = new LCP_Paywall_Rules();
if (is_admin()) {
$this->admin = new LCP_Paywall_Admin();
}
add_action('init', array($this, 'init_hooks'));
add_filter('the_content', array($this, 'maybe_restrict_content'), 999);
}
public function init_hooks() {
// Register scripts and styles
add_action('admin_enqueue_scripts', array($this, 'register_admin_assets'));
}
public function register_admin_assets() {
wp_register_style(
'lcp-paywall-admin',
LCP_PAYWALL_URL . 'assets/css/admin.css',
array(),
LCP_PAYWALL_VERSION
);
wp_register_script(
'lcp-paywall-admin',
LCP_PAYWALL_URL . 'assets/js/admin.js',
array('jquery', 'jquery-ui-sortable'),
LCP_PAYWALL_VERSION,
true
);
}
public function maybe_restrict_content($content) {
if (!is_singular()) return $content;
$post_type = get_post_type();
$post_id = get_the_ID();
if ($this->should_restrict_access($post_type, $post_id)) {
return $this->get_nag_message();
}
return $content;
}
private function should_restrict_access($post_type, $post_id) {
$post_type_rules = $this->rules->get_rules_for_post_type($post_type);
// Get default lock status
$default_lock_status = isset($post_type_rules['settings']['default_lock_status']) ?
$post_type_rules['settings']['default_lock_status'] === 'locked' : true;
// If no rules exist, use default lock status
if (empty($post_type_rules['rules'])) {
return $default_lock_status;
}
// Check each rule in order until one evaluates to true
foreach ($post_type_rules['rules'] as $rule) {
if ($this->evaluate_rule($rule, $post_id)) {
// First matching rule determines the lock status
return $rule['action'] === 'lock';
}
}
// If no rules match, use default lock status
return $default_lock_status;
}
private function evaluate_rule($rule, $post_id) {
if ($rule['condition'] === 'user') {
return $this->evaluate_user_rule($rule);
} else if ($rule['condition'] === 'post') {
return $this->evaluate_post_rule($rule, $post_id);
}
return false;
}
private function evaluate_user_rule($rule) {
if ($rule['value_type'] === 'logged_in') {
$is_logged_in = is_user_logged_in();
switch ($rule['operator']) {
case '=':
case 'is':
return $is_logged_in;
case '!=':
case 'isnot':
return !$is_logged_in;
default:
return false;
}
}
return false;
}
private function evaluate_post_rule($rule, $post_id) {
if ($rule['value_type'] === 'taxonomy' && !empty($rule['taxonomy']) && !empty($rule['term'])) {
$terms = wp_get_post_terms($post_id, $rule['taxonomy'], array('fields' => 'ids'));
if (is_wp_error($terms)) {
return false;
}
switch ($rule['operator']) {
case '=':
case 'in':
return in_array((int)$rule['term'], $terms);
case '!=':
case 'notin':
return !in_array((int)$rule['term'], $terms);
default:
return false;
}
}
return false;
}
private function get_nag_message() {
$settings = get_option('lcp_paywall_settings', array());
$message = isset($settings['nag_message']) ? $settings['nag_message'] : '';
if (empty($message)) {
$message = '<div class="lcp-paywall-nag">
<h3>This content is locked</h3>
<p>Please upgrade your subscription to access this content.</p>
</div>';
}
return apply_filters('lcp_paywall_nag_message', $message);
}
}