<?php

/**
 * Plugin Name: Bobasoftware Publisher Paywall
 * Plugin URI: https://bobasoftware.com/
 * Description: A WordPress paywall plugin with subscriptions, powered by Stripe. Free version includes subscription monetization, free publish window, and magic login. Pro adds tip unlock, per-article purchases, and free-post quotas.
 * Version: 2.0.3
 * Author: Boba Software
 * Author URI: https://bobasoftware.com/about
 * License: GPLv2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: bobasoftware-publisher-paywall
 * Requires at least: 6.0
 * Requires PHP: 7.4
 * Tested up to: 7.1
 *
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
if ( !function_exists( 'bspp_fs' ) ) {
    function bspp_fs() {
        global $bspp_fs;
        if ( !isset( $bspp_fs ) ) {
            require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
            $bspp_fs = fs_dynamic_init( array(
                'id'               => '29558',
                'slug'             => 'simplepress-paywall',
                'type'             => 'plugin',
                'public_key'       => 'pk_690e9d68faba56863456041cd6efa',
                'is_premium'       => false,
                'premium_suffix'   => 'Pro',
                'has_addons'       => false,
                'has_paid_plans'   => true,
                'is_org_compliant' => true,
                'trial'            => array(
                    'days'               => 14,
                    'is_require_payment' => false,
                ),
                'menu'             => array(
                    'slug'    => 'bobasoftware-publisher-paywall',
                    'support' => false,
                    'pricing' => false,
                ),
                'is_live'          => true,
            ) );
        }
        return $bspp_fs;
    }

    bspp_fs();
    do_action( 'bspp_fs_loaded' );
}
/**
 * Cached Freemius license gate. Front-end/reader paths must only read options — never call Freemius.
 *
 * Fail-closed: missing or >48h-old sync timestamp means locked, even if the cached flag says active.
 *
 * @return bool
 */
if ( !function_exists( 'bspp_can_use_premium_code' ) ) {
    function bspp_can_use_premium_code() {
        $last_sync = (int) get_option( 'bobasoftware_pp_license_last_sync', 0 );
        if ( $last_sync <= 0 || time() - $last_sync > 48 * HOUR_IN_SECONDS ) {
            return false;
        }
        return (int) get_option( 'bobasoftware_pp_license_active', 0 ) === 1;
    }

}
/**
 * Write our license-gate options from the Freemius SDK's current in-memory state.
 *
 * Does not call the Freemius API. Use after the SDK has already refreshed (Account
 * Sync License, after_license_change, or a successful _sync_cron()).
 *
 * @return void
 */
if ( !function_exists( 'bspp_refresh_premium_license_cache' ) ) {
    function bspp_refresh_premium_license_cache() {
        if ( !function_exists( 'bspp_fs' ) ) {
            return;
        }
        $fs = bspp_fs();
        if ( !is_object( $fs ) || !method_exists( $fs, 'is_paying_or_trial' ) ) {
            return;
        }
        $is_active = (bool) $fs->is_paying_or_trial();
        update_option( 'bobasoftware_pp_license_active', ( $is_active ? 1 : 0 ) );
        update_option( 'bobasoftware_pp_license_last_sync', time() );
        update_option( 'bobasoftware_pp_license_remote_sync', 1 );
    }

}
/**
 * Refresh cached license state from Freemius. Runs only on guarded admin_init.
 *
 * @return void
 */
