admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Ajax_Driver::NONCE ),
'jobNonce' => wp_create_nonce( Ajax_Driver::JOB_NONCE ),
'loginUrl' => wp_login_url(),
'i18n' => array(
'relogin' => __( 'The site now runs the restored backup, and this browser is not logged in to it. The restore is finishing in the background. Log in with an administrator account from the backup, then come back to this page to keep it or roll it back.', 'accessnow-site-replica' ),
'login' => __( 'Log in', 'accessnow-site-replica' ),
'confirmCancel' => __( 'Cancel this job? A partial backup is kept but marked incomplete.', 'accessnow-site-replica' ),
'lost' => __( 'Lost contact with this site. Retrying…', 'accessnow-site-replica' ),
'stopped' => __( 'Stopped: this site stopped answering. Check your connection, then press Resume.', 'accessnow-site-replica' ),
'leave' => __( 'A job is running. It will pause if you leave this page (you can resume it later).', 'accessnow-site-replica' ),
'copied' => __( 'Copied', 'accessnow-site-replica' ),
/* translators: %1$d: number of backups, %2$s: total size. */
'selected' => __( '%1$d selected (%2$s)', 'accessnow-site-replica' ),
/* translators: %1$d: number of backups, %2$s: total size. */
'bulkConfirm' => __( 'Delete %1$d backups (%2$s) permanently?', 'accessnow-site-replica' ),
'bulkNewest' => __( 'This includes the newest backup of a site: the next backup of that site will download everything again.', 'accessnow-site-replica' ),
'from' => _x( 'from', 'pull backup from a site', 'accessnow-site-replica' ),
'expired' => __( 'This page lost its link to the job (it was opened again elsewhere). Reload the page to keep following it: the job keeps its progress.', 'accessnow-site-replica' ),
/* translators: %s: time left, e.g. "5 minutes". */
'eta' => __( 'about %s left', 'accessnow-site-replica' ),
/* translators: %s: how long, e.g. "40 seconds". */
'working' => __( 'still working, %s since the last update', 'accessnow-site-replica' ),
'planTitle' => __( 'Review the restore', 'accessnow-site-replica' ),
'planIntro' => __( 'Nothing on this site has changed yet. The backup is imported into temporary tables first; this site is only switched at the end, and you can roll back afterwards.', 'accessnow-site-replica' ),
'planFrom' => __( 'From', 'accessnow-site-replica' ),
'planInto' => __( 'Into (this site)', 'accessnow-site-replica' ),
'planBackup' => __( 'Backup made', 'accessnow-site-replica' ),
'planDatabase' => __( 'Database', 'accessnow-site-replica' ),
'planFiles' => __( 'Files', 'accessnow-site-replica' ),
'planReplace' => __( 'Addresses and paths that will be changed', 'accessnow-site-replica' ),
'planMoved' => __( 'Tables that exist only on this site and will be removed (kept until you choose Keep):', 'accessnow-site-replica' ),
'planRemoved' => __( 'Plugins that exist only on this site and will be removed (Roll back returns them):', 'accessnow-site-replica' ),
'planKept' => __( 'Media and must-use plugins that exist only on this site are kept.', 'accessnow-site-replica' ),
'optKeepLogin' => __( 'Keep me logged in (carry my account over)', 'accessnow-site-replica' ),
'optDiscourage' => __( 'Discourage search engines from indexing this copy', 'accessnow-site-replica' ),
'optMail' => __( 'Block all outgoing email from this copy', 'accessnow-site-replica' ),
'optSsl' => __( 'Deactivate SSL-forcing plugins (this site uses http)', 'accessnow-site-replica' ),
'optGuid' => __( 'Leave post GUIDs unchanged (recommended when moving a live site)', 'accessnow-site-replica' ),
'optFiles' => __( 'Restore files (uploads, plugins, themes)', 'accessnow-site-replica' ),
/* translators: %s: this site's host name. */
'typeHost' => __( 'This replaces a live site. Type %s to confirm:', 'accessnow-site-replica' ),
'start' => __( 'Replace this site with the backup', 'accessnow-site-replica' ),
'keep' => __( 'Keep the restored site', 'accessnow-site-replica' ),
'rollback' => __( 'Roll back', 'accessnow-site-replica' ),
'openSite' => __( 'Open the site in a new tab', 'accessnow-site-replica' ),
'confirmKeep' => __( 'Keep the restored site and delete the previous version?', 'accessnow-site-replica' ),
'confirmRoll' => __( 'Put the previous site back exactly as it was?', 'accessnow-site-replica' ),
'rows' => __( 'rows', 'accessnow-site-replica' ),
'tables' => __( 'tables', 'accessnow-site-replica' ),
/* translators: %s: size, e.g. 12 MB. */
'uploadedN' => __( '%s uploaded', 'accessnow-site-replica' ),
/* translators: %s: size, e.g. 12 MB. */
'downloaded' => __( '%s downloaded', 'accessnow-site-replica' ),
/* translators: 1: current step, 2: number of steps. */
'step' => __( 'step %1$s of %2$s', 'accessnow-site-replica' ),
/* translators: %s: number of files. */
'file1' => _n( '%s file', '%s files', 1, 'accessnow-site-replica' ),
/* translators: %s: number of files. */
'fileN' => _n( '%s file', '%s files', 2, 'accessnow-site-replica' ),
/* translators: %s: number of rows. */
'row1' => _n( '%s row', '%s rows', 1, 'accessnow-site-replica' ),
/* translators: %s: number of rows. */
'rowN' => _n( '%s row', '%s rows', 2, 'accessnow-site-replica' ),
/* translators: %s: number of warnings. */
'warning1' => _n( '%s warning', '%s warnings', 1, 'accessnow-site-replica' ),
/* translators: %s: number of warnings. */
'warningN' => _n( '%s warning', '%s warnings', 2, 'accessnow-site-replica' ),
/* translators: %s: number of unchanged files. */
'unchangedN' => __( '%s unchanged', 'accessnow-site-replica' ),
/* translators: %s: seconds. */
'retryingIn' => __( 'retrying in %s s', 'accessnow-site-replica' ),
'status' => array(
'queued' => __( 'Starting', 'accessnow-site-replica' ),
'running' => __( 'Running', 'accessnow-site-replica' ),
'waiting' => __( 'Waiting to retry', 'accessnow-site-replica' ),
'paused' => __( 'Paused', 'accessnow-site-replica' ),
'failed' => __( 'Stopped with an error', 'accessnow-site-replica' ),
'cancelled' => __( 'Cancelled', 'accessnow-site-replica' ),
'completed' => __( 'Completed', 'accessnow-site-replica' ),
'awaiting' => __( 'Waiting for you', 'accessnow-site-replica' ),
),
'types' => array(
'pull' => __( 'Pull backup', 'accessnow-site-replica' ),
'clone' => __( 'Clone into this site', 'accessnow-site-replica' ),
'restore' => __( 'Restore', 'accessnow-site-replica' ),
'backup' => __( 'Back up this site', 'accessnow-site-replica' ),
'cloud' => __( 'Cloud backup', 'accessnow-site-replica' ),
'cloud_db' => __( 'Database backup to the cloud', 'accessnow-site-replica' ),
'import' => __( 'Download from the cloud', 'accessnow-site-replica' ),
),
'backgroundHint' => __( 'This backup runs on the server: you can close this page. You will find the result here and, if you chose so, by e-mail.', 'accessnow-site-replica' ),
),
)
);
}
/* -------------------------------------------------------------------------
* Pages
* ---------------------------------------------------------------------- */
/**
* Page wrapper start.
*
* @param string $title Title.
*/
public static function open( $title ) {
echo '
' . esc_html( $title ) . '
';
try {
// Only a folder that exists can be served; opening a page must not create it.
if ( Storage::exists() && true === Storage::web_exposed() ) {
echo '
' . esc_html__( 'The backup folder can be downloaded from the web on this server (it ignores the folder’s .htaccess protection). Ask your host to stop serving that folder, or set ACCESSNOW_REPLICA_STORAGE_DIR in wp-config.php to a folder the web server does not serve, before making backups here.', 'accessnow-site-replica' ) . '
';
}
} catch ( \RuntimeException $e ) {
echo '
' . esc_html( $e->getMessage() ) . '
';
}
self::mail_guard_notice();
}
/**
* Says so while this copy is blocking its own outgoing email.
*
* A restore can install a guard that stops a copied site emailing the original's customers.
* That is the right default for a copy, and a mystery if nobody says it is there — so it is
* said, on this plugin's own pages, with the way to undo it.
*/
private static function mail_guard_notice() {
if ( ! in_array( 'accessnow-replica-mail-guard.php', array_map( 'basename', wp_get_mu_plugins() ), true ) ) {
return;
}
echo '
' . esc_html__( 'This copy is not sending email. A restore installed a guard so a copied site cannot email the original site’s people. Delete wp-content/mu-plugins/accessnow-replica-mail-guard.php to allow email again.', 'accessnow-site-replica' ) . '
';
}
/**
* Page wrapper end.
*/
public static function close() {
echo '
' . esc_html__( 'Saved connections can no longer be decrypted (the security keys in wp-config.php changed). Add the connections again.', 'accessnow-site-replica' ) . '
';
self::log_table( Logger::entries( Logger::site_dir() . 'activity.jsonl', 100 ) );
// On a site that is being pulled, the requests from the other site are logged here.
$audit = Logger::entries( Logger::site_dir() . 'source-audit.jsonl', 100 );
if ( $audit ) {
echo '
' . esc_html__( 'Pull requests from other sites', 'accessnow-site-replica' ) . '
';
self::log_table( $audit );
}
self::close();
}
/**
* Settings.
*/
public static function page_settings() {
self::open( __( 'Settings', 'accessnow-site-replica' ) );
$s = Settings::all();
?>
__( 'Pull backup', 'accessnow-site-replica' ),
'clone' => __( 'Clone into this site', 'accessnow-site-replica' ),
'restore' => __( 'Restore', 'accessnow-site-replica' ),
'backup' => __( 'Back up this site', 'accessnow-site-replica' ),
'cloud' => __( 'Cloud backup', 'accessnow-site-replica' ),
'cloud_db' => __( 'Database backup to the cloud', 'accessnow-site-replica' ),
'import' => __( 'Download from the cloud', 'accessnow-site-replica' ),
);
return $labels[ $type ] ?? ucfirst( (string) $type );
}
/**
* Live job panel (driven by admin.js).
*
* @param Job $job Job.
*/
public static function job_panel( Job $job ) {
$token = Job_Store::reissue_token( $job );
$summary = Job_Runner::summary( $job );
printf(
'',
esc_attr( $job->id() ),
esc_attr( $token ),
esc_attr( (string) wp_json_encode( $summary ) )
);
self::job_template();
}
/**
* Markup template for job panels.
*/
public static function job_template() {
static $printed = false;
if ( $printed ) {
return;
}
$printed = true;
?>