Esipher admin page under * wp-admin. */ class Plugin { /** * Singleton instance. * * @var Plugin|null */ private static $instance = null; /** * Namespace-prefix to directory map, longest prefix first so that * sub-namespaces (Adapters, Cli) are matched before the generic * top-level Esipher\ prefix. * * @var array */ private $namespace_map = array(); /** * Get the singleton instance, creating it on first call. * * @return Plugin */ public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Constructor: registers the autoloader and CLI command. */ private function __construct() { $this->namespace_map = array( 'Esipher\\Adapters\\' => ESIPHER_PLUGIN_DIR . 'adapters/', 'Esipher\\Cli\\' => ESIPHER_PLUGIN_DIR . 'cli/', 'Esipher\\Admin\\' => ESIPHER_PLUGIN_DIR . 'admin/', 'Esipher\\' => ESIPHER_PLUGIN_DIR . 'includes/', ); spl_autoload_register( array( $this, 'autoload' ) ); if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'esipher', Cli\Cli_Command::class ); } if ( function_exists( 'is_admin' ) && is_admin() ) { ( new Admin\Admin_Page() )->register(); ( new Admin\Github_Admin_Page() )->register(); ( new Admin\Mobile_App_Admin_Page() )->register(); } // Registered unconditionally, not inside the is_admin() branch // above: GitHub's own webhook deliveries and the hourly cron // safety-net check both need this wired up on ordinary // front-end requests too, not just wp-admin ones. ( new Github_Sync_Service() )->register(); register_deactivation_hook( ESIPHER_PLUGIN_FILE, array( __CLASS__, 'on_deactivate' ) ); } /** * Clear the sync-check cron event on deactivation — WordPress * doesn't do this automatically, and a scheduled hook pointing at * a deactivated plugin's class would otherwise fire into nothing * (harmlessly, since WP-Cron simply can't find the callback, but * needlessly) until the site's cron table is next cleaned up. */ public static function on_deactivate() { $timestamp = wp_next_scheduled( Github_Sync_Service::CRON_HOOK ); if ( $timestamp ) { wp_unschedule_event( $timestamp, Github_Sync_Service::CRON_HOOK ); } } /** * Spl_autoload_register callback. Maps a namespaced class/interface/ * trait name to the WordPress file-naming convention: `Class_Name` * becomes `class-class-name.php` (`interface-`/`trait-` for those). * * @param string $class_name Fully-qualified class/interface/trait name. */ public function autoload( $class_name ) { foreach ( $this->namespace_map as $prefix => $dir ) { if ( 0 !== strpos( $class_name, $prefix ) ) { continue; } $relative = substr( $class_name, strlen( $prefix ) ); $slug = strtolower( str_replace( '_', '-', $relative ) ); foreach ( array( 'class-' . $slug, 'interface-' . $slug, 'trait-' . $slug ) as $basename ) { $file = $dir . $basename . '.php'; if ( file_exists( $file ) ) { require $file; return; } } } } }