if ( !function_exists( 'bspp_sync_premium_license_status' ) ) {
    function bspp_sync_premium_license_status() {
        if ( !is_admin() || wp_doing_cron() || !current_user_can( 'manage_options' ) ) {
            return;
        }
        if ( !function_exists( 'bspp_fs' ) ) {
            return;
        }
        $fs = bspp_fs();
        if ( !is_object( $fs ) || !method_exists( $fs, 'is_paying_or_trial' ) ) {
            return;
        }
        $force_sync = false;
        if ( method_exists( $fs, 'get_unique_affix' ) ) {
            $sync_action = $fs->get_unique_affix() . '_sync_license';
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only detection of Freemius Sync License; does not change state by itself.
            $fs_action = ( isset( $_REQUEST['fs_action'] ) ? sanitize_key( wp_unslash( $_REQUEST['fs_action'] ) ) : '' );
            if ( $fs_action === $sync_action ) {
                $force_sync = true;
            }
        }
        $last_sync = (int) get_option( 'bobasoftware_pp_license_last_sync', 0 );
        $has_remote_sync = (int) get_option( 'bobasoftware_pp_license_remote_sync', 0 ) === 1;
        // Throttle live Freemius API pulls to 15 minutes. Do not apply this until a remote
        // sync has succeeded at least once, otherwise leftover cache-only last_sync values
        // from the previous implementation would skip the first real pull.
        // Skip the throttle for Account → Sync License so a reactivation is not stuck
        // behind a recent cancel-sync for up to 15 minutes.
        if ( !$force_sync && $has_remote_sync && $last_sync > 0 && time() - $last_sync < 15 * MINUTE_IN_SECONDS ) {
            return;
        }
        if ( !method_exists( $fs, '_sync_cron' ) ) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- server-side diagnostic; not shown to readers.
            error_log( 'Bobasoftware Publisher Paywall: Freemius license sync skipped; _sync_cron() is not available on this SDK.' );
            return;
        }
        if ( class_exists( 'FS_Api' ) && method_exists( 'FS_Api', 'is_temporary_down' ) && FS_Api::is_temporary_down() ) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- server-side diagnostic; not shown to readers.
            error_log( 'Bobasoftware Publisher Paywall: Freemius license sync skipped; API is temporarily unavailable.' );
            return;
        }
        try {
            /*
             * This uses an underscore-prefixed quasi-internal Freemius SDK method because there is no
             * officially public live-sync API. If a future Freemius SDK upgrade breaks this, check the
             * SDK's own Account page "Sync License" button handler (fs_action={slug}_sync_license) for
             * the current equivalent internal call.
             */
            $fs->_sync_cron();
        } catch ( \Throwable $e ) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- server-side diagnostic; not shown to readers.
            error_log( 'Bobasoftware Publisher Paywall: Freemius license sync failed: ' . $e->getMessage() );
            return;
        }
        bspp_refresh_premium_license_cache();
    }

    add_action( 'admin_init', 'bspp_sync_premium_license_status', 20 );
    if ( function_exists( 'bspp_fs' ) ) {
        $bspp_fs_for_hooks = bspp_fs();
        if ( is_object( $bspp_fs_for_hooks ) && method_exists( $bspp_fs_for_hooks, 'add_action' ) ) {
            // after_license_change: SDK applied a plan/license change to local state.
            // after_account_plan_sync: _sync_license() finished (Account → Sync License).
            $bspp_fs_for_hooks->add_action( 'after_license_change', 'bspp_refresh_premium_license_cache' );
            $bspp_fs_for_hooks->add_action( 'after_account_plan_sync', 'bspp_refresh_premium_license_cache' );
        }
    }
}
/**
 * HTML for the publisher-facing inactive-license notice.
 *
 * @return string
 */
if ( !function_exists( 'bspp_get_license_inactive_notice_html' ) ) {
    function bspp_get_license_inactive_notice_html() {
        $reactivate_url = '';
        if ( function_exists( 'bspp_fs' ) ) {
            $fs = bspp_fs();
            if ( is_object( $fs ) && method_exists( $fs, 'get_account_url' ) ) {
                $reactivate_url = (string) $fs->get_account_url();
            }
            if ( $reactivate_url === '' && is_object( $fs ) && method_exists( $fs, 'get_upgrade_url' ) ) {
                $reactivate_url = (string) $fs->get_upgrade_url();
            }
        }
        if ( $reactivate_url === '' ) {
            $reactivate_url = admin_url( 'admin.php?page=bobasoftware-publisher-paywall' );
        }
        $message = sprintf( 
            /* translators: 1: opening anchor tag, 2: closing anchor tag */
            __( 'Your Pro license isn\'t active. %1$sReactivate on Freemius%2$s to use this feature.', 'bobasoftware-publisher-paywall' ),
            '<a href="' . esc_url( $reactivate_url ) . '">',
            '</a>'
         );
        return '<div class="notice notice-warning inline"><p>' . wp_kses( $message, array(
            'a' => array(
                'href' => true,
            ),
        ) ) . '</p></div>';
    }

}
/**
 * Echo the publisher-facing inactive-license notice.
 *
 * @return void
 */
