getFile() ), '/vendor/freemius/' ); } /** * Records what the bootstrap-level try/catch swallowed. Both catch sites * below stop this plugin's own code from loading further for the request * (and, if unrelated to Freemius, give no other signal that happened) — * without this, a bug in our own PSR-4 code surfacing here would silently * disable every CPT/REST route/cron with no way to tell why. */ function academiccrew_log_bootstrap_throwable( \Throwable $throwable ): void { error_log( sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log '[AcademicCrew] Bootstrap aborted by %s: %s in %s:%d', get_class( $throwable ), $throwable->getMessage(), $throwable->getFile(), $throwable->getLine() ) ); } /** * Drops every AcademicCrew table and option. Wired to Freemius's * `after_uninstall` action (see below) rather than shipped as an * `uninstall.php` file — Freemius requires the latter to be removed * because its presence stops WordPress from ever firing the * `uninstall_{plugin}` hook Freemius relies on to track uninstalls. */ function academiccrew_run_uninstall_cleanup(): void { global $wpdb; $tables = [ 'academiccrew_member_publications', 'academiccrew_pub_tag_relations', 'academiccrew_pub_tags', 'academiccrew_skip_log', 'academiccrew_excluded_publications', 'academiccrew_sync_log', 'academiccrew_publications', ]; foreach ( $tables as $table ) { $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}{$table}`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } $options = [ 'academiccrew_version', 'academiccrew_db_version', 'academiccrew_settings', 'academiccrew_navmenu_swept', 'academiccrew_members_archive_page_id', 'academiccrew_courses_archive_page_id', 'academiccrew_collaborations_archive_page_id', 'academiccrew_publications_archive_page_id', 'academiccrew_projects_archive_intro', ]; delete_transient( 'academiccrew_generated_page_ids' ); foreach ( $options as $option ) { delete_option( $option ); } } // ── Composer autoloader ────────────────────────────────────────────────── // Wrapped in try/catch because freemius/wordpress-sdk declares start.php as // a Composer "autoload.files" entry — it runs its own top-level bootstrap // code the instant this require executes, before our own Freemius bootstrap // below ever gets a chance to run. A fatal here (e.g. a corrupted vendor/ // copy) would otherwise take the entire site down; instead we self-heal the // licensing state and skip loading the rest of *this* plugin for the // request, letting WordPress and every other plugin continue normally. try { if ( file_exists( ACADEMICCREW_PATH . 'vendor/autoload.php' ) ) { require_once ACADEMICCREW_PATH . 'vendor/autoload.php'; } } catch ( \Throwable $e ) { academiccrew_log_bootstrap_throwable( $e ); if ( academiccrew_is_freemius_throwable( $e ) ) { academiccrew_reset_freemius_state(); } return; } // ── Freemius SDK bootstrap ─────────────────────────────────────────────── // Kept as plain global functions (Freemius' own required pattern) rather // than relying on the PSR-4 autoload above — licensing must never be the // thing that breaks. // // Single-codebase model: one package, gated at runtime via // Core\Freemius::is_pro() (=> can_use_premium_code()). 'is_premium' stays // false — that flag is for a *separate* premium package/zip, which would // make every premium check pass unconditionally regardless of license. // No 'menu' key: pointing it at our existing top-level menu (with OR // without a 'parent' sub-key — both tried) breaks every one of our own // admin submenus with "Sorry, you are not allowed to access this page." // Freemius gets its own independent menu entry instead; harmless. if ( ! function_exists( 'academiccrew_fs' ) ) { function academiccrew_fs() { global $academiccrew_fs; if ( ! isset( $academiccrew_fs ) ) { $sdk_start = ACADEMICCREW_PATH . 'vendor/freemius/wordpress-sdk/start.php'; if ( ! file_exists( $sdk_start ) ) { return null; } require_once $sdk_start; if ( ! function_exists( 'fs_dynamic_init' ) ) { return null; } $academiccrew_fs = fs_dynamic_init( [ 'id' => '39304', 'slug' => 'academic-crew', 'type' => 'plugin', 'public_key' => 'pk_a3ca47ee8298060c9d038aa4cbcdb', 'is_premium' => false, 'has_premium_version' => true, 'has_addons' => false, 'has_paid_plans' => true, 'is_org_compliant' => true, 'menu' => [ 'slug' => 'academiccrew-account', ], ] ); } return $academiccrew_fs; } /** * A fatal thrown synchronously here (SDK already loaded above, now * actually connecting/activating the account) self-heals the same way. * NEITHER of the two try/catch blocks in this file can catch a fatal * thrown later from a hook Freemius registers internally — e.g. during * admin_menu, which is where the one real crash we hit actually * happened. WordPress's own fatal-error handler (wp-settings.php, * registered before any plugin loads) always runs first there and * calls exit(), which unavoidably skips every shutdown function * registered after it, ours included — there is no userland way around * that. For that case WordPress's built-in Recovery Mode (the "check * your site admin email" critical-error screen) is the real safety * net, and it only works if outgoing mail is actually deliverable from * this server. */ try { $academiccrew_fs_instance = academiccrew_fs(); if ( $academiccrew_fs_instance ) { $academiccrew_fs_instance->add_action( 'after_uninstall', 'academiccrew_run_uninstall_cleanup' ); } do_action( 'academiccrew_fs_loaded' ); } catch ( \Throwable $e ) { academiccrew_log_bootstrap_throwable( $e ); if ( academiccrew_is_freemius_throwable( $e ) ) { academiccrew_reset_freemius_state(); } } } require_once ACADEMICCREW_PATH . 'includes/helpers.php'; register_activation_hook( ACADEMICCREW_FILE, [ \AcademicCrew\Core\Activator::class, 'activate' ] ); register_deactivation_hook( ACADEMICCREW_FILE, [ \AcademicCrew\Core\Deactivator::class, 'deactivate' ] ); add_action( 'plugins_loaded', static function (): void { \AcademicCrew\Core\Plugin::get_instance()->run(); } );