log( $message );
}
}
/**
* The main plugin class.
*
* Initializes the plugin, loads dependencies, defines constants,
* and registers hooks. Implements the Singleton pattern.
*
* @since 0.1.0
*/
final class AgeWalletOIDCClientPro {
/**
* Plugin version.
*
* @since 0.1.0
* @var string
*/
const VERSION = AGEWALLET_VERSION;
// --- Constants (Option Keys) ---
// API Credentials
const OPT_CLIENT_ID = 'agewallet_oidc_client_id';
const OPT_CLIENT_SECRET = 'agewallet_oidc_client_secret';
const OPT_HMAC_SECRET = 'agewallet_hmac_secret'; // Needed for signing internal tokens/params
// Gate Appearance Options
const OPT_LOGO_ID = 'agewallet_oidc_logo_id';
const OPT_LOGO_WIDTH_PX = 'agewallet_oidc_logo_width_px';
const OPT_COPY_WYSIWYG = 'agewallet_oidc_copy_wysiwyg';
const OPT_HIDE_HEADING = 'agewallet_oidc_hide_heading';
// Content Guarding Options
const OPT_BLOCK_MODE = 'agewallet_oidc_block_mode';
const OPT_BLOCKED_PATHS = 'agewallet_oidc_blocked_paths';
// Metadata Options
const OPT_METADATA_DEFAULT = 'agewallet_metadata_default';
const OPT_WC_GATE_CHECKOUT = 'agewallet_wc_gate_checkout';
const OPT_WC_METADATA_FIELDS = 'agewallet_wc_checkout_metadata_fields';
// WC checkout-gate mode values (stored in OPT_WC_GATE_CHECKOUT)
const WC_GATE_MODE_OFF = 'off';
const WC_GATE_MODE_FORCE_ALWAYS = 'force-always';
const WC_GATE_MODE_CONDITIONAL_CART = 'conditional-on-cart';
// Metadata length cap (matches server-side acceptance limit)
const METADATA_MAX_BYTES = 4096;
// --- Debugging Option ---
/**
* Option key for enabling the plugin's internal debug logging.
* @since 0.1.0
*/
const OPT_DEBUG_MODE = 'agewallet_oidc_debug_mode';
// Internal Query Vars (for rewrite rules) - Ensure these match rewrite setup
const QV_LAUNCH = 'agewallet_launch';
const QV_CALLBACK = 'agewallet_callback';
const QV_SUCCESS = 'agewallet_success'; // success/cookie-setting endpoint
// AgeWallet API Endpoints (Using .io)
const AUTH_ENDPOINT = 'https://app.agewallet.io/user/authorize';
const TOKEN_ENDPOINT = 'https://app.agewallet.io/user/token';
const USERINFO_ENDPOINT = 'https://app.agewallet.io/user/userinfo';
// --- End Constants ---
/**
* The single instance of the class.
*
* @since 0.1.0
* @var AgeWalletOIDCClientPro|null
*/
private static $instance = null;
/**
* Ensures only one instance of the plugin class is loaded or can be loaded.
* Follows the Singleton pattern.
*
* @since 0.1.0
* @static
* @see AGEWALLET()
* @return AgeWalletOIDCClientPro - Main instance.
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*
* Private to prevent direct object creation. Sets up hooks and loads dependencies.
*
* @since 0.1.0
*/
private function __construct() {
// --- DEBUG LOGGING ---
$this->log_debug( 'Main class __construct started.' );
// --- END DEBUG LOGGING ---
$this->define_constants(); // Define constants needed by other classes
$this->load_dependencies();
$this->setup_hooks();
// --- DEBUG LOGGING ---
$this->log_debug( 'Main class __construct finished.' );
// --- END DEBUG LOGGING ---
}
/**
* Define any necessary dynamic constants.
* Currently empty, but can be used later if needed.
*
* @since 0.1.0
* @access private
*/
private function define_constants() {
// Example: define('MY_CONSTANT', 'value');
}
/**
* Load required dependency files.
* Includes class files from the /includes directory.
* Logs errors if files are missing.
*
* @since 0.1.0
* @access private
*/
private function load_dependencies() {
// --- DEBUG LOGGING ---
$this->log_debug( 'load_dependencies started.' );
// --- END DEBUG LOGGING ---
$includes_path = AGEWALLET_PLUGIN_DIR . 'includes/';
$files_to_include = array(
'class-agewallet-helpers.php',
'class-agewallet-metadata-builder.php',
'class-agewallet-admin.php',
'class-agewallet-oidc-handler.php',
'class-agewallet-gating-manager.php',
'class-agewallet-api.php',
'class-agewallet-product-flags.php',
);
// HOOK: Allow developers to add/remove/change dependency files.
$files_to_include = apply_filters( 'agewallet_dependency_files', $files_to_include );
foreach ( $files_to_include as $filename ) {
$file_path = $includes_path . $filename;
if ( file_exists( $file_path ) ) {
require_once $file_path;
// $this->log_debug("Loaded file: includes/{$filename}");
} else {
// Log a critical error if a required file is missing
$this->log_debug( "ERROR: Failed to load required file: includes/{$filename}. Plugin functionality may be broken." );
// Trigger a user warning or admin notice for missing files
if ( is_admin() ) {
add_action(
'admin_notices',
function () use ( $filename ) {
echo '
';
printf(
/* translators: %s: Name of the missing file. */
esc_html__( 'AgeWallet Plugin Error: Required file "%s" is missing. Please reinstall the plugin.', 'agewallet-oidc-client' ),
esc_html( "includes/{$filename}" )
);
echo '
';
}
);
}
}
}
// --- DEBUG LOGGING ---
$this->log_debug( 'load_dependencies finished.' );
// --- END DEBUG LOGGING ---
}
/**
* Register all necessary WordPress hooks.
* Sets up activation, deactivation, and core plugin hooks.
*
* @since 0.1.0
* @access private
*/
private function setup_hooks() {
// --- DEBUG LOGGING ---
$this->log_debug( 'setup_hooks started.' );
// --- END DEBUG LOGGING ---
// Activation and deactivation hooks. Use static methods.
register_activation_hook( AGEWALLET_PLUGIN_FILE, array( __CLASS__, 'activate_plugin' ) );
register_deactivation_hook( AGEWALLET_PLUGIN_FILE, array( __CLASS__, 'deactivate_plugin' ) );
// Hook to initialize plugin components once WordPress and other plugins are loaded.
add_action( 'plugins_loaded', array( $this, 'init_plugin' ) );
// --- DEBUG LOGGING ---
$this->log_debug( 'setup_hooks finished.' );
// --- END DEBUG LOGGING ---
}
/**
* Initialize plugin components.
* Instantiates core plugin classes after dependencies are loaded.
* Hooked to 'plugins_loaded'.
*
* @since 0.1.0
*/
public function init_plugin() {
// --- DEBUG LOGGING ---
$this->log_debug( 'init_plugin started (hooked on plugins_loaded).' );
// --- END DEBUG LOGGING ---
// Note: load_plugin_textdomain() is no longer called manually since WP 4.6 —
// WordPress.org auto-loads translations under the plugin slug. Removed per
// the Plugin Check tool's recommendation.
// Instantiate helper class (ensure it's available early)
if ( class_exists( 'AgeWallet_Helpers' ) ) {
AgeWallet_Helpers::instance();
$this->log_debug( 'AgeWallet_Helpers instantiated.' );
} else {
$this->log_missing_class( 'AgeWallet_Helpers' );
}
// Instantiate Admin class (registers admin hooks). Check is_admin() first.
if ( is_admin() && class_exists( 'AgeWallet_Admin' ) ) {
AgeWallet_Admin::instance();
$this->log_debug( 'AgeWallet_Admin instantiated.' );
} elseif ( is_admin() ) { // Only log missing admin class if in admin context
$this->log_missing_class( 'AgeWallet_Admin' );
}
// Instantiate OIDC Handler class (registers OIDC endpoint hooks).
if ( class_exists( 'AgeWallet_OIDC_Handler' ) ) {
AgeWallet_OIDC_Handler::instance();
$this->log_debug( 'AgeWallet_OIDC_Handler instantiated.' );
} else {
$this->log_missing_class( 'AgeWallet_OIDC_Handler' );
}
// Instantiate Gating Manager class (registers shortcode, front-end scripts/styles).
if ( class_exists( 'AgeWallet_Gating_Manager' ) ) {
AgeWallet_Gating_Manager::instance();
$this->log_debug( 'AgeWallet_Gating_Manager instantiated.' );
} else {
$this->log_missing_class( 'AgeWallet_Gating_Manager' );
}
// Instantiate API class (registers secure content endpoint & file cache).
if ( class_exists( 'AgeWallet_API' ) ) {
AgeWallet_API::instance();
$this->log_debug( 'AgeWallet_API instantiated.' );
} else {
$this->log_missing_class( 'AgeWallet_API' );
}
// Instantiate WooCommerce integration only when WC is active.
if ( class_exists( 'WooCommerce' ) ) {
$wc_file = AGEWALLET_PLUGIN_DIR . 'includes/class-agewallet-woocommerce.php';
if ( ! class_exists( 'AgeWallet_WooCommerce' ) && file_exists( $wc_file ) ) {
require_once $wc_file;
}
if ( class_exists( 'AgeWallet_WooCommerce' ) ) {
AgeWallet_WooCommerce::instance();
$this->log_debug( 'AgeWallet_WooCommerce instantiated.' );
}
}
// HOOK: Signal that all AgeWallet components are loaded and ready.
do_action( 'agewallet_initialized' );
// --- DEBUG LOGGING ---
$this->log_debug( 'init_plugin finished.' );
// --- END DEBUG LOGGING ---
}
/**
* Plugin activation hook callback (static version).
* Ensures dependencies are loaded if possible, generates HMAC secret, and flushes rewrite rules.
*
* @since 0.1.0
* @static
*/
public static function activate_plugin() {
// --- DEBUG LOGGING ---
agewallet_debug_log( '[AgeWallet Plugin] Static activate_plugin hook fired.' );
// --- END DEBUG LOGGING ---
// Manually include helpers if needed for activation, as instance doesn't exist yet.
$helpers_file = AGEWALLET_PLUGIN_DIR . 'includes/class-agewallet-helpers.php';
if ( file_exists( $helpers_file ) ) {
require_once $helpers_file;
if ( class_exists( 'AgeWallet_Helpers' ) ) {
AgeWallet_Helpers::instance()->ensure_hmac_secret(); // Ensure secret is generated
agewallet_debug_log( '[AgeWallet Plugin] HMAC secret checked/generated during activation.' );
} else {
agewallet_debug_log( '[AgeWallet Plugin] ERROR during activation: AgeWallet_Helpers class not found in included file.' );
}
} else {
agewallet_debug_log( '[AgeWallet Plugin] ERROR during activation: Helpers file not found at ' . $helpers_file );
}
// Manually include OIDC Handler to ensure rules are added before flush
$handler_file = AGEWALLET_PLUGIN_DIR . 'includes/class-agewallet-oidc-handler.php';
if ( file_exists( $handler_file ) ) {
// We need helpers to be loaded first
if ( ! class_exists( 'AgeWallet_Helpers' ) && file_exists( $helpers_file ) ) {
require_once $helpers_file;
}
require_once $handler_file;
if ( class_exists( 'AgeWallet_OIDC_Handler' ) ) {
// Instantiate briefly just to call the rule registration (can't rely on init hook during activation)
$handler_instance = AgeWallet_OIDC_Handler::instance(); // Get instance
$handler_instance->register_rewrite_rules(); // Register rules in memory
agewallet_debug_log( '[AgeWallet Plugin] Rewrite rules registered in memory during activation.' );
} else {
agewallet_debug_log( '[AgeWallet Plugin] ERROR during activation: AgeWallet_OIDC_Handler class not found.' );
}
} else {
agewallet_debug_log( '[AgeWallet Plugin] ERROR during activation: OIDC Handler file not found.' );
}
flush_rewrite_rules(); // Save rules to the database/htaccess
// --- DEBUG LOGGING ---
agewallet_debug_log( '[AgeWallet Plugin] Rewrite rules flushed during activation.' );
// --- END DEBUG LOGGING ---
// HOOK: Allow other plugins to perform actions on activation.
do_action( 'agewallet_activated' );
}
/**
* Plugin deactivation hook callback (static version).
* Flushes rewrite rules to remove custom endpoints.
*
* @since 0.1.0
* @static
*/
public static function deactivate_plugin() {
// --- DEBUG LOGGING ---
agewallet_debug_log( '[AgeWallet Plugin] Static deactivate_plugin hook fired.' );
// --- END DEBUG LOGGING ---
// Clear scheduled cron event
wp_clear_scheduled_hook( 'agewallet_scheduled_purge_event' );
flush_rewrite_rules(); // Remove/Save rules
// --- DEBUG LOGGING ---
agewallet_debug_log( '[AgeWallet Plugin] Rewrite rules flushed during deactivation.' );
// --- END DEBUG LOGGING ---
// HOOK: Allow other plugins to perform actions on deactivation.
do_action( 'agewallet_deactivated' );
}
// --- Helper methods for logging (can be used by instance after __construct) ---
/**
* Logs a debug message if logging is enabled.
* Prepends '[AgeWallet Plugin]' to the message. Uses instance context.
*
* @since 0.1.0
* @param string $message The message to log.
* @param mixed $context Optional context data (array, object, string).
*/
private function log_debug( $message, $context = null ) {
// Logic will be handled centrally in AgeWallet_Helpers::log()
if ( class_exists( 'AgeWallet_Helpers' ) && method_exists( AgeWallet_Helpers::instance(), 'log' ) ) {
AgeWallet_Helpers::instance()->log( '[Main] ' . $message, $context ); // Add context prefix
}
}
/**
* Logs an error message when a required class is not found. Uses instance context.
* Also triggers an admin notice if in the admin area.
*
* @since 0.1.0
* @param string $class_name The name of the missing class.
* @param string $stage Optional stage description (e.g., 'Activation', 'Init').
*/
private function log_missing_class( $class_name, $stage = 'Init' ) {
$log_entry = sprintf( 'ERROR [%s]: Class "%s" not found. Check file inclusion.', $stage, $class_name );
$this->log_debug( $log_entry ); // Use log_debug which will check settings
// Trigger an admin notice as well
if ( is_admin() ) {
// Use a static variable to ensure the hook is added only once, even if called multiple times
static $notice_added = false;
if ( ! $notice_added ) {
add_action(
'admin_notices',
function () use ( $class_name ) {
echo '';
printf(
/* translators: %s: Name of the missing class file (guessed). */
esc_html__( 'AgeWallet Plugin Error: A required file is missing or unloadable (%s). Please reinstall the plugin.', 'agewallet-oidc-client' ),
esc_html( str_replace( '_', '-', strtolower( $class_name ) ) . '.php' ) // Guess filename
);
echo '
';
}
);
$notice_added = true;
}
}
}
// --- Singleton Pattern Boilerplate ---
/**
* Cloning is forbidden. Prevents duplication of the singleton instance.
* @since 0.1.0
*/
public function __clone() {
_doing_it_wrong( __FUNCTION__, esc_html__( 'Cloning is forbidden.', 'agewallet-oidc-client' ), '0.1.0' );
}
/**
* Unserializing instances of this class is forbidden. Prevents unserializing.
* @since 0.1.0
*/
public function __wakeup() {
_doing_it_wrong( __FUNCTION__, esc_html__( 'Unserializing instances of this class is forbidden.', 'agewallet-oidc-client' ), '0.1.0' );
}
} // End final class AgeWalletOIDCClientPro
/**
* Begins execution of the plugin.
* Retrieves the singleton instance of the main plugin class.
*
* @since 0.1.0
* @return AgeWalletOIDCClientPro
*/
function AGEWALLET() {
// --- DEBUG LOGGING ---
// This initial log will run if WP_DEBUG_LOG is on, before our setting is checked.
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
agewallet_debug_log( '[AgeWallet Plugin] === AGEWALLET() function called ===' );
}
// --- END DEBUG LOGGING ---
return AgeWalletOIDCClientPro::instance();
}
// Get the plugin running.
// --- DEBUG LOGGING ---
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
agewallet_debug_log( '[AgeWallet Plugin] Calling AGEWALLET() to instantiate plugin.' );
}
// --- END DEBUG LOGGING ---
AGEWALLET();
// --- DEBUG LOGGING ---
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
agewallet_debug_log( '[AgeWallet Plugin] Main plugin file finished loading.' );
}
// --- END DEBUG LOGGING ---