if ( !function_exists( 'bspp_render_license_inactive_notice' ) ) {
    function bspp_render_license_inactive_notice() {
        echo wp_kses_post( bspp_get_license_inactive_notice_html() );
    }

}
/**
 * Log the real license-block reason server-side only (never shown to readers).
 *
 * @param string $context Short identifier for the blocked code path.
 * @return void
 */
if ( !function_exists( 'bspp_log_premium_license_blocked' ) ) {
    function bspp_log_premium_license_blocked(  $context  ) {
        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- server-side diagnostic; not shown to readers.
        error_log( 'Bobasoftware Publisher Paywall: Pro feature blocked (Freemius license inactive or license sync stale). Context: ' . sanitize_key( (string) $context ) );
    }

}
/**
 * Reader-safe message when a Pro offer cannot run because the publisher license is inactive.
 *
 * @return string
 */
if ( !function_exists( 'bspp_premium_offer_unavailable_message' ) ) {
    function bspp_premium_offer_unavailable_message() {
        return __( 'This option is temporarily unavailable.', 'bobasoftware-publisher-paywall' );
    }

}
/**
 * Stop a Pro admin_post request when the license is inactive.
 *
 * Publishers see the shared license notice; readers see a generic unavailable message.
 *
 * @param string $context Short identifier for the blocked code path.
 * @return void
 */
if ( !function_exists( 'bspp_die_premium_feature_unavailable' ) ) {
    function bspp_die_premium_feature_unavailable(  $context  ) {
        bspp_log_premium_license_blocked( $context );
        if ( current_user_can( 'manage_options' ) ) {
            wp_die( wp_kses_post( bspp_get_license_inactive_notice_html() ), '', array(
                'response' => 403,
            ) );
        }
        wp_die( esc_html( bspp_premium_offer_unavailable_message() ) );
    }

}
// Define constants.
if ( !defined( 'BSPP_VERSION' ) ) {
    define( 'BSPP_VERSION', '2.0.3' );
}
define( 'BSPP_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
define( 'BSPP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
// Load required files.
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-plugin.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-settings-repository.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-membership-repository.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-purchase-repository.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-webhook-event-repository.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-magic-link-repository.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-email-health.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-magic-login.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-stripe-checkout.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-stripe-webhook.php';
require_once BSPP_PLUGIN_PATH . 'database/class-bspp-db-schema.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-paywall-engine.php';
require_once BSPP_PLUGIN_PATH . 'includes/class-bspp-paywall-overlay.php';
// Activation hook.
register_activation_hook( __FILE__, 'bspp_activate_plugin' );
function bspp_activate_plugin() {
    BSPP_DB_Schema::create_tables();
    update_option( 'bobasoftware_pp_db_schema_version', BSPP_DB_Schema::VERSION );
    $settings_repository = new BSPP_Settings_Repository();
    $settings_repository->seed_defaults_if_missing();
}

// Deactivation hook (data retained for re-activation).
register_deactivation_hook( __FILE__, 'bspp_deactivate_plugin' );
function bspp_deactivate_plugin() {
    // Intentionally minimal: no table drops; cron/transients may be cleared in future.
}

// Initialize plugin.
function bspp_init_plugin() {
    return new BSPP_Plugin();
}

bspp_init_plugin();