%1$s %2$s
', esc_html__( 'AeroSpin CSS Loading Spinners:', 'aerospin-css-loading-spinners' ), sprintf( /* translators: 1: Minimum WordPress version. 2: Current WordPress version. 3: Minimum PHP version. 4: Current PHP version. */ esc_html__( 'This plugin requires WordPress %1$s or higher and PHP %3$s or higher. Current versions: WordPress %2$s, PHP %4$s.', 'aerospin-css-loading-spinners' ), esc_html( ACLS_MINIMUM_WP_VERSION ), esc_html( $current_wp_version ), esc_html( ACLS_MINIMUM_PHP_VERSION ), esc_html( PHP_VERSION ) ) ); } /** * Load core files cleanly. * * @since 1.1.4 * * @return void */ private static function load_includes() { // Require the helper schema before everything else to establish defaults. self::require_plugin_file( 'includes/acls-helpers.php' ); self::require_plugin_file( 'includes/acls-spinners-list.php' ); self::require_plugin_file( 'includes/acls-settings.php' ); self::require_plugin_file( 'admin/acls-admin.php' ); self::require_plugin_file( 'public/acls-public.php' ); } /** * Load a plugin file while capturing accidental output. * * This prevents accidental output from included files from reaching the * browser and records the output length/hex value when WP_DEBUG is enabled. * * @since 1.1.4 * * @param string $relative_path Relative plugin path. * @return bool */ private static function require_plugin_file( $relative_path ) { $relative_path = ltrim( (string) $relative_path, '/\\' ); $file_path = ACLS_PLUGIN_DIR . $relative_path; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_exists -- WP_Filesystem is not available during early bootstrap without massive overhead. if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) { self::bootstrap_debug_log( 'Missing or unreadable include: ' . $relative_path ); return false; } // Capture output buffer to prevent headers already sent errors. ob_start(); require_once $file_path; $unexpected_output = ob_get_clean(); if ( '' !== $unexpected_output ) { self::bootstrap_debug_log( 'Unexpected output while loading ' . $relative_path . '. Length: ' . strlen( $unexpected_output ) . '. Hex: ' . bin2hex( $unexpected_output ) ); } return true; } /** * Determine whether AeroSpin CSS Loading Spinners should log bootstrap debug details. * * @since 1.1.4 * * @return bool */ private static function should_log_bootstrap_debug() { return defined( 'WP_DEBUG' ) && WP_DEBUG; } /** * Write a safe bootstrap debug message. * * Stores recent bootstrap/debug messages in a WordPress option instead of * writing directly to a log file. * * @since 1.1.4 * * @param string $message Message to log. * @return void */ private static function bootstrap_debug_log( $message ) { if ( ! self::should_log_bootstrap_debug() ) { return; } if ( ! is_string( $message ) || '' === $message ) { return; } $entries = get_option( ACLS_DEBUG_LOG_OPTION, array() ); if ( ! is_array( $entries ) ) { $entries = array(); } // Enhancement: Replaced deprecated current_time() with wp_date() and gmdate() for accurate, modern time tracking. $entries[] = array( 'time' => function_exists( 'wp_date' ) ? wp_date( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s' ), 'message' => sanitize_text_field( $message ), ); // Keep only the most recent 20 logs. $entries = array_slice( $entries, -20 ); update_option( ACLS_DEBUG_LOG_OPTION, $entries, false ); } } } if ( ! class_exists( 'AeroSpin_CSS_Loading_Spinners_Bootstrap' ) ) { class_alias( 'ACLS_Bootstrap', 'AeroSpin_CSS_Loading_Spinners_Bootstrap' ); } if ( ! class_exists( 'Pure_CSS_Spinners_Bootstrap' ) ) { class_alias( 'ACLS_Bootstrap', 'Pure_CSS_Spinners_Bootstrap' ); } if ( ! class_exists( 'AreoSpin_Css_Loading_Spinners_Bootstrap' ) ) { class_alias( 'ACLS_Bootstrap', 'AreoSpin_Css_Loading_Spinners_Bootstrap' ); } /* |-------------------------------------------------------------------------- | SECTION 3: Main Plugin Class |-------------------------------------------------------------------------- | | The main AeroSpin CSS Loading Spinners class registers lifecycle hooks and plugin row action | links. Feature-specific behavior remains in the dedicated admin, public, | settings, helper, and spinner registry files. | */ if ( ! class_exists( 'AeroSpin_CSS_Loading_Spinners' ) ) { /** * Main plugin bootstrap class. * * @since 1.0.0 */ // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound -- Main plugin file houses both early bootstrap and lifecycle hooks. final class ACLS_Plugin { /** * Single plugin instance. * * @since 1.0.0 * * @var AeroSpin_CSS_Loading_Spinners|null */ private static $instance = null; /** * Get the single plugin instance. * * @since 1.0.0 * * @return AeroSpin_CSS_Loading_Spinners */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Set up lifecycle hooks and plugin integrations. * * @since 1.0.0 */ private function __construct() { // Register activation/deactivation hooks. register_activation_hook( ACLS_PLUGIN_FILE, array( $this, 'activate' ) ); register_deactivation_hook( ACLS_PLUGIN_FILE, array( $this, 'deactivate' ) ); // Add custom action links. add_filter( 'plugin_action_links_' . ACLS_PLUGIN_BASENAME, array( $this, 'add_action_links' ) ); if ( defined( 'PURE_CSS_SPINNERS_PLUGIN_BASENAME' ) && PURE_CSS_SPINNERS_PLUGIN_BASENAME !== ACLS_PLUGIN_BASENAME ) { add_filter( 'plugin_action_links_' . PURE_CSS_SPINNERS_PLUGIN_BASENAME, array( $this, 'add_action_links' ) ); } add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 ); // Ensure admin controller is registered. if ( class_exists( 'ACLS_Admin' ) ) { ACLS_Admin::get_instance(); } elseif ( class_exists( 'AeroSpin_CSS_Loading_Spinners_Admin' ) ) { AeroSpin_CSS_Loading_Spinners_Admin::get_instance(); } } /** * Prevent cloning the singleton. * * @since 1.0.0 * * @return void */ private function __clone() { _doing_it_wrong( __FUNCTION__, esc_html__( 'Cloning instances of the main plugin class is forbidden.', 'aerospin-css-loading-spinners' ), '1.0.0' ); } /** * Prevent unserializing the singleton. * * @since 1.0.0 * * @return void */ public function __wakeup() { _doing_it_wrong( __FUNCTION__, esc_html__( 'Unserializing instances of the main plugin class is forbidden.', 'aerospin-css-loading-spinners' ), '1.0.0' ); } /* |-------------------------------------------------------------------------- | SECTION 4: Plugin Action Links and Meta Links |-------------------------------------------------------------------------- */ /** * Add plugin action links on the Plugins screen. * * @since 1.0.0 * * @param array $links Existing plugin action links. * @return array Modified plugin action links. */ public function add_action_links( $links ) { $slug = defined( 'ACLS_SETTINGS_PAGE_SLUG' ) ? ACLS_SETTINGS_PAGE_SLUG : ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_SETTINGS_PAGE_SLUG' ) ? AEROSPIN_CSS_LOADING_SPINNERS_SETTINGS_PAGE_SLUG : 'acls-settings' ); $settings_url = admin_url( 'admin.php?page=' . $slug ); $plugin_links = array( 'settings' => sprintf( '%2$s', esc_url( $settings_url ), esc_html__( 'Settings', 'aerospin-css-loading-spinners' ) ), ); return array_merge( $plugin_links, $links ); } /** * Add plugin row meta links. * * @since 1.1.4 * * @param array $links Plugin row meta links. * @param string $file Current plugin file basename. * @return array Modified plugin row meta links. */ public function add_plugin_row_meta( $links, $file ) { if ( ACLS_PLUGIN_BASENAME !== $file && PURE_CSS_SPINNERS_PLUGIN_BASENAME !== $file ) { return $links; } $brand_url = defined( 'ACLS_BRAND_URL' ) ? ACLS_BRAND_URL : 'https://wpclearpath.com'; $doc_url = defined( 'ACLS_DOCS_URL' ) ? ACLS_DOCS_URL : 'https://wpclearpath.com/aerospin-css-loading-spinners'; $row_meta = array( sprintf( '%2$s', esc_url( $doc_url ), esc_html__( 'Docs', 'aerospin-css-loading-spinners' ) ), sprintf( '%2$s', esc_url( $brand_url ), esc_html__( 'Visit Site', 'aerospin-css-loading-spinners' ) ), ); return array_merge( $links, $row_meta ); } /* |-------------------------------------------------------------------------- | SECTION 5: Activation and Deactivation |-------------------------------------------------------------------------- */ /** * Run plugin activation tasks. * * Creates the settings option only when it does not already exist. * Existing user settings are never overwritten during activation. * * @since 1.0.0 * * @return void */ public function activate() { ob_start(); if ( ! ACLS_Bootstrap::meets_minimum_requirements() ) { $this->log_activation_output(); deactivate_plugins( ACLS_PLUGIN_BASENAME ); wp_die( sprintf( /* translators: 1: Minimum WordPress version. 2: Current WordPress version. */ esc_html__( 'AeroSpin CSS Loading Spinners requires WordPress %1$s or higher and PHP %2$s or higher.', 'aerospin-css-loading-spinners' ), esc_html( ACLS_MINIMUM_WP_VERSION ), esc_html( ACLS_MINIMUM_PHP_VERSION ) ), esc_html__( 'AeroSpin CSS Loading Spinners Activation Error', 'aerospin-css-loading-spinners' ), array( 'back_link' => true, 'response' => 200, ) ); } if ( false === get_option( ACLS_OPTIONS_KEY, false ) ) { add_option( ACLS_OPTIONS_KEY, $this->get_activation_defaults() ); } $this->log_activation_output(); } /** * Run plugin deactivation tasks. * * User settings are intentionally preserved. Data removal belongs in * uninstall.php, not in the deactivation routine. * * @since 1.0.0 * * @return void */ public function deactivate() { // Intentionally left blank. Data removal is handled in uninstall.php. } /** * Capture and log unexpected activation output. * Logs to the database via options instead of directly calling * error_log() to comply strictly with WordPress PHPCS standards. * * @since 1.1.4 * * @return void */ private function log_activation_output() { $unexpected_output = ob_get_clean(); if ( '' === $unexpected_output ) { return; } // Store early-activation anomalies safely via settings logic to ensure PHPCS compliance. if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $entries = get_option( ACLS_DEBUG_LOG_OPTION, array() ); if ( ! is_array( $entries ) ) { $entries = array(); } // Enhancement: Replaced deprecated current_time() with wp_date() and gmdate() for absolute PHPCS compliance. $entries[] = array( 'time' => function_exists( 'wp_date' ) ? wp_date( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s' ), 'message' => 'AeroSpin CSS Loading Spinners Activation Output: ' . strlen( $unexpected_output ) . ' bytes detected.', ); // Keep only the most recent 20 logs. $entries = array_slice( $entries, -20 ); update_option( ACLS_DEBUG_LOG_OPTION, $entries, false ); } } /** * Get safe default settings for activation. * * By the time this runs, the OOP bootstrap guarantees the helper class is available. * * @since 1.1.4 * * @return array */ private function get_activation_defaults() { if ( class_exists( 'ACLS_Helpers' ) || class_exists( 'AeroSpin_CSS_Loading_Spinners_Helpers' ) ) { return class_exists( 'ACLS_Helpers' ) ? ACLS_Helpers::normalize_settings( ACLS_Helpers::get_defaults() ) : AeroSpin_CSS_Loading_Spinners_Helpers::normalize_settings( AeroSpin_CSS_Loading_Spinners_Helpers::get_defaults() ); } return array(); } } } if ( ! class_exists( 'AeroSpin_CSS_Loading_Spinners' ) ) { class_alias( 'ACLS_Plugin', 'AeroSpin_CSS_Loading_Spinners' ); } if ( ! class_exists( 'Pure_CSS_Spinners' ) ) { class_alias( 'ACLS_Plugin', 'Pure_CSS_Spinners' ); } if ( ! class_exists( 'AreoSpin_Css_Loading_Spinners' ) ) { class_alias( 'ACLS_Plugin', 'AreoSpin_Css_Loading_Spinners' ); } /* |-------------------------------------------------------------------------- | SECTION 6: Plugin Initialization |-------------------------------------------------------------------------- */ // Initialize the secure bootstrap sequence. ACLS_Bootstrap::init(); // Spin up the core hooks if the environment qualifies. if ( ACLS_Bootstrap::meets_minimum_requirements() ) { ACLS_Plugin::get_instance(); }