'wowm-plugin', 'utm_medium' => $medium, 'utm_campaign' => 'winonweb-migrate'], self::SITE) . ($anchor ? '#' . $anchor : ''); } const ICONS = [ 'database' => '', 'uploads' => '', 'themes' => '', 'plugins' => '', 'muplugins' => '', 'other' => '', 'export' => '', 'import' => '', 'backups' => '', 'schedule' => '', 'download' => '', 'restore' => '', 'trash' => '', 'check' => '', 'x' => '', 'arrow' => '', 'warning' => '', 'info' => '', 'error' => '', 'plus' => '', 'globe' => '', 'shield' => '', 'file' => '', 'chevron' => '', 'login' => '', 'help' => '', 'tools' => '', 'search' => '', 'undo' => '', 'layers' => '', ]; public static function init(): void { add_action('admin_menu', [self::class, 'menu']); add_action('admin_enqueue_scripts', [self::class, 'assets']); add_filter('plugin_action_links_' . Plugin::basename(), function ($links) { array_unshift($links, '' . esc_html__('Open', 'winonweb-migrate') . ''); return $links; }); add_filter( 'plugin_row_meta', function ($meta, $file) { if ($file === Plugin::basename()) { $meta[] = '' . esc_html__('Guide', 'winonweb-migrate') . ''; $meta[] = '' . esc_html__('Migration help from WinOnWeb', 'winonweb-migrate') . ''; } return $meta; }, 10, 2, ); } public static function icon(string $name, int $size = 20): string { return sprintf('', $size, self::ICONS[$name] ?? ''); } public static function the_icon(string $name, int $size = 20): void { $shape = ['d' => true, 'cx' => true, 'cy' => true, 'r' => true, 'rx' => true, 'ry' => true, 'x' => true, 'y' => true, 'width' => true, 'height' => true, 'points' => true, 'x1' => true, 'x2' => true, 'y1' => true, 'y2' => true]; echo wp_kses(self::icon($name, $size), [ 'svg' => ['class' => true, 'width' => true, 'height' => true, 'viewbox' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'aria-hidden' => true], 'path' => $shape, 'circle' => $shape, 'ellipse' => $shape, 'rect' => $shape, 'line' => $shape, 'polyline' => $shape, ]); } public static function menu(): void { $icon = 'data:image/svg+xml;base64,' . base64_encode(''); add_menu_page(__('WinOnWeb Migrate', 'winonweb-migrate'), __('Migrate', 'winonweb-migrate'), 'manage_options', self::PAGE, [self::class, 'render'], $icon, 80); } public static function assets(string $hook): void { if ($hook !== 'toplevel_page_' . self::PAGE) { return; } // Before the page data is built, so an expired undo is not offered. Rollback::expire(); $css = WOWM_DIR . 'assets/admin.css'; $js = WOWM_DIR . 'assets/admin.js'; wp_enqueue_style('wowm-admin', WOWM_URL . 'assets/admin.css', [], WOWM_VERSION . '.' . filemtime($css)); wp_enqueue_script('wowm-admin', WOWM_URL . 'assets/admin.js', [], WOWM_VERSION . '.' . filemtime($js), true); $max_upload = wp_max_upload_size(); wp_localize_script('wowm-admin', 'WOWM', [ 'ajax' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce(Ajax::NONCE), 'chunk' => (int) max(256 * KB_IN_BYTES, min(8 * MB_IN_BYTES, $max_upload * 0.8)), 'home' => untrailingslashit((string) get_option('home')), 'login' => wp_login_url(admin_url('admin.php?page=' . self::PAGE)), 'backups' => Ajax::backups_payload(), 'schedule' => ['settings' => Scheduler::settings(), 'next' => Scheduler::next_run()], 'rollback' => self::rollback_payload(), 'lastImport' => Rollback::last_import(), 'activeJob' => Scheduler::active(), 'now' => time(), 'icons' => array_map(function ($svg) { return ''; }, self::ICONS), 'i18n' => [ 'calculating' => __('Calculating…', 'winonweb-migrate'), 'convertTitle' => /* translators: %s: plugin the archive was made with, e.g. Duplicator */ __('Converting the %s archive', 'winonweb-migrate'), 'checksTitle' => __('Server check', 'winonweb-migrate'), 'rollbackTitle' => /* translators: %s: relative time, e.g. "2 hours ago" */ __('Import finished %s', 'winonweb-migrate'), 'rollbackText' => __('The previous database is kept for 7 days, so this import can be undone. Is the site working as expected?', 'winonweb-migrate'), 'rollbackKeep' => __('Everything works, remove the old database', 'winonweb-migrate'), 'rollbackUndo' => __('Undo import', 'winonweb-migrate'), 'confirmUndo' => __('Put the previous database back? The imported database is discarded. Files stay as they are (use the safety backup to restore them).', 'winonweb-migrate'), 'undone' => __('The previous database is back. Log in again.', 'winonweb-migrate'), 'kept' => __('Old database removed', 'winonweb-migrate'), 'undoNote' => __('Not happy with the result? "Undo import" at the top of this page brings the previous database back.', 'winonweb-migrate'), 'healthTitle' => __('Site check after import', 'winonweb-migrate'), 'healthOk' => __('The site answers normally.', 'winonweb-migrate'), 'healthBroken' => __('The site shows an error after this import.', 'winonweb-migrate'), /* translators: %s: countdown like 9:41 */ 'autoUndoIn' => __('The previous database comes back automatically in %s unless you keep the import.', 'winonweb-migrate'), 'autoUndoOff' => __('Automatic undo is off: use "Undo import" or "Keep" below.', 'winonweb-migrate'), 'keepAnyway' => __('Keep the import anyway', 'winonweb-migrate'), 'undoNow' => __('Undo import now', 'winonweb-migrate'), 'recheck' => __('Check again', 'winonweb-migrate'), 'recoveryTitle' => __('If wp-admin stops opening', 'winonweb-migrate'), 'recoveryText' => __('When a plugin or theme causes a critical error, WordPress emails the site administrator a recovery-mode link. Log in through it and click "Undo import". From the command line:', 'winonweb-migrate'), 'recoveryCli' => 'wp wowm undo', 'undoneTitle' => __('Import undone', 'winonweb-migrate'), 'undoneText' => __('The login page showed a critical error right after the import, so the previous database was put back automatically. Fix the cause shown below (usually the PHP version or a plugin) and import again. Files from the archive stayed in place; the backup made before the import restores them.', 'winonweb-migrate'), 'deactivatedTitle' => __('Deactivated after import', 'winonweb-migrate'), 'deactivatedText' => __('Their files are not on this server or they belong to the previous host. Reinstall or re-enable them from Plugins if you need them.', 'winonweb-migrate'), 'autoUndo' => __('If the site shows a critical error after the import, undo it automatically: at once when wp-admin cannot open, otherwise after 10 minutes (recommended)', 'winonweb-migrate'), 'resumeTitle' => __('Continue the interrupted upload?', 'winonweb-migrate'), /* translators: 1: file name, 2: size already uploaded */ 'resumeText' => __('%1$s: %2$s already on the server. Pick the same file to continue from there.', 'winonweb-migrate'), 'resumeDiscard' => __('Start over', 'winonweb-migrate'), /* translators: %s: size already uploaded */ 'resuming' => __('Resuming from %s…', 'winonweb-migrate'), 'reattachTitle' => __('Still running in the background', 'winonweb-migrate'), 'reattachText' => __('This job kept going while the page was closed. Reconnected to its progress.', 'winonweb-migrate'), 'backgroundNote' => __('You can close this tab: the job continues on the server and this page reconnects when you come back.', 'winonweb-migrate'), 'differential' => __('Changes only', 'winonweb-migrate'), 'basedOn' => /* translators: %s: date of the full backup */ __('on top of the backup from %s', 'winonweb-migrate'), 'confirmDeleteChain' => /* translators: %d: number of changes-only backups */ __('Delete this full backup? Its %d changes-only backups are deleted too, they cannot be restored without it.', 'winonweb-migrate'), 'baseNone' => __('Everything (full backup)', 'winonweb-migrate'), 'baseChanges' => /* translators: %s: date of the full backup */ __('Only files changed since %s', 'winonweb-migrate'), 'scanNeedle' => __('Old address, e.g. old-domain.com', 'winonweb-migrate'), 'scanning' => /* translators: %s: progress in percent */ __('Scanning… %s%%', 'winonweb-migrate'), 'noHits' => __('Nothing found. The database is clean.', 'winonweb-migrate'), 'hitsTitle' => /* translators: %s: number of rows */ __('Found in %s places', 'winonweb-migrate'), 'table' => __('Table', 'winonweb-migrate'), 'column' => __('Column', 'winonweb-migrate'), 'matches' => __('Rows', 'winonweb-migrate'), 'replaceTitle' => __('Replacing in the database', 'winonweb-migrate'), 'replaceDone' => __('Replacement finished', 'winonweb-migrate'), 'replaceResult' => /* translators: %s: number of rows */ __('%s rows updated.', 'winonweb-migrate'), 'confirmReplace' => /* translators: 1: text to find, 2: replacement */ __('Replace "%1$s" with "%2$s" in the whole database? A database backup is made first.', 'winonweb-migrate'), 'replaceYes' => __('Replace', 'winonweb-migrate'), 'dbBackupTitle' => __('Backing up the database first', 'winonweb-migrate'), 'changed' => __('Changed', 'winonweb-migrate'), 'counting' => __('counting…', 'winonweb-migrate'), 'excludeFolder' => __('Click to leave this out of the archive', 'winonweb-migrate'), 'includeFolder' => __('Excluded. Click to include it again', 'winonweb-migrate'), 'exportTitle' => __('Exporting your site', 'winonweb-migrate'), 'importTitle' => __('Importing archive', 'winonweb-migrate'), 'preBackupTitle' => __('Backing up this site first', 'winonweb-migrate'), 'restoreTitle' => __('Restoring backup', 'winonweb-migrate'), 'exportDone' => __('Your archive is ready', 'winonweb-migrate'), 'importDone' => __('Import complete', 'winonweb-migrate'), 'failed' => __('Something went wrong', 'winonweb-migrate'), 'cancelled' => __('Cancelled', 'winonweb-migrate'), 'cancel' => __('Cancel', 'winonweb-migrate'), 'cancelling' => __('Cancelling…', 'winonweb-migrate'), 'close' => __('Close', 'winonweb-migrate'), 'download' => __('Download', 'winonweb-migrate'), 'restore' => __('Restore', 'winonweb-migrate'), 'delete' => __('Delete', 'winonweb-migrate'), 'login' => __('Log in again', 'winonweb-migrate'), 'viewSite' => __('View site', 'winonweb-migrate'), 'loginNote' => __('Users and passwords now come from the imported site. Log in with an account from that site.', 'winonweb-migrate'), 'elapsed' => __('Elapsed', 'winonweb-migrate'), 'tables' => __('Tables', 'winonweb-migrate'), 'rows' => __('Rows', 'winonweb-migrate'), 'files' => __('Files', 'winonweb-migrate'), 'packed' => __('Packed', 'winonweb-migrate'), 'unpacked' => __('Unpacked', 'winonweb-migrate'), 'archive' => __('Archive', 'winonweb-migrate'), 'uploading' => /* translators: %s: file name */ __('Uploading %s', 'winonweb-migrate'), 'checking' => __('Checking archive…', 'winonweb-migrate'), 'badFile' => __('Choose a .wowm, All-in-One .wpress or Duplicator .zip file.', 'winonweb-migrate'), 'badResponse' => __('The server returned an unexpected response.', 'winonweb-migrate'), 'retrying' => __('Connection hiccup, retrying…', 'winonweb-migrate'), 'timedOut' => __('The server did not answer in time.', 'winonweb-migrate'), /* translators: 1: error message, 2: attempt number, 3: number of attempts */ 'uploadRetry' => __('%1$s Retrying (%2$s of %3$s)…', 'winonweb-migrate'), 'bigFileNote' => __('Large file: this can take a long time on shared hosting, and the bar may pause for a minute between pieces. It is still working, keep this tab open. For files of a few GB it is faster to SFTP the archive into wp-content/wowm-backups and use Restore from Backups.', 'winonweb-migrate'), /* translators: %s: piece size, e.g. 2 MB */ 'chunkNote' => __('This host limits the upload size, so the file is sent in %s pieces. It is slower but reliable: keep this tab open and wait. For very large files, SFTP into wp-content/wowm-backups and use Restore from Backups instead.', 'winonweb-migrate'), 'confirmDelete' => __('Delete this backup permanently?', 'winonweb-migrate'), 'deleted' => __('Backup deleted', 'winonweb-migrate'), 'saved' => __('Schedule saved', 'winonweb-migrate'), 'noBackups' => __('No backups yet. Create your first export, it takes a minute.', 'winonweb-migrate'), 'nextRun' => /* translators: %s: date and time */ __('Next backup: %s', 'winonweb-migrate'), 'scheduleOff' => __('Automatic backups are off.', 'winonweb-migrate'), 'free' => /* translators: %s: size, e.g. 12 GB */ __('%s free on disk', 'winonweb-migrate'), 'origin' => [ 'manual' => __('Manual', 'winonweb-migrate'), 'scheduled' => __('Scheduled', 'winonweb-migrate'), 'pre-import' => __('Before import', 'winonweb-migrate'), 'uploaded' => __('Uploaded', 'winonweb-migrate'), 'cli' => __('WP-CLI', 'winonweb-migrate'), ], 'components' => [ 'database' => __('Database', 'winonweb-migrate'), 'uploads' => __('Media', 'winonweb-migrate'), 'themes' => __('Themes', 'winonweb-migrate'), 'plugins' => __('Plugins', 'winonweb-migrate'), 'muplugins' => __('MU plugins', 'winonweb-migrate'), 'other' => __('Other', 'winonweb-migrate'), ], 'from' => __('From', 'winonweb-migrate'), 'to' => __('To', 'winonweb-migrate'), 'created' => __('Created', 'winonweb-migrate'), 'size' => __('Size', 'winonweb-migrate'), 'wordpress' => __('WordPress', 'winonweb-migrate'), 'php' => __('PHP', 'winonweb-migrate'), 'theme' => __('Theme', 'winonweb-migrate'), 'plugins' => __('Active plugins', 'winonweb-migrate'), 'nothingSelected' => __('Select at least one thing to export.', 'winonweb-migrate'), 'findPlaceholder' => __('Find, e.g. old-domain.com', 'winonweb-migrate'), 'replacePlaceholder' => __('Replace with', 'winonweb-migrate'), 'remove' => __('Remove', 'winonweb-migrate'), 'readyToImport' => __('Ready to import', 'winonweb-migrate'), 'understand' => /* translators: %s: site address */ __('I understand this replaces the content of %s', 'winonweb-migrate'), 'importNow' => __('Import now', 'winonweb-migrate'), 'importDb' => __('Import the database', 'winonweb-migrate'), 'importFiles' => __('Import files', 'winonweb-migrate'), 'keepDropins' => __('Keep this host\'s drop-ins (recommended)', 'winonweb-migrate'), 'preBackup' => /* translators: %s: list of parts, e.g. Database, Themes */ __('First back up what gets replaced: %s (recommended)', 'winonweb-migrate'), 'preBackupLabel' => /* translators: %s: address of the site the archive comes from */ __('Before importing %s', 'winonweb-migrate'), 'extraReplace' => __('Extra find & replace', 'winonweb-migrate'), 'addRule' => __('Add rule', 'winonweb-migrate'), 'filesOnlyNote' => __('Files were imported. The database was not changed.', 'winonweb-migrate'), ], ]); } /** * Rollback state for the screen. */ public static function rollback_payload(): ?array { $data = Rollback::get(); if (!$data) { return null; } return $data; } public static function render(): void { Storage::cleanup(); require WOWM_DIR . 'views/admin.php'; } }