` the tab's script moves by
* calling the step action in a loop. Without the script the run still
* finishes through WP-Cron, and reloading the tab shows how far it got.
*/
final class ImportTab {
/** Script handle. */
public const SCRIPT = 'ops404-al-history-import';
/**
* Enqueue the progress script. Called by SettingsPage::assets() on this
* plugin's screen only.
*/
public static function enqueue(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- which tab is showing; display only.
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : '';
if ( 'import' !== $tab ) {
return;
}
wp_enqueue_script(
self::SCRIPT,
plugins_url( 'assets/js/history-import.js', OPS_ACTIVITY_LOG_FILE ),
array(),
OPS_ACTIVITY_LOG_VERSION,
true
);
wp_localize_script(
self::SCRIPT,
'ops404AlImport',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'action' => ImportActions::STEP_ACTION,
'failed' => __( 'The import paused because a step failed. It will carry on in the background, or when you reload this page.', '404ops-activity-log' ),
/* translators: 1: rows processed so far, 2: rows in all. */
'counter' => __( '%1$s of %2$s processed', '404ops-activity-log' ),
)
);
}
/**
* Render the whole tab.
*/
public static function render(): void {
self::render_result_notice();
echo '
' . esc_html__( 'Import history from another activity log', '404ops-activity-log' ) . '
';
echo '' . esc_html__( 'Switching from Simple History, WP Activity Log or Stream? Bring their history with you. The other plugin\'s tables are only read, never changed, and it can be deactivated first.', '404ops-activity-log' ) . '
';
self::render_rules();
foreach ( Importer::sources() as $source ) {
// The network source (#8) is the super admins': a main-site
// administrator does not see it (ImportActions refuses it too).
if ( ! current_user_can( Importer::capability( $source->key() ) ) ) {
continue;
}
self::render_source( $source );
}
}
/**
* What every imported row is held to, stated before anyone presses Import.
*/
private static function render_rules(): void {
$cutoff = Importer::retention_cutoff();
echo '';
if ( null === $cutoff ) {
echo '- ' . esc_html__( 'Retention is unlimited, so events of any age are imported.', '404ops-activity-log' ) . '
';
} else {
$days = ActivityLog::retention_days_or_default( get_option( 'ops_activity_log_retention_days', ActivityLog::DEFAULT_RETENTION_DAYS ) );
echo '- ' . esc_html(
sprintf(
/* translators: 1: retention period in days, 2: the oldest date kept (UTC). */
__( 'Events older than your retention period (%1$d days, before %2$s UTC) are skipped.', '404ops-activity-log' ),
$days,
$cutoff
)
) . '
';
}
echo '- ' . esc_html__( 'Events by users you exclude from the log are skipped.', '404ops-activity-log' ) . '
';
echo '- ' . esc_html(
(bool) get_option( EventLogger::OPTION_STORE_FULL_IPS, false )
? __( 'IP addresses are kept in full, as your settings say.', '404ops-activity-log' )
: __( 'IP addresses are truncated, as your settings say.', '404ops-activity-log' )
) . '
';
echo '- ' . esc_html__( 'Values this log never stores, such as passwords, keys and email addresses in messages, are withheld.', '404ops-activity-log' ) . '
';
echo '- ' . esc_html__( 'Running an import again only adds events not already imported.', '404ops-activity-log' ) . '
';
echo '
';
}
/**
* The notice for the result an action redirected back with. Only a known
* code is ever shown.
*/
private static function render_result_notice(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only.
$code = isset( $_GET[ ImportActions::RESULT_ARG ] ) ? sanitize_key( wp_unslash( $_GET[ ImportActions::RESULT_ARG ] ) ) : '';
if ( ! in_array( $code, ImportActions::RESULTS, true ) ) {
return;
}
$messages = array(
'started' => __( 'Import started. It runs in the background; keep this page open to watch it, or come back later.', '404ops-activity-log' ),
'running' => __( 'That import is already running.', '404ops-activity-log' ),
'empty' => __( 'That source has no events to import.', '404ops-activity-log' ),
'unsupported' => __( 'That source cannot be imported. See the reason below.', '404ops-activity-log' ),
'cancelled' => __( 'Import stopped. Events already imported are kept; starting again skips them.', '404ops-activity-log' ),
'not_running' => __( 'That import was not running.', '404ops-activity-log' ),
'bad_request' => __( 'That request was not understood.', '404ops-activity-log' ),
'bad_nonce' => __( 'The link has expired. Reload the page and try again.', '404ops-activity-log' ),
'forbidden' => __( 'You are not allowed to import history.', '404ops-activity-log' ),
);
$is_error = ! in_array( $code, array( 'started', 'cancelled', 'empty' ), true );
echo '' . esc_html( $messages[ $code ] ) . '
';
}
/**
* One source's panel.
*
* @param Source $source The source.
*/
private static function render_source( Source $source ): void {
$key = $source->key();
$detection = $source->detect();
echo '';
echo '
' . esc_html( $source->label() ) . '
';
if ( Importer::targets_network( $key ) ) {
echo '
' . esc_html__( 'What Stream recorded in the network admin (network settings, network plugin activation, sites). These events belong to no site: they are imported into the network log, under the main site\'s settings.', '404ops-activity-log' ) . '
';
}
if ( Detection::ABSENT === $detection->status ) {
echo '
' . esc_html(
sprintf(
/* translators: %s: plugin name. */
__( 'No %s tables were found on this site.', '404ops-activity-log' ),
$source->label()
)
) . '
';
return;
}
if ( Detection::UNSUPPORTED === $detection->status ) {
echo '' . esc_html( $detection->reason ) . '
';
return;
}
$preview = $source->preview();
$state = Importer::state( $key );
echo '';
self::row( __( 'Tables', '404ops-activity-log' ), $detection->shape );
self::row( __( 'Events', '404ops-activity-log' ), number_format_i18n( $preview['count'] ) );
self::row(
__( 'Date range (UTC)', '404ops-activity-log' ),
null === $preview['first'] ? '—' : $preview['first'] . ' – ' . $preview['last']
);
echo '
';
self::render_progress( $key, $state );
if ( Importer::RUNNING === $state['status'] ) {
self::action_form( ImportActions::OP_CANCEL, $key, __( 'Stop import', '404ops-activity-log' ), 'button-secondary' );
} elseif ( $preview['count'] > 0 ) {
$label = Importer::IDLE === $state['status']
? __( 'Import history', '404ops-activity-log' )
: __( 'Import new events', '404ops-activity-log' );
self::action_form( ImportActions::OP_START, $key, $label, 'button-primary' );
}
echo '';
}
/**
* The progress bar and the run's counts.
*
* @param string $key Source key.
* @param array $state Importer::state().
*/
private static function render_progress( string $key, array $state ): void {
if ( Importer::IDLE === $state['status'] ) {
return;
}
$progress = ImportActions::progress( $state );
$labels = array(
Importer::RUNNING => __( 'Importing…', '404ops-activity-log' ),
Importer::DONE => __( 'Last import finished.', '404ops-activity-log' ),
Importer::CANCELLED => __( 'Last import was stopped.', '404ops-activity-log' ),
);
echo '';
echo '
' . esc_html( $labels[ $progress['status'] ] ?? '' ) . '
';
echo '
';
echo '
' . esc_html(
sprintf(
/* translators: 1: rows processed so far, 2: rows in all. */
__( '%1$s of %2$s processed', '404ops-activity-log' ),
number_format_i18n( $progress['processed'] ),
number_format_i18n( $progress['total'] )
)
) . '
';
echo '
';
echo '- ' . esc_html(
sprintf(
/* translators: 1: events imported, 2: of which imported as generic events. */
__( 'Imported: %1$s (of which %2$s had no equivalent here and were kept as "Imported" events)', '404ops-activity-log' ),
number_format_i18n( $progress['imported'] ),
number_format_i18n( $progress['generic'] )
)
) . '
';
$reasons = array(
Importer::SKIP_RETENTION => __( 'Skipped, older than your retention period', '404ops-activity-log' ),
Importer::SKIP_EXCLUDED => __( 'Skipped, by an excluded user', '404ops-activity-log' ),
Importer::SKIP_DUPLICATE => __( 'Skipped, already imported', '404ops-activity-log' ),
Importer::SKIP_UNREADABLE => __( 'Skipped, no readable date', '404ops-activity-log' ),
Importer::SKIP_FAILED => __( 'Not imported, the database refused the write', '404ops-activity-log' ),
);
foreach ( $reasons as $reason => $label ) {
$count = (int) ( $progress['skipped'][ $reason ] ?? 0 );
if ( $count > 0 ) {
echo '- ' . esc_html( $label . ': ' . number_format_i18n( $count ) ) . '
';
}
}
echo '
';
}
/**
* @param string $label Header cell.
* @param string $value Value cell.
*/
private static function row( string $label, string $value ): void {
echo '| ' . esc_html( $label ) . ' | ' . esc_html( $value ) . ' |
';
}
/**
* @param string $op Operation.
* @param string $key Source key.
* @param string $label Button label.
* @param string $button Button class.
*/
private static function action_form( string $op, string $key, string $label, string $button ): void {
echo '';
}
}