first commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Activity Log detail modal template
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2024, Snap Creek LLC
|
||||
*/
|
||||
|
||||
use Duplicator\Controllers\ActivityLogPageController;
|
||||
use Duplicator\Models\ActivityLog\AbstractLogEvent;
|
||||
|
||||
defined("ABSPATH") or die("");
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var \Duplicator\Core\Views\TplMng $tplMng
|
||||
*/
|
||||
|
||||
$log = $tplMng->getDataValueObjRequired('log', AbstractLogEvent::class);
|
||||
?>
|
||||
<div class="dup-box dup-log-detail-modal margin-0">
|
||||
<div class="dup-box-title">
|
||||
<i class="fas fa-list-ul"></i> <?php echo esc_html($log->getTitle()); ?>
|
||||
</div>
|
||||
<div class="dup-box-panel">
|
||||
<div class="dup-log-detail-meta">
|
||||
<div class="dup-log-type-wrapper">
|
||||
<strong><?php esc_html_e('Event:', 'duplicator-pro'); ?></strong>
|
||||
<span class="dup-log-type">
|
||||
<?php echo esc_html($log->getObjectTypeLabel()); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="dup-log-severity-wrapper">
|
||||
<strong><?php esc_html_e('Type:', 'duplicator-pro'); ?></strong>
|
||||
<span class="dup-log-severity <?php echo esc_attr(ActivityLogPageController::getSeverityClass($log->getSeverity())); ?>">
|
||||
<?php echo esc_html($log->getSeverityLabel()); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="dup-log-date-wrapper">
|
||||
<strong><?php esc_html_e('Activity Date:', 'duplicator-pro'); ?></strong>
|
||||
<span class="dup-log-date">
|
||||
<?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($log->getCreatedAt()))); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="dup-log-detail-content">
|
||||
<?php $log->detailHtml(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Activity Log list template
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2024, Snap Creek LLC
|
||||
*/
|
||||
|
||||
use Duplicator\Ajax\ServicesActivityLog;
|
||||
use Duplicator\Models\ActivityLog\AbstractLogEvent;
|
||||
|
||||
defined("ABSPATH") or die("");
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var \Duplicator\Core\Views\TplMng $tplMng
|
||||
*/
|
||||
|
||||
$page = $tplMng->getDataValueIntRequired('page');
|
||||
$perPage = $tplMng->getDataValueInt('perPage', 50);
|
||||
$totalItems = $tplMng->getDataValueIntRequired('totalItems');
|
||||
$totalPages = $tplMng->getDataValueIntRequired('totalPages');
|
||||
$logTypes = $tplMng->getDataValueArrayRequired('logTypes');
|
||||
$severityLevels = $tplMng->getDataValueArrayRequired('severityLevels');
|
||||
$filters = $tplMng->getDataValueArrayRequired('filters');
|
||||
/** @var array<AbstractLogEvent> */
|
||||
$logs = $tplMng->getDataValueArrayRequired('logs');
|
||||
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<?php $tplMng->render('admin_pages/activity_log/parts/toolbar'); ?>
|
||||
|
||||
<table class="widefat dup-table-list striped dup-activity-log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="manage-column column-date"><?php esc_html_e('Date', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-severity"><?php esc_html_e('Type', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-type"><?php esc_html_e('Event', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-title"><?php esc_html_e('Title', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-description"><?php esc_html_e('Description', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-actions"><?php esc_html_e('Actions', 'duplicator-pro'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($logs)) : ?>
|
||||
<tr>
|
||||
<td colspan="6" class="no-items">
|
||||
<?php esc_html_e('No activity logs found.', 'duplicator-pro'); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($logs as $log) : ?>
|
||||
<?php
|
||||
$tplMng->render('admin_pages/activity_log/parts/table_row', ['log' => $log]);
|
||||
?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th scope="col" class="manage-column column-date"><?php esc_html_e('Date', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-severity"><?php esc_html_e('Type', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-type"><?php esc_html_e('Event', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-title"><?php esc_html_e('Title', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-description"><?php esc_html_e('Description', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-actions"><?php esc_html_e('Actions', 'duplicator-pro'); ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
DupliJs.ActivityLog = {
|
||||
modalBox: null,
|
||||
init: function() {
|
||||
this.modalBox = new DuplicatorModalBox();
|
||||
this.initEvents();
|
||||
},
|
||||
initEvents: function() {
|
||||
$(document).on('click', '.dup-log-view-btn', function() {
|
||||
const logId = $(this).data('log-id');
|
||||
DupliJs.ActivityLog.openDetail(logId);
|
||||
});
|
||||
},
|
||||
openDetail: function(logId) {
|
||||
if (this.modalBox) {
|
||||
this.modalBox.close();
|
||||
}
|
||||
|
||||
DupliJs.Util.ajaxWrapper({
|
||||
'action': '<?php echo esc_js(ServicesActivityLog::NONCE_GET_DETAIL); ?>',
|
||||
'nonce': '<?php echo esc_js(wp_create_nonce(ServicesActivityLog::NONCE_GET_DETAIL)); ?>',
|
||||
'log_id': logId
|
||||
},
|
||||
function(result, data, funcData) {
|
||||
if (funcData.success) {
|
||||
DupliJs.ActivityLog.modalBox.setOptions({
|
||||
htmlContent: funcData.html,
|
||||
closeInContent: true,
|
||||
closeColor: '#000'
|
||||
});
|
||||
DupliJs.ActivityLog.modalBox.open();
|
||||
} else {
|
||||
DupliJs.addAdminMessage(funcData.message, 'error');
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
DupliJs.ActivityLog.init();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Template for displaying backup log context in activity log error events
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2025, Snap Creek LLC
|
||||
*/
|
||||
|
||||
defined("ABSPATH") or die("");
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var \Duplicator\Core\Views\TplMng $tplMng
|
||||
* @var string[] $logLines Array of log lines to display
|
||||
* @var string $logUrl URL to the full log file
|
||||
*/
|
||||
|
||||
$logLines = $tplMng->getDataValueArray('logLines', []);
|
||||
$logUrl = $tplMng->getDataValueString('logUrl', '');
|
||||
?>
|
||||
<div class="dup-error-logs-section">
|
||||
<div class="dup-error-logs-content">
|
||||
<div class="dup-log-content">
|
||||
<pre><?php echo esc_html(implode("\n", $logLines)); ?></pre>
|
||||
</div>
|
||||
|
||||
<div class="dup-log-meta">
|
||||
<?php printf(esc_html__('Showing %d lines from when error occurred', 'duplicator-pro'), count($logLines)); ?>
|
||||
<?php if (!empty($logUrl)) : ?>
|
||||
| <a href="<?php echo esc_url($logUrl); ?>" target="_blank"><?php esc_html_e('View complete log file', 'duplicator-pro'); ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Activity Log list template
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2024, Snap Creek LLC
|
||||
*/
|
||||
|
||||
use Duplicator\Controllers\ActivityLogPageController;
|
||||
use Duplicator\Models\ActivityLog\AbstractLogEvent;
|
||||
use Duplicator\Models\ActivityLog\LogUtils;
|
||||
|
||||
defined("ABSPATH") or die("");
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var \Duplicator\Core\Views\TplMng $tplMng
|
||||
* @var array<AbstractLogEvent> $logs Array of log events to display
|
||||
*/
|
||||
|
||||
$severityLevels = LogUtils::getSeverityLabels();
|
||||
/** @var array<AbstractLogEvent> */
|
||||
$logs = $tplMng->getDataValueArrayRequired('logs');
|
||||
?>
|
||||
<table class="widefat dup-table-list striped dup-activity-log-table small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="manage-column column-date"><?php esc_html_e('Date', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-severity"><?php esc_html_e('Type', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-title"><?php esc_html_e('Title', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-description"><?php esc_html_e('Description', 'duplicator-pro'); ?></th>
|
||||
<th scope="col" class="manage-column column-duration"><?php esc_html_e('Duration', 'duplicator-pro'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($logs)) : ?>
|
||||
<tr>
|
||||
<td colspan="5" class="no-items">
|
||||
<?php esc_html_e('No activity logs found.', 'duplicator-pro'); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
$lastLogId = $logs[count($logs) - 1]->getId();
|
||||
foreach ($logs as $currentLog) :
|
||||
// Get execution time directly from current log using its atomic timing data
|
||||
if (method_exists($currentLog, 'getExecutionTimeForPhase')) {
|
||||
$durationFormatted = $currentLog->getExecutionTimeForPhase($currentLog->getSubType());
|
||||
} else {
|
||||
// Fallback for log types that don't support timing
|
||||
$durationFormatted = __('N/A', 'duplicator-pro');
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td class="column-date">
|
||||
<?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($currentLog->getCreatedAt()))); ?>
|
||||
</td>
|
||||
<td class="column-severity">
|
||||
<span class="dup-log-severity <?php echo esc_attr(ActivityLogPageController::getSeverityClass($currentLog->getSeverity())); ?>">
|
||||
<?php echo esc_html($severityLevels[$currentLog->getSeverity()] ?? __('Unknown', 'duplicator-pro')); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="column-title">
|
||||
<?php echo esc_html($currentLog->getTitle()); ?>
|
||||
</td>
|
||||
<td class="column-description">
|
||||
<?php echo wp_kses_post($currentLog->getShortDescription()); ?>
|
||||
</td>
|
||||
<td class="column-duration">
|
||||
<?php if ($currentLog->getId() === $lastLogId) : ?>
|
||||
<?php echo esc_html(sprintf(__('Total: %s', 'duplicator-pro'), $durationFormatted)); ?>
|
||||
<?php else : ?>
|
||||
<?php echo esc_html($durationFormatted); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Activity Log table row template
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2024, Snap Creek LLC
|
||||
*/
|
||||
|
||||
use Duplicator\Controllers\ActivityLogPageController;
|
||||
use Duplicator\Models\ActivityLog\AbstractLogEvent;
|
||||
|
||||
defined("ABSPATH") || exit;
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var Duplicator\Core\Views\TplMng $tplMng
|
||||
*/
|
||||
|
||||
$log = $tplMng->getDataValueObjRequired('log', AbstractLogEvent::class);
|
||||
$severityLevels = $tplMng->getDataValueArrayRequired('severityLevels');
|
||||
$isMainEvent = ($log->getParentId() <= 0);
|
||||
$rowClasses = [];
|
||||
|
||||
if ($isMainEvent) {
|
||||
$rowClasses[] = 'main-event';
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<tr class="<?php echo esc_attr(implode(' ', $rowClasses)); ?>">
|
||||
<td class="column-date">
|
||||
<?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($log->getCreatedAt()))); ?>
|
||||
</td>
|
||||
<td class="column-severity">
|
||||
<span class="dup-log-severity <?php echo esc_attr(ActivityLogPageController::getSeverityClass($log->getSeverity())); ?>">
|
||||
<?php echo esc_html($severityLevels[$log->getSeverity()] ?? __('Unknown', 'duplicator-pro')); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="column-type">
|
||||
<?php echo esc_html($log->getObjectTypeLabel()); ?>
|
||||
</td>
|
||||
<td class="column-title">
|
||||
<span class="link-style dup-log-view-btn" data-log-id="<?php echo (int) $log->getId(); ?>">
|
||||
<?php echo esc_html($log->getTitle()); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="column-description">
|
||||
<?php echo wp_kses_post($log->getShortDescription()); ?>
|
||||
</td>
|
||||
<td class="column-actions">
|
||||
<button type="button" class="button small secondary hollow margin-bottom-0 dup-log-view-btn" data-log-id="<?php echo (int) $log->getId(); ?>">
|
||||
<?php esc_html_e('Details', 'duplicator-pro'); ?>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Activity Log toolbar template (filters and bulk actions)
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2024, Snap Creek LLC
|
||||
*/
|
||||
|
||||
defined("ABSPATH") || exit;
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var Duplicator\Core\Views\TplMng $tplMng
|
||||
*/
|
||||
|
||||
$page = $tplMng->getDataValueIntRequired('page');
|
||||
$totalItems = $tplMng->getDataValueIntRequired('totalItems');
|
||||
$totalPages = $tplMng->getDataValueIntRequired('totalPages');
|
||||
$filters = $tplMng->getDataValueArrayRequired('filters');
|
||||
$severityLevels = $tplMng->getDataValueArrayRequired('severityLevels');
|
||||
|
||||
$currentUrl = self_admin_url('admin.php?page=' . $_REQUEST['page']);
|
||||
$showAllTitle = __(
|
||||
'When enabled, shows a detailed view with all sub-events expanded. When disabled, shows a compact view with only main events.',
|
||||
'duplicator-pro'
|
||||
);
|
||||
?>
|
||||
<div class="tablenav top">
|
||||
<form method="get" class="dup-activity-log-filters float-left" action="<?php echo esc_url($currentUrl); ?>">
|
||||
<div class="dup-toolbar">
|
||||
<input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>">
|
||||
<input type="checkbox"
|
||||
id="filter_show_all"
|
||||
name="filter_show_all"
|
||||
value="1"
|
||||
title="<?php echo esc_attr($showAllTitle); ?>"
|
||||
<?php checked(!empty($filters['show_all'])); ?>>
|
||||
<label for="filter_show_all">
|
||||
<?php esc_html_e('Detailed list', 'duplicator-pro'); ?>
|
||||
</label>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<input type="date"
|
||||
id="filter_date_from"
|
||||
name="filter_date_from"
|
||||
title="<?php esc_attr_e('Filter by date from', 'duplicator-pro'); ?>"
|
||||
value="<?php echo esc_attr($filters['date_from']); ?>">
|
||||
|
||||
<span> - </span>
|
||||
|
||||
<input type="date"
|
||||
id="filter_date_to"
|
||||
name="filter_date_to"
|
||||
title="<?php esc_attr_e('Filter by date to', 'duplicator-pro'); ?>"
|
||||
value="<?php echo esc_attr($filters['date_to']); ?>">
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<select id="filter_severity"
|
||||
name="filter_severity"
|
||||
title="<?php esc_attr_e('Filter by severity level', 'duplicator-pro'); ?>">
|
||||
<option value="-1" <?php selected($filters['severity'], -1); ?>>
|
||||
<?php esc_html_e('All Severities', 'duplicator-pro'); ?>
|
||||
</option>
|
||||
<?php foreach ($severityLevels as $severityValue => $severityLabel) : ?>
|
||||
<option value="<?php echo esc_attr($severityValue); ?>" <?php selected($filters['severity'], $severityValue); ?>>
|
||||
<?php echo esc_html($severityLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<input type="submit"
|
||||
name="filter_action"
|
||||
class="button primary small"
|
||||
value="<?php esc_attr_e('Filter', 'duplicator-pro'); ?>">
|
||||
|
||||
<a href="<?php echo esc_url($currentUrl); ?>"
|
||||
class="button secondary small hollow">
|
||||
<?php esc_html_e('Reset', 'duplicator-pro'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($totalPages > 1) : ?>
|
||||
<div class="tablenav-pages">
|
||||
<span class="displaying-num">
|
||||
<?php echo esc_html(sprintf(__('%d items', 'duplicator-pro'), $totalItems)); ?>
|
||||
</span>
|
||||
|
||||
<span class="pagination-links">
|
||||
<?php if ($page > 1) : ?>
|
||||
<a class="first-page button" href="<?php echo esc_url(add_query_arg('paged', 1)); ?>">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
<a class="prev-page button" href="<?php echo esc_url(add_query_arg('paged', $page - 1)); ?>">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<span class="paging-input">
|
||||
<?php echo esc_html(sprintf(__('%1$d of %2$d', 'duplicator-pro'), $page, $totalPages)); ?>
|
||||
</span>
|
||||
|
||||
<?php if ($page < $totalPages) : ?>
|
||||
<a class="next-page button" href="<?php echo esc_url(add_query_arg('paged', $page + 1)); ?>">
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
<a class="last-page button" href="<?php echo esc_url(add_query_arg('paged', $totalPages)); ?>">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<br class="clear">
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*/
|
||||
|
||||
use Duplicator\Views\UserUIOptions;
|
||||
|
||||
defined("ABSPATH") or die("");
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var Duplicator\Core\Views\TplMng $tplMng
|
||||
* @var array<string, mixed> $tplData
|
||||
*/
|
||||
|
||||
$uiOpts = UserUIOptions::getInstance();
|
||||
|
||||
$perPage = $uiOpts->get(UserUIOptions::VAL_ACTIVITY_LOG_PER_PAGE);
|
||||
?>
|
||||
<fieldset class="screen-options margin-b-20px" >
|
||||
<legend>
|
||||
<?php esc_html_e('Pagination', 'duplicator-pro'); ?>
|
||||
</legend>
|
||||
<label for="activity_log_per_page" class="inline-display" >
|
||||
<?php esc_html_e('Activity Logs Per Page', 'duplicator-pro'); ?>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
max="999"
|
||||
class="screen-per-page inline-display margin-0 width-small"
|
||||
name="activity_log_per_page"
|
||||
id="activity_log_per_page"
|
||||
maxlength="3"
|
||||
value="<?php echo esc_html($perPage); ?>"
|
||||
>
|
||||
</fieldset>
|
||||
<input type="hidden" name="wp_screen_options[option]" value="activity_log_screen_options">
|
||||
<input type="hidden" name="wp_screen_options[value]" value="val">
|
||||
<input
|
||||
type="submit"
|
||||
name="screen-options-apply"
|
||||
id="screen-options-apply"
|
||||
class="button secondary hollow small margin-0"
|
||||
value="Apply"
|
||||
>
|
||||
Reference in New Issue
Block a user