AI-SiteArk: '; echo esc_html__( 'Two copies of this plugin are installed and active (the “ai-siteark” and the legacy “ai-sitevault” folders). Only one is running. Please deactivate or delete the duplicate under Plugins to clear this warning — your backups are not affected.', 'ai-siteark' ); echo '
'; } ); return; } /* * First copy to load. The ENTIRE plugin body is wrapped in this function_exists() guard so the * declarations below are CONDITIONAL — PHP registers unconditional top-level functions at COMPILE * time (before the runtime `return` above can ever run), so only a conditional declaration keeps * a second copy's file from fataling with "Cannot redeclare…" the moment it is compiled. The * matching closing brace is at the very end of this file. */ if ( ! function_exists( 'aisiteark_self_disable' ) ) : define( 'AISITEARK_LOADED', true ); /* ------------------------------------------------------------------------- * Constants * ---------------------------------------------------------------------- */ define( 'AISITEARK_VERSION', '2.12.1' ); define( 'AISITEARK_DB_VERSION', '1.0.0' ); define( 'AISITEARK_FILE', __FILE__ ); define( 'AISITEARK_DIR', plugin_dir_path( __FILE__ ) ); define( 'AISITEARK_URL', plugin_dir_url( __FILE__ ) ); define( 'AISITEARK_BASENAME', plugin_basename( __FILE__ ) ); define( 'AISITEARK_MIN_PHP', '7.4' ); define( 'AISITEARK_MIN_WP', '6.0' ); define( 'AISITEARK_DOTORG', true ); // WordPress.org free build — see tools/build-dotorg.sh. define( 'AISITEARK_SAFE_MODE', 'aisiteark_safe_mode' ); // Option set by the fatal fuse below. /* ------------------------------------------------------------------------- * Fatal fuse — AI-SiteArk must never be the reason a site is down. * * A fatal thrown while WordPress is LOADING plugins kills every request: front end and * wp-admin, for every visitor, until someone edits files over FTP. For the plugin that owns * this site's restores, that is the worst possible failure — the tool you'd reach for to fix * the site is the thing that broke it, and you can't even log in to switch it off. * * So we arm a fuse for the whole of our own load. If PHP dies during it and the fatal came * from a file inside THIS plugin, we deactivate ourselves on the way out — written straight * to the database, because our own classes may be exactly what is broken. The failing request * is already lost, but the NEXT one loads a healthy site and the admin gets a notice * explaining what happened. Recovery becomes "reinstall the plugin", never "restore the site". * * Scope is deliberately load-time only: a fatal inside a backup/restore step (say, a host * OOM on a huge archive) fails that job, not the site, and must NOT silently switch off the * user's backups. See aisiteark_disarm_fuse(). * ---------------------------------------------------------------------- */ $GLOBALS['aisiteark_loading'] = true; /** * Trip into safe mode: record the fatal straight to the database so that from the NEXT * request onwards this plugin loads but boots nothing. * * Written with raw $wpdb on purpose — our own classes may be exactly what is broken, and the * options cache is meaningless on a request that is already dying. * * Safe mode rather than deactivation: a deactivated plugin cannot run, so it cannot tell you * why it went away — you would just find AI-SiteArk mysteriously switched off. Staying * "active" but inert keeps the site up AND lets us show the reason. * * @param array $err The fatal from error_get_last(). * @return void */ function aisiteark_self_disable( array $err ) { global $wpdb; if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) { return; // Too early to reach the database — nothing safe we can do. } $why = maybe_serialize( array( 'time' => time(), 'message' => isset( $err['message'] ) ? substr( (string) $err['message'], 0, 500 ) : '', 'file' => isset( $err['file'] ) ? (string) $err['file'] : '', 'line' => isset( $err['line'] ) ? (int) $err['line'] : 0, 'version' => defined( 'AISITEARK_VERSION' ) ? AISITEARK_VERSION : '', ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $exists = $wpdb->get_var( $wpdb->prepare( "SELECT option_id FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", AISITEARK_SAFE_MODE ) ); if ( $exists ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->update( $wpdb->options, array( 'option_value' => $why ), array( 'option_name' => AISITEARK_SAFE_MODE ) ); } else { // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->insert( $wpdb->options, array( 'option_name' => AISITEARK_SAFE_MODE, 'option_value' => $why, 'autoload' => 'yes' ) ); } } /** * Shutdown handler: did WE just kill this request while the site was loading? * * @return void */ function aisiteark_fatal_fuse() { if ( empty( $GLOBALS['aisiteark_loading'] ) ) { return; // Past load — a job failing is not a site outage. } $err = error_get_last(); if ( ! $err || ! in_array( (int) $err['type'], array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) { return; // Clean shutdown, or a mere warning/notice. } $file = isset( $err['file'] ) ? str_replace( '\\', '/', (string) $err['file'] ) : ''; $dir = str_replace( '\\', '/', AISITEARK_DIR ); if ( '' === $file || 0 !== strpos( $file, $dir ) ) { return; // Someone else's fatal — never switch off another plugin's problem. } aisiteark_self_disable( $err ); } register_shutdown_function( 'aisiteark_fatal_fuse' ); /** * Our load finished without dying — disarm, so later job failures don't disable backups. * * @return void */ function aisiteark_disarm_fuse() { $GLOBALS['aisiteark_loading'] = false; } add_action( 'plugins_loaded', 'aisiteark_disarm_fuse', 99 ); /* ------------------------------------------------------------------------- * Autoloader — maps the AtoZ\SiteArk\ namespace to WordPress-style filenames. * * AtoZ\SiteArk\Activator => includes/class-activator.php * AtoZ\SiteArk\Helpers\Settings => includes/helpers/class-settings.php * AtoZ\SiteArk\Providers\Storage\... => includes/providers/storage/class-...php * AtoZ\SiteArk\Admin\Admin => admin/class-admin.php * ---------------------------------------------------------------------- */ spl_autoload_register( function ( $class ) { $prefix = 'AtoZ\\SiteArk\\'; if ( 0 !== strpos( $class, $prefix ) ) { return; } $relative = substr( $class, strlen( $prefix ) ); $parts = explode( '\\', $relative ); $name = array_pop( $parts ); $file = 'class-' . str_replace( '_', '-', strtolower( $name ) ) . '.php'; $base = AISITEARK_DIR . 'includes/'; if ( ! empty( $parts ) && 'Admin' === $parts[0] ) { $base = AISITEARK_DIR . 'admin/'; array_shift( $parts ); } elseif ( ! empty( $parts ) && 'Front' === $parts[0] ) { $base = AISITEARK_DIR . 'public/'; array_shift( $parts ); } $sub = ''; foreach ( $parts as $part ) { $sub .= strtolower( str_replace( '_', '-', $part ) ) . '/'; } $path = $base . $sub . $file; if ( is_readable( $path ) ) { require_once $path; } } ); /* ------------------------------------------------------------------------- * Activation / deactivation * ---------------------------------------------------------------------- */ /** * Activation. Clears any previous fuse trip (a reinstall is the documented repair, so a * deliberate reactivation means "try me again"), and refuses to run the activator at all on * a half-installed copy rather than fataling the Plugins screen. * * @return void */ function aisiteark_activate() { delete_option( AISITEARK_SAFE_MODE ); // A deliberate reactivation means "try me again". if ( aisiteark_missing_core_files() ) { return; } \AtoZ\SiteArk\Activator::activate(); } register_activation_hook( __FILE__, 'aisiteark_activate' ); register_deactivation_hook( __FILE__, array( 'AtoZ\SiteArk\Deactivator', 'deactivate' ) ); /* ------------------------------------------------------------------------- * Bootstrap * ---------------------------------------------------------------------- */ /** * Files this plugin cannot boot without that are missing from disk. * * Guards against a HALF-INSTALLED plugin folder — an update whose request timed out * between "delete the old folder" and "extract the new one", a partial FTP upload, or a * host's file sync hiccupping. WordPress only checks that the MAIN plugin file exists; if * one of our other files is missing, the first reference to a class inside it throws an * uncaught Error that white-screens the ENTIRE SITE ("Class AtoZ\SiteArk\Helpers\Plan * not found"). A backup plugin taking the whole site down is never an acceptable failure * mode, so we detect it and stay inert with an actionable notice instead. * * @return string[] Missing plugin-relative paths (empty when the install is intact). */ function aisiteark_missing_core_files() { static $missing = null; if ( null !== $missing ) { return $missing; } $missing = array(); foreach ( array( 'includes/class-siteark.php', 'includes/helpers/class-plan.php', 'includes/helpers/class-settings.php', ) as $rel ) { if ( ! is_readable( AISITEARK_DIR . $rel ) ) { $missing[] = $rel; } } return $missing; } /** * Tell an administrator how to repair a half-installed copy, since the plugin itself is * deliberately doing nothing until it is whole again. * * @return void */ function aisiteark_incomplete_notice() { if ( ! current_user_can( 'activate_plugins' ) ) { return; } // The fuse tripped on an earlier request: we are in safe mode to keep the site up. $fuse = get_option( AISITEARK_SAFE_MODE ); if ( is_array( $fuse ) ) { echo '' . esc_html__( 'AI-SiteArk has switched itself into safe mode.', 'ai-siteark' ) . ' ' . esc_html__( 'It hit a fatal error while your site was loading, so it stopped running rather than let your site go down. Your site is fine and your backups are untouched — but backups and restores are paused until this is fixed. Reinstall AI-SiteArk (Plugins → Add New → Upload Plugin), which repairs an interrupted or damaged update, then deactivate and reactivate it to clear safe mode.', 'ai-siteark' ) . '
' . esc_html( (string) $fuse['message'] ) . '
' . esc_html__( 'AI-SiteArk is not fully installed.', 'ai-siteark' ) . ' ' . esc_html__( 'Some of its files are missing — an update was most likely interrupted. AI-SiteArk has switched itself off to keep your site running. Reinstall it (Plugins → Add New → Upload Plugin) to restore backups and restores. Your existing backups are untouched.', 'ai-siteark' ) . '