mirror of
https://github.com/WenPai-org/wp-archiver.git
synced 2025-08-17 19:31:09 +08:00
2.0 稳定版
This commit is contained in:
parent
00a1ba5f27
commit
900b43143f
9 changed files with 4665 additions and 954 deletions
476
assets/css/archiver.css
Normal file
476
assets/css/archiver.css
Normal file
|
@ -0,0 +1,476 @@
|
|||
/**
|
||||
* WP Archiver 样式表
|
||||
*/
|
||||
|
||||
/* CSS 变量定义 */
|
||||
:root {
|
||||
--archiver-primary: #2271b1;
|
||||
--archiver-primary-hover: #135e96;
|
||||
--archiver-success: #00a32a;
|
||||
--archiver-warning: #dba617;
|
||||
--archiver-error: #d63638;
|
||||
--archiver-info: #72aee6;
|
||||
--archiver-border: #c3c4c7;
|
||||
--archiver-bg: #f0f0f1;
|
||||
--archiver-bg-white: #ffffff;
|
||||
--archiver-text: #3c434a;
|
||||
--archiver-text-light: #646970;
|
||||
--archiver-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
--archiver-radius: 4px;
|
||||
--archiver-transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 管理页面基础样式 */
|
||||
.archiver-admin-wrap {
|
||||
margin: 20px 20px 20px 2px;
|
||||
}
|
||||
|
||||
.archiver-admin-wrap .card {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
max-width: unset;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 标签页样式 */
|
||||
.archiver-settings-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-tab {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.settings-tab.active {
|
||||
border-bottom: 2px solid #007cba;
|
||||
font-weight: 600;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.settings-tab:hover:not(.active) {
|
||||
background: #f0f0f1;
|
||||
border-bottom-color: #dcdcde;
|
||||
}
|
||||
|
||||
/* 开关样式 */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
|
||||
/* 通知样式 */
|
||||
.notice {
|
||||
padding: 8px 12px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.notice-success {
|
||||
background-color: #dff0d8;
|
||||
border-left: 4px solid #46b450;
|
||||
}
|
||||
|
||||
.notice-error {
|
||||
background-color: #f2dede;
|
||||
border-left: 4px solid #dc3232;
|
||||
}
|
||||
|
||||
/* 服务状态样式 */
|
||||
.service-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.service-status.online .status-dot {
|
||||
background: #46b450;
|
||||
}
|
||||
|
||||
.service-status.offline .status-dot {
|
||||
background: #dc3232;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
/* 工具网格 */
|
||||
.archiver-tools-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.tool-section {
|
||||
padding: 15px;
|
||||
background: var(--archiver-bg);
|
||||
border-radius: var(--archiver-radius);
|
||||
}
|
||||
|
||||
.tool-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 队列样式 */
|
||||
.queue-status {
|
||||
background: #f0f0f1;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.queue-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.queue-list ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.queue-list li {
|
||||
margin: 5px 0;
|
||||
word-break: break-all;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.wp-list-table {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.wp-list-table th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Meta Box 样式 */
|
||||
.archiver-metabox {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.archiver-service-tabs {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid var(--archiver-border);
|
||||
}
|
||||
|
||||
.archiver-service-tab {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--archiver-text);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: var(--archiver-transition);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.archiver-service-tab:hover {
|
||||
color: var(--archiver-primary);
|
||||
}
|
||||
|
||||
.archiver-service-tab.active {
|
||||
color: var(--archiver-primary);
|
||||
border-bottom-color: var(--archiver-primary);
|
||||
}
|
||||
|
||||
#archiver-snapshots {
|
||||
min-height: 80px;
|
||||
margin-bottom: 15px;
|
||||
background: var(--archiver-bg);
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.archiver-loading {
|
||||
text-align: center;
|
||||
color: var(--archiver-text-light);
|
||||
}
|
||||
|
||||
.archiver-loading .spinner {
|
||||
float: none;
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.archiver-snapshot-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.archiver-snapshot-list li {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--archiver-border);
|
||||
transition: var(--archiver-transition);
|
||||
}
|
||||
|
||||
.archiver-snapshot-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.archiver-snapshot-list li:hover {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.archiver-snapshot-list a {
|
||||
text-decoration: none;
|
||||
color: var(--archiver-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.archiver-snapshot-list a:hover {
|
||||
color: var(--archiver-primary-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.archiver-no-snapshots {
|
||||
color: var(--archiver-text-light);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.archiver-view-all {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.archiver-view-all a {
|
||||
color: var(--archiver-primary);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.archiver-view-all a:hover {
|
||||
color: var(--archiver-primary-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.archiver-view-all .dashicons {
|
||||
font-size: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.archiver-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid var(--archiver-border);
|
||||
}
|
||||
|
||||
#archiver-immediate-snapshot {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
#archiver-immediate-snapshot .dashicons {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#archiver-immediate-snapshot.updating-message:before {
|
||||
content: "\f463";
|
||||
display: inline-block;
|
||||
font-family: dashicons;
|
||||
animation: spin 1s infinite linear;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#archiver-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 13px;
|
||||
color: var(--archiver-text-light);
|
||||
}
|
||||
|
||||
#archiver-status.success {
|
||||
color: var(--archiver-success);
|
||||
}
|
||||
|
||||
#archiver-status.error {
|
||||
color: var(--archiver-error);
|
||||
}
|
||||
|
||||
#archiver-status .dashicons {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Admin Bar 样式 */
|
||||
#wp-admin-bar-archiver .ab-item {
|
||||
transition: var(--archiver-transition);
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver.archiver-success .ab-item {
|
||||
color: var(--archiver-success) !important;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver.archiver-failure .ab-item {
|
||||
color: var(--archiver-error) !important;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver .ab-icon {
|
||||
margin-right: 6px !important;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver .ab-icon.dashicons-backup::before {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver-trigger .ab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver-trigger .ab-icon {
|
||||
margin-left: 6px !important;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver-trigger .ab-icon::before {
|
||||
content: "\f463";
|
||||
font-family: dashicons;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver.archiver-active .ab-icon,
|
||||
#wp-admin-bar-archiver-trigger.archiver-active .ab-icon {
|
||||
opacity: 1;
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-table th {
|
||||
font-weight: 600;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.button {
|
||||
border-radius: 3px;
|
||||
font-weight: 400;
|
||||
transition: var(--archiver-transition);
|
||||
}
|
||||
|
||||
.button-small {
|
||||
font-size: 11px;
|
||||
padding: 0 8px;
|
||||
line-height: 22px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* 描述文字 */
|
||||
.description {
|
||||
font-size: 13px;
|
||||
color: var(--archiver-text-light);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 782px) {
|
||||
.archiver-tools-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.archiver-service-tabs {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.archiver-service-tab {
|
||||
flex: 1 1 auto;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 焦点样式 */
|
||||
.archiver-service-tab:focus,
|
||||
.settings-tab:focus,
|
||||
.button:focus {
|
||||
outline: 2px solid var(--archiver-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
605
assets/js/archiver.js
Normal file
605
assets/js/archiver.js
Normal file
|
@ -0,0 +1,605 @@
|
|||
/**
|
||||
* WP Archiver 现代化前端脚本
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
const Archiver = {
|
||||
|
||||
config: {
|
||||
checkInterval: 5000,
|
||||
maxChecks: 12,
|
||||
fadeInDuration: 300,
|
||||
cachePrefix: 'archiver_cache_',
|
||||
debounceDelay: 300
|
||||
},
|
||||
|
||||
currentService: null,
|
||||
serviceCheckTimers: {},
|
||||
|
||||
init: function() {
|
||||
$(document).ready(() => {
|
||||
this.initMetabox();
|
||||
this.initAdminBar();
|
||||
this.initAdminPage();
|
||||
this.initLazyLoading();
|
||||
this.initServiceChecks();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化元框
|
||||
*/
|
||||
initMetabox: function() {
|
||||
const $container = $('#archiver-snapshots');
|
||||
if (!$container.length) return;
|
||||
|
||||
const url = $container.data('url') || $('#archiver-url').val();
|
||||
const service = $container.data('service') || $('#archiver_primary_service').val();
|
||||
|
||||
if (!url) return;
|
||||
|
||||
this.currentService = service;
|
||||
|
||||
// 服务标签切换
|
||||
$('.archiver-service-tab').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const $tab = $(e.currentTarget);
|
||||
const selectedService = $tab.data('service');
|
||||
|
||||
$('.archiver-service-tab').removeClass('active');
|
||||
$tab.addClass('active');
|
||||
|
||||
this.currentService = selectedService;
|
||||
this.loadSnapshots(url, $container, selectedService);
|
||||
});
|
||||
|
||||
// 延迟加载快照
|
||||
this.loadSnapshots(url, $container, service);
|
||||
|
||||
// 立即存档按钮
|
||||
$('#archiver-immediate-snapshot').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.triggerSnapshot(url, this.currentService);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化管理栏
|
||||
*/
|
||||
initAdminBar: function() {
|
||||
const $trigger = $('#wp-admin-bar-archiver-trigger a');
|
||||
if (!$trigger.length) return;
|
||||
|
||||
// 延迟加载快照计数
|
||||
const $countItem = $('#wp-admin-bar-archiver-snapshots');
|
||||
if ($countItem.length && archiver.url) {
|
||||
this.updateAdminBarCount(archiver.url);
|
||||
}
|
||||
|
||||
// 触发快照
|
||||
$trigger.on('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.triggerAdminBarSnapshot();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化管理页面
|
||||
*/
|
||||
initAdminPage: function() {
|
||||
// 标签切换
|
||||
$('.archiver-tab').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const $tab = $(this);
|
||||
const tabId = $tab.data('tab');
|
||||
|
||||
$('.archiver-tab').removeClass('active');
|
||||
$tab.addClass('active');
|
||||
|
||||
$('.archiver-tab-content').removeClass('active');
|
||||
$('#' + tabId).addClass('active');
|
||||
|
||||
// 保存用户偏好
|
||||
if (typeof(Storage) !== "undefined") {
|
||||
localStorage.setItem('archiver_active_tab', tabId);
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复上次的标签
|
||||
const savedTab = localStorage.getItem('archiver_active_tab');
|
||||
if (savedTab && $(`.archiver-tab[data-tab="${savedTab}"]`).length) {
|
||||
$(`.archiver-tab[data-tab="${savedTab}"]`).trigger('click');
|
||||
}
|
||||
|
||||
// 标签链接
|
||||
$('.archiver-tab-link').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const tab = $(this).attr('href').replace('#', '');
|
||||
$(`.archiver-tab[data-tab="${tab}"]`).trigger('click');
|
||||
});
|
||||
|
||||
// 测试服务连接
|
||||
$('.test-service').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const $button = $(e.currentTarget);
|
||||
const service = $button.data('service');
|
||||
this.testServiceConnection(service, $button);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化服务检查
|
||||
*/
|
||||
initServiceChecks: function() {
|
||||
if ($('.archiver-service-card').length === 0) return;
|
||||
|
||||
// 检查所有服务状态
|
||||
$('.archiver-service-card').each((index, element) => {
|
||||
const $card = $(element);
|
||||
const service = $card.find('.test-service').data('service');
|
||||
if (service) {
|
||||
this.checkServiceStatus(service);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查服务状态
|
||||
*/
|
||||
checkServiceStatus: function(service) {
|
||||
const $status = $(`#service-status-${service}`);
|
||||
if (!$status.length) return;
|
||||
|
||||
// 清除现有定时器
|
||||
if (this.serviceCheckTimers[service]) {
|
||||
clearTimeout(this.serviceCheckTimers[service]);
|
||||
}
|
||||
|
||||
$status.removeClass('online offline').find('.status-text').text(archiver.i18n.checking);
|
||||
|
||||
// 模拟检查延迟
|
||||
this.serviceCheckTimers[service] = setTimeout(() => {
|
||||
// 根据服务配置判断状态
|
||||
const isEnabled = archiver.enabled_services[service];
|
||||
const statusClass = isEnabled ? 'online' : 'offline';
|
||||
const statusText = isEnabled ? archiver.i18n.online : archiver.i18n.offline;
|
||||
|
||||
$status.addClass(statusClass).find('.status-text').text(statusText);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 测试服务连接
|
||||
*/
|
||||
testServiceConnection: function(service, $button) {
|
||||
const originalText = $button.text();
|
||||
$button.prop('disabled', true).text(archiver.i18n.checking);
|
||||
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_test_service',
|
||||
service: service,
|
||||
nonce: archiver.admin_nonce
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
this.showNotification(response.data.message, 'success');
|
||||
this.checkServiceStatus(service);
|
||||
} else {
|
||||
this.showNotification(response.data.message || archiver.i18n.test_failed, 'error');
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.showNotification(archiver.i18n.test_failed, 'error');
|
||||
},
|
||||
complete: () => {
|
||||
$button.prop('disabled', false).text(originalText);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 延迟加载
|
||||
*/
|
||||
initLazyLoading: function() {
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const $element = $(entry.target);
|
||||
const url = $element.data('url');
|
||||
const service = $element.data('service');
|
||||
if (url && !$element.data('loaded')) {
|
||||
this.loadSnapshots(url, $element, service);
|
||||
$element.data('loaded', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.archiver-lazy').each(function() {
|
||||
observer.observe(this);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载快照
|
||||
*/
|
||||
loadSnapshots: function(url, $container, service = null) {
|
||||
// 检查本地缓存
|
||||
const cacheKey = this.getCacheKey(url, service);
|
||||
const cached = this.getCache(cacheKey);
|
||||
if (cached) {
|
||||
this.displaySnapshots(cached, $container);
|
||||
// 后台检查更新
|
||||
this.checkForUpdates(url, $container, service);
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
this.showLoading($container);
|
||||
|
||||
// 从服务器获取
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_get_snapshots',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url,
|
||||
service: service
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
this.setCache(cacheKey, response.data);
|
||||
this.displaySnapshots(response.data, $container);
|
||||
} else {
|
||||
this.showError($container, response.data.message);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.showError($container, archiver.i18n.error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示快照
|
||||
*/
|
||||
displaySnapshots: function(data, $container) {
|
||||
if (!data.html || data.html.includes('archiver-no-snapshots')) {
|
||||
$container.html(data.html || '<p class="archiver-no-snapshots">' + archiver.i18n.no_snapshots + '</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
$container.hide().html(data.html).fadeIn(this.config.fadeInDuration);
|
||||
|
||||
// 添加淡入动画
|
||||
$container.find('li').each(function(index) {
|
||||
$(this).css({
|
||||
opacity: 0,
|
||||
transform: 'translateY(10px)'
|
||||
}).delay(index * 50).animate({
|
||||
opacity: 1
|
||||
}, {
|
||||
duration: 300,
|
||||
step: function(now, fx) {
|
||||
if (fx.prop === 'opacity' && now > 0.5) {
|
||||
$(this).css('transform', 'translateY(0)');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发快照
|
||||
*/
|
||||
triggerSnapshot: function(url, service = null) {
|
||||
const $button = $('#archiver-immediate-snapshot');
|
||||
const $status = $('#archiver-status');
|
||||
|
||||
$button.prop('disabled', true).addClass('updating-message');
|
||||
$status.show().removeClass('error success')
|
||||
.html('<span class="spinner is-active"></span> ' + archiver.i18n.triggering);
|
||||
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_immediate_snapshot',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url,
|
||||
service: service
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
$status.removeClass('error').addClass('success')
|
||||
.html('<span class="dashicons dashicons-yes"></span> ' + response.data.message);
|
||||
|
||||
// 清除缓存并开始轮询
|
||||
this.clearCache(this.getCacheKey(url, service));
|
||||
if (response.data.refresh) {
|
||||
this.startPolling(url, service);
|
||||
}
|
||||
|
||||
// 触发自定义事件
|
||||
$(document).trigger('archiver:snapshot:success', [url, service]);
|
||||
} else {
|
||||
$status.removeClass('success').addClass('error')
|
||||
.html('<span class="dashicons dashicons-warning"></span> ' + response.data.message);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
$status.removeClass('success').addClass('error')
|
||||
.html('<span class="dashicons dashicons-warning"></span> ' + archiver.i18n.error);
|
||||
},
|
||||
complete: () => {
|
||||
$button.prop('disabled', false).removeClass('updating-message');
|
||||
setTimeout(() => {
|
||||
$status.fadeOut();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 管理栏触发快照
|
||||
*/
|
||||
triggerAdminBarSnapshot: function() {
|
||||
const $item = $('#wp-admin-bar-archiver');
|
||||
$item.addClass('archiver-active');
|
||||
|
||||
$.ajax({
|
||||
url: archiver.rest_url + 'trigger-snapshot',
|
||||
method: 'POST',
|
||||
beforeSend: (xhr) => {
|
||||
xhr.setRequestHeader('X-WP-Nonce', archiver.nonce);
|
||||
},
|
||||
data: {
|
||||
url: archiver.url,
|
||||
service: archiver.primary_service
|
||||
},
|
||||
success: (response) => {
|
||||
$item.removeClass('archiver-active');
|
||||
if (response.success) {
|
||||
$item.addClass('archiver-success');
|
||||
this.clearCache(this.getCacheKey(archiver.url));
|
||||
this.updateAdminBarCount(archiver.url);
|
||||
this.showNotification(archiver.i18n.success, 'success');
|
||||
} else {
|
||||
$item.addClass('archiver-failure');
|
||||
this.showNotification(response.message || archiver.i18n.error, 'error');
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
$item.removeClass('archiver-active').addClass('archiver-failure');
|
||||
this.showNotification(archiver.i18n.error, 'error');
|
||||
},
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
$item.removeClass('archiver-success archiver-failure');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新管理栏计数
|
||||
*/
|
||||
updateAdminBarCount: function(url) {
|
||||
const $countItem = $('#wp-admin-bar-archiver-snapshots .ab-label');
|
||||
if (!$countItem.length) return;
|
||||
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_get_snapshots',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url,
|
||||
service: archiver.primary_service
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success && response.data.count !== undefined) {
|
||||
const count = response.data.count >= 10 ? '10+' : response.data.count;
|
||||
$countItem.text(archiver.i18n.view_all + ' (' + count + ')');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查更新
|
||||
*/
|
||||
checkForUpdates: function(url, $container, service = null) {
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_check_cache_update',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url,
|
||||
service: service
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success && response.data.has_update) {
|
||||
const cacheKey = this.getCacheKey(url, service);
|
||||
this.clearCache(cacheKey);
|
||||
this.displaySnapshots(response.data, $container);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始轮询
|
||||
*/
|
||||
startPolling: function(url, service = null) {
|
||||
let checkCount = 0;
|
||||
const $container = $('#archiver-snapshots');
|
||||
|
||||
const interval = setInterval(() => {
|
||||
checkCount++;
|
||||
|
||||
if (checkCount > this.config.maxChecks) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkForUpdates(url, $container, service);
|
||||
}, this.config.checkInterval);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示加载状态
|
||||
*/
|
||||
showLoading: function($container) {
|
||||
const loadingHtml = `
|
||||
<div class="archiver-loading">
|
||||
<span class="spinner is-active"></span>
|
||||
${archiver.i18n.loading}
|
||||
</div>
|
||||
`;
|
||||
$container.html(loadingHtml);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示错误
|
||||
*/
|
||||
showError: function($container, message) {
|
||||
$container.html('<p class="archiver-error">' + message + '</p>');
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示通知
|
||||
*/
|
||||
showNotification: function(message, type = 'info') {
|
||||
// 如果在管理页面,使用 WordPress 通知
|
||||
if ($('body').hasClass('wp-admin')) {
|
||||
const $notice = $('<div class="notice notice-' + type + ' is-dismissible"><p>' + message + '</p></div>');
|
||||
$('.wrap > h1').after($notice);
|
||||
|
||||
// 自动关闭
|
||||
setTimeout(() => {
|
||||
$notice.fadeOut(() => $notice.remove());
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 缓存管理
|
||||
*/
|
||||
getCacheKey: function(url, service = null) {
|
||||
const serviceKey = service || 'default';
|
||||
return this.config.cachePrefix + this.hashCode(url + '_' + serviceKey);
|
||||
},
|
||||
|
||||
getCache: function(key) {
|
||||
if (!this.isStorageAvailable()) return null;
|
||||
|
||||
try {
|
||||
const cached = sessionStorage.getItem(key);
|
||||
|
||||
if (cached) {
|
||||
const data = JSON.parse(cached);
|
||||
// 5分钟缓存
|
||||
if (Date.now() - data.timestamp < 300000) {
|
||||
return data.content;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Cache read error:', e);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
setCache: function(key, data) {
|
||||
if (!this.isStorageAvailable()) return;
|
||||
|
||||
try {
|
||||
const cacheData = {
|
||||
content: data,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
sessionStorage.setItem(key, JSON.stringify(cacheData));
|
||||
} catch (e) {
|
||||
console.warn('Cache write error:', e);
|
||||
}
|
||||
},
|
||||
|
||||
clearCache: function(key) {
|
||||
if (!this.isStorageAvailable()) return;
|
||||
|
||||
try {
|
||||
if (key) {
|
||||
sessionStorage.removeItem(key);
|
||||
} else {
|
||||
// 清除所有archiver缓存
|
||||
const keys = [];
|
||||
for (let i = 0; i < sessionStorage.length; i++) {
|
||||
const k = sessionStorage.key(i);
|
||||
if (k && k.startsWith(this.config.cachePrefix)) {
|
||||
keys.push(k);
|
||||
}
|
||||
}
|
||||
keys.forEach(k => sessionStorage.removeItem(k));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Cache clear error:', e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 工具函数
|
||||
*/
|
||||
isStorageAvailable: function() {
|
||||
try {
|
||||
const test = '__archiver_test__';
|
||||
sessionStorage.setItem(test, test);
|
||||
sessionStorage.removeItem(test);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
hashCode: function(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash).toString();
|
||||
},
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
*/
|
||||
debounce: function(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
Archiver.init();
|
||||
|
||||
// 暴露到全局
|
||||
window.WPArchiver = Archiver;
|
||||
|
||||
})(jQuery);
|
466
css/archiver.css
466
css/archiver.css
|
@ -1,183 +1,453 @@
|
|||
/**
|
||||
* WP Archiver 样式表
|
||||
*/
|
||||
|
||||
/* 管理栏样式 */
|
||||
#wp-admin-bar-archiver .ab-item {
|
||||
transition: all .3s ease;
|
||||
color: #72aee6
|
||||
transition: all .3s ease;
|
||||
color: #72aee6;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver.archiver-success .ab-item {
|
||||
color: #46b450!important
|
||||
color: #46b450 !important;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver.archiver-failure .ab-item {
|
||||
color: #dc3232!important
|
||||
color: #dc3232 !important;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver .ab-icon.dashicons.dashicons-backup {
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver-trigger .ab-icon {
|
||||
display: inline-block;
|
||||
float: right!important;
|
||||
margin: 0 0 0 6px;
|
||||
opacity: 0;
|
||||
transition: opacity .3s ease;
|
||||
vertical-align: middle
|
||||
display: inline-block;
|
||||
float: right !important;
|
||||
margin: 0 0 0 6px;
|
||||
opacity: 0;
|
||||
transition: opacity .3s ease;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver-trigger .ab-icon::before {
|
||||
content: "\f463";
|
||||
font-family: dashicons;
|
||||
font-size: 18px;
|
||||
speak: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
content: "\f463";
|
||||
font-family: dashicons;
|
||||
font-size: 18px;
|
||||
speak: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#wp-admin-bar-archiver-trigger.archiver-active .ab-icon {
|
||||
opacity: 1;
|
||||
animation: archiver-spin 1s infinite linear
|
||||
#wp-admin-bar-archiver-trigger.archiver-active .ab-icon,
|
||||
#wp-admin-bar-archiver.archiver-active .ab-icon {
|
||||
opacity: 1;
|
||||
animation: archiver-spin 1s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes archiver-spin {
|
||||
0% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
0% { transform: rotate(0); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
/* 管理页面样式 */
|
||||
.archiver-admin-wrap {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.archiver-version {
|
||||
font-size: 13px;
|
||||
padding-left: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.nav-tab-wrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
border: 0px solid #c3c4c7;
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
background: #fff;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-weight: 400;
|
||||
}
|
||||
.nav-tab.nav-tab-active {
|
||||
border-bottom: 2px solid #007cba;
|
||||
font-weight: 600;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
.nav-tab:hover:not(.active) {
|
||||
|
||||
.nav-tab:hover {
|
||||
background: #f0f0f1;
|
||||
border-bottom-color: #dcdcde;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
.nav-tab.nav-tab-active {
|
||||
border-bottom-color: #007cba;
|
||||
font-weight: 600;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.archiver-tab-content {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
max-width: unset;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
.postbox {
|
||||
border: 0 solid #c3c4c7;
|
||||
background: #ffffff00
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.archiver-stats-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 0 0 20px
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.archiver-stat-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.04)
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.04);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.archiver-stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 3px 5px rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.archiver-stat-card h3 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #646970
|
||||
margin: 0 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #646970;
|
||||
}
|
||||
|
||||
.archiver-stat-card .stat-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1d2327
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1d2327;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-subtitle {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 缓存分布 */
|
||||
.cache-distribution {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.cache-bar {
|
||||
display: flex;
|
||||
height: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.cache-segment {
|
||||
height: 100%;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cache-segment.hot { background: #e74c3c; }
|
||||
.cache-segment.warm { background: #f39c12; }
|
||||
.cache-segment.cold { background: #3498db; }
|
||||
.cache-segment.frozen { background: #95a5a6; }
|
||||
|
||||
.cache-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.legend-color.hot { background: #e74c3c; }
|
||||
.legend-color.warm { background: #f39c12; }
|
||||
.legend-color.cold { background: #3498db; }
|
||||
.legend-color.frozen { background: #95a5a6; }
|
||||
|
||||
/* 待处理列表 */
|
||||
.archiver-pending-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
background: #f9f9f9;
|
||||
margin: 0 0 20px
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.archiver-pending-list ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.archiver-pending-list li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
word-break: break-all
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
word-break: break-all;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.archiver-pending-list li:last-child {
|
||||
border-bottom: none
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media (max-width:782px) {
|
||||
.archiver-stats-container {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.archiver-stat-card {
|
||||
width: 100%
|
||||
}
|
||||
/* 元框样式 */
|
||||
#archiver_post,
|
||||
#archiver_terms {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#archiver_post,#archiver_terms {
|
||||
background: #fff;
|
||||
border-radius: 4px
|
||||
#archiver-snapshots {
|
||||
min-height: 60px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#archiver-snapshots ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#archiver-snapshots li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #eee
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#archiver-snapshots li:last-child {
|
||||
border-bottom: none
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#archiver-snapshots li:hover {
|
||||
background: #f5f5f5;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
#archiver-snapshots a {
|
||||
text-decoration: none;
|
||||
color: #2271b1
|
||||
text-decoration: none;
|
||||
color: #2271b1;
|
||||
}
|
||||
|
||||
#archiver-snapshots a:hover {
|
||||
text-decoration: underline
|
||||
text-decoration: underline;
|
||||
color: #135e96;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.archiver-loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.archiver-loading .spinner {
|
||||
float: none;
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
|
||||
/* 状态消息 */
|
||||
#archiver-status {
|
||||
vertical-align: middle;
|
||||
color: #646970
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
color: #646970;
|
||||
}
|
||||
|
||||
#archiver-status.success {
|
||||
color: #46b450;
|
||||
}
|
||||
|
||||
#archiver-status.error {
|
||||
color: #dc3232;
|
||||
}
|
||||
|
||||
#archiver-status .dashicons {
|
||||
vertical-align: middle;
|
||||
margin-right: 3px
|
||||
vertical-align: middle;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
#archiver-immediate-snapshot {
|
||||
margin-top: 10px
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#archiver-immediate-snapshot.updating-message:before {
|
||||
content: "\f463";
|
||||
display: inline-block;
|
||||
font-family: dashicons;
|
||||
animation: archiver-spin 1s infinite linear;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.archiver-view-all {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 工具区域 */
|
||||
.archiver-tools-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.archiver-tools-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.archiver-tools-section h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.archiver-tool-form {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* 缓存操作 */
|
||||
.archiver-cache-actions {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.archiver-cache-actions .button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* 关于页面 */
|
||||
.archiver-about-content {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.archiver-about-content ul {
|
||||
list-style: disc;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.archiver-about-content li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 782px) {
|
||||
.archiver-stats-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.archiver-stat-card {
|
||||
width: 100%;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.cache-legend {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nav-tab-wrapper {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.wp-admin.admin-color-modern .archiver-stat-card,
|
||||
.wp-admin.admin-color-modern .archiver-tab-content {
|
||||
background: #1e1e1e;
|
||||
border-color: #3c434a;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.wp-admin.admin-color-modern .archiver-stat-card h3 {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.wp-admin.admin-color-modern .stat-value {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.wp-admin.admin-color-modern .archiver-pending-list {
|
||||
background: #2c3338;
|
||||
border-color: #3c434a;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.archiver-snapshot-list li {
|
||||
animation: fadeInUp 0.3s ease forwards;
|
||||
}
|
||||
|
||||
/* 工具提示 */
|
||||
[title] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cache-segment:hover::after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 缓存统计区域 */
|
||||
.archiver-cache-stats {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.archiver-cache-stats h2 {
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
628
includes/class-archiver-cache.php
Normal file
628
includes/class-archiver-cache.php
Normal file
|
@ -0,0 +1,628 @@
|
|||
<?php
|
||||
/**
|
||||
* WP Archiver 缓存管理系统
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Archiver_Cache {
|
||||
|
||||
private $table_name;
|
||||
private $cache_durations = [
|
||||
'hot' => HOUR_IN_SECONDS,
|
||||
'warm' => DAY_IN_SECONDS,
|
||||
'cold' => WEEK_IN_SECONDS,
|
||||
'frozen' => MONTH_IN_SECONDS
|
||||
];
|
||||
|
||||
// 添加服务特定的缓存前缀
|
||||
private $cache_prefix = 'archiver_';
|
||||
|
||||
// 批处理优化
|
||||
private $batch_size = 3;
|
||||
private $max_retries = 3;
|
||||
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$this->table_name = $wpdb->prefix . 'archiver_cache';
|
||||
$this->batch_size = apply_filters('archiver_batch_size', 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取快照数据(支持多服务)
|
||||
*/
|
||||
public function get_snapshots($url, $service = null, $force_refresh = false) {
|
||||
if (!get_option('archiver_cache_enabled', true)) {
|
||||
return $this->fetch_from_archive_service($url, $service);
|
||||
}
|
||||
|
||||
// 如果没有指定服务,使用主服务
|
||||
if (!$service) {
|
||||
$service = get_option('archiver_primary_service', 'wenpai');
|
||||
}
|
||||
|
||||
$url_hash = md5($url . '_' . $service);
|
||||
|
||||
// 1. 检查内存缓存
|
||||
if (!$force_refresh) {
|
||||
$memory_key = $this->cache_prefix . 'snap_' . $url_hash;
|
||||
$cached_data = wp_cache_get($memory_key, 'archiver');
|
||||
|
||||
if ($cached_data !== false) {
|
||||
$this->update_access_stats($url_hash, $service);
|
||||
return $cached_data;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查数据库缓存
|
||||
if (!$force_refresh) {
|
||||
$db_cache = $this->get_from_database($url_hash, $service);
|
||||
if ($db_cache !== false) {
|
||||
wp_cache_set($memory_key, $db_cache, 'archiver', HOUR_IN_SECONDS);
|
||||
return $db_cache;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 添加到后台队列(带服务信息)
|
||||
$this->queue_for_update($url, $service, false);
|
||||
|
||||
// 4. 返回过期数据或空数组
|
||||
return $this->get_stale_data($url_hash, $service) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库获取缓存
|
||||
*/
|
||||
private function get_from_database($url_hash, $service = 'wenpai') {
|
||||
global $wpdb;
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM {$this->table_name}
|
||||
WHERE url_hash = %s AND service = %s AND expires_at > NOW() AND status = 'active'",
|
||||
$url_hash, $service
|
||||
));
|
||||
|
||||
if (!$row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->update_access_stats($url_hash, $service);
|
||||
$data = maybe_unserialize($row->snapshot_data);
|
||||
|
||||
// 根据访问频率提升缓存级别
|
||||
if ($row->api_calls_saved > 10 && $row->cache_type !== 'hot') {
|
||||
$this->promote_cache($url_hash, $service, 'hot');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期数据
|
||||
*/
|
||||
private function get_stale_data($url_hash, $service = 'wenpai') {
|
||||
global $wpdb;
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT snapshot_data FROM {$this->table_name}
|
||||
WHERE url_hash = %s AND service = %s
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1",
|
||||
$url_hash, $service
|
||||
));
|
||||
|
||||
return $row ? maybe_unserialize($row->snapshot_data) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存快照数据
|
||||
*/
|
||||
public function save_snapshots($url, $snapshots, $service = 'wenpai') {
|
||||
global $wpdb;
|
||||
|
||||
$url_hash = md5($url . '_' . $service);
|
||||
$cache_type = $this->determine_cache_type($url);
|
||||
$cache_duration = get_option('archiver_cache_duration', $this->cache_durations[$cache_type]);
|
||||
$expires_at = date('Y-m-d H:i:s', time() + $cache_duration);
|
||||
|
||||
$data = [
|
||||
'url' => $url,
|
||||
'url_hash' => $url_hash,
|
||||
'service' => $service,
|
||||
'snapshot_data' => maybe_serialize($snapshots),
|
||||
'snapshot_count' => is_array($snapshots) ? count($snapshots) : 0,
|
||||
'cache_type' => $cache_type,
|
||||
'expires_at' => $expires_at,
|
||||
'last_accessed' => current_time('mysql'),
|
||||
'created_at' => current_time('mysql'),
|
||||
'status' => 'active'
|
||||
];
|
||||
|
||||
$wpdb->replace($this->table_name, $data);
|
||||
|
||||
// 更新内存缓存
|
||||
$memory_key = $this->cache_prefix . 'snap_' . $url_hash;
|
||||
wp_cache_set($memory_key, $snapshots, 'archiver', HOUR_IN_SECONDS);
|
||||
|
||||
// 更新统计
|
||||
$this->increment_archived_count();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定缓存类型
|
||||
*/
|
||||
private function determine_cache_type($url) {
|
||||
// 首页:热数据
|
||||
if ($url === home_url() || $url === home_url('/')) {
|
||||
return 'hot';
|
||||
}
|
||||
|
||||
$post_id = url_to_postid($url);
|
||||
if ($post_id) {
|
||||
$post = get_post($post_id);
|
||||
if ($post) {
|
||||
$days_old = (time() - strtotime($post->post_date)) / DAY_IN_SECONDS;
|
||||
|
||||
if ($days_old < 7) return 'hot';
|
||||
elseif ($days_old < 30) return 'warm';
|
||||
elseif ($days_old < 365) return 'cold';
|
||||
}
|
||||
}
|
||||
|
||||
return 'frozen';
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到更新队列(支持多服务)
|
||||
*/
|
||||
public function queue_for_update($url, $service = null, $priority = false) {
|
||||
$queue = get_option('archiver_background_queue', []);
|
||||
|
||||
// 如果没有指定服务,使用所有启用的服务
|
||||
if (!$service) {
|
||||
$enabled_services = get_option('archiver_services', array('wenpai' => true));
|
||||
foreach ($enabled_services as $service_id => $enabled) {
|
||||
if ($enabled) {
|
||||
$this->add_to_queue($queue, $url, $service_id, $priority);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->add_to_queue($queue, $url, $service, $priority);
|
||||
}
|
||||
|
||||
// 限制队列大小
|
||||
$max_queue_size = apply_filters('archiver_queue_limit', 100);
|
||||
$queue = array_slice($queue, 0, $max_queue_size);
|
||||
|
||||
update_option('archiver_background_queue', $queue);
|
||||
|
||||
// 触发后台处理
|
||||
if (!wp_next_scheduled('archiver_process_background_queue')) {
|
||||
wp_schedule_single_event(time() + 10, 'archiver_process_background_queue');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个项目到队列
|
||||
*/
|
||||
private function add_to_queue(&$queue, $url, $service, $priority) {
|
||||
$item = array('url' => $url, 'service' => $service, 'retries' => 0);
|
||||
|
||||
// 检查是否已存在
|
||||
foreach ($queue as $existing) {
|
||||
if (is_array($existing) && $existing['url'] === $url && $existing['service'] === $service) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($priority) {
|
||||
array_unshift($queue, $item);
|
||||
} else {
|
||||
$queue[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理后台队列
|
||||
*/
|
||||
public function process_background_queue() {
|
||||
$queue = get_option('archiver_background_queue', []);
|
||||
|
||||
if (empty($queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 每次处理批量URL
|
||||
$batch = array_splice($queue, 0, $this->batch_size);
|
||||
$failed_items = array();
|
||||
|
||||
foreach ($batch as $item) {
|
||||
if (!is_array($item)) {
|
||||
// 兼容旧格式
|
||||
$item = array('url' => $item, 'service' => 'wenpai', 'retries' => 0);
|
||||
}
|
||||
|
||||
$success = $this->fetch_and_cache_snapshots($item['url'], $item['service']);
|
||||
|
||||
if (!$success) {
|
||||
$item['retries'] = (isset($item['retries']) ? $item['retries'] : 0) + 1;
|
||||
if ($item['retries'] < $this->max_retries) {
|
||||
$failed_items[] = $item;
|
||||
} else {
|
||||
$this->increment_failed_count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将失败的项目重新加入队列末尾
|
||||
$queue = array_merge($queue, $failed_items);
|
||||
|
||||
// 更新队列
|
||||
update_option('archiver_background_queue', $queue);
|
||||
|
||||
// 如果还有待处理项,继续安排
|
||||
if (!empty($queue)) {
|
||||
wp_schedule_single_event(time() + 30, 'archiver_process_background_queue');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存档服务获取并缓存数据
|
||||
*/
|
||||
public function fetch_and_cache_snapshots($url, $service = 'wenpai') {
|
||||
$snapshots = $this->fetch_from_archive_service($url, $service);
|
||||
|
||||
if ($snapshots !== false && !empty($snapshots)) {
|
||||
$this->save_snapshots($url, $snapshots, $service);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存档服务获取数据
|
||||
*/
|
||||
private function fetch_from_archive_service($url, $service = null) {
|
||||
if (!$service) {
|
||||
$service = get_option('archiver_primary_service', 'wenpai');
|
||||
}
|
||||
|
||||
$services = ARCHIVER_SERVICES;
|
||||
if (!isset($services[$service])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($service) {
|
||||
case 'wayback':
|
||||
return $this->fetch_from_wayback($url);
|
||||
|
||||
case 'wenpai':
|
||||
return $this->fetch_from_wenpai($url);
|
||||
|
||||
case 'archive_today':
|
||||
return $this->fetch_from_archive_today($url);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Wayback Machine 获取数据
|
||||
*/
|
||||
private function fetch_from_wayback($url) {
|
||||
$api_url = add_query_arg([
|
||||
'url' => $url,
|
||||
'output' => 'json',
|
||||
'limit' => 10
|
||||
], ARCHIVER_SERVICES['wayback']['fetch_url']);
|
||||
|
||||
$response = wp_remote_get($api_url, [
|
||||
'timeout' => 30,
|
||||
'sslverify' => true,
|
||||
'headers' => [
|
||||
'User-Agent' => 'WP-Archiver/' . ARCHIVER_VERSION
|
||||
]
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wp_remote_retrieve_response_code($response) !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->process_wayback_response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 WenPai Archive 获取数据
|
||||
*/
|
||||
private function fetch_from_wenpai($url) {
|
||||
$api_url = add_query_arg([
|
||||
'url' => $url,
|
||||
'output' => 'json',
|
||||
], ARCHIVER_SERVICES['wenpai']['fetch_url']);
|
||||
|
||||
$response = wp_remote_get($api_url, [
|
||||
'timeout' => 30,
|
||||
'sslverify' => false, // WenPai 可能使用自签名证书
|
||||
'headers' => [
|
||||
'User-Agent' => 'WP-Archiver/' . ARCHIVER_VERSION
|
||||
]
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wp_remote_retrieve_response_code($response) !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->process_wayback_response($data); // 使用相同的处理方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Wayback Machine 响应
|
||||
*/
|
||||
private function process_wayback_response($data) {
|
||||
if (count($data) < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$headers = array_shift($data);
|
||||
$snapshots = [];
|
||||
|
||||
// 只取最新的10条
|
||||
$data = array_slice($data, -10);
|
||||
|
||||
foreach ($data as $row) {
|
||||
$snapshot = [];
|
||||
foreach ($row as $i => $value) {
|
||||
if (isset($headers[$i])) {
|
||||
$snapshot[$headers[$i]] = $value;
|
||||
}
|
||||
}
|
||||
$snapshots[] = $snapshot;
|
||||
}
|
||||
|
||||
return array_reverse($snapshots);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Archive.today 获取数据
|
||||
*/
|
||||
private function fetch_from_archive_today($url) {
|
||||
// Archive.today 没有官方 API,返回空数组
|
||||
// 但仍可以触发保存
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新访问统计
|
||||
*/
|
||||
private function update_access_stats($url_hash, $service = 'wenpai') {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$this->table_name}
|
||||
SET last_accessed = NOW(),
|
||||
api_calls_saved = api_calls_saved + 1
|
||||
WHERE url_hash = %s AND service = %s",
|
||||
$url_hash, $service
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提升缓存级别
|
||||
*/
|
||||
private function promote_cache($url_hash, $service, $new_type) {
|
||||
global $wpdb;
|
||||
|
||||
$cache_duration = get_option('archiver_cache_duration', $this->cache_durations[$new_type]);
|
||||
$new_expires = date('Y-m-d H:i:s', time() + $cache_duration);
|
||||
|
||||
$wpdb->update(
|
||||
$this->table_name,
|
||||
[
|
||||
'cache_type' => $new_type,
|
||||
'expires_at' => $new_expires
|
||||
],
|
||||
['url_hash' => $url_hash, 'service' => $service]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期缓存
|
||||
*/
|
||||
public function cleanup_expired_cache() {
|
||||
global $wpdb;
|
||||
|
||||
// 保留最近30天的数据,即使已过期
|
||||
$cutoff_date = date('Y-m-d H:i:s', time() - (30 * DAY_IN_SECONDS));
|
||||
|
||||
$deleted = $wpdb->query($wpdb->prepare(
|
||||
"DELETE FROM {$this->table_name}
|
||||
WHERE expires_at < %s AND created_at < %s",
|
||||
current_time('mysql'), $cutoff_date
|
||||
));
|
||||
|
||||
// 清理孤立的内存缓存
|
||||
wp_cache_flush_group('archiver');
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计
|
||||
*/
|
||||
public function get_cache_stats() {
|
||||
global $wpdb;
|
||||
|
||||
$stats = $wpdb->get_row(
|
||||
"SELECT
|
||||
COUNT(*) as total_entries,
|
||||
SUM(api_calls_saved) as total_api_saves,
|
||||
SUM(CASE WHEN cache_type = 'hot' THEN 1 ELSE 0 END) as hot_entries,
|
||||
SUM(CASE WHEN cache_type = 'warm' THEN 1 ELSE 0 END) as warm_entries,
|
||||
SUM(CASE WHEN cache_type = 'cold' THEN 1 ELSE 0 END) as cold_entries,
|
||||
SUM(CASE WHEN cache_type = 'frozen' THEN 1 ELSE 0 END) as frozen_entries,
|
||||
COUNT(DISTINCT service) as services_used
|
||||
FROM {$this->table_name}
|
||||
WHERE status = 'active'"
|
||||
);
|
||||
|
||||
// 添加服务级别的统计
|
||||
if ($stats) {
|
||||
$stats->service_stats = $wpdb->get_results(
|
||||
"SELECT service, COUNT(*) as count, SUM(api_calls_saved) as saves
|
||||
FROM {$this->table_name}
|
||||
WHERE status = 'active'
|
||||
GROUP BY service"
|
||||
);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热缓存
|
||||
*/
|
||||
public function preheat_cache($post_types = null) {
|
||||
if (!$post_types) {
|
||||
$post_types = get_option('archiver_post_types', ['post', 'page']);
|
||||
}
|
||||
|
||||
$posts = get_posts([
|
||||
'post_type' => $post_types,
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 50,
|
||||
'date_query' => [
|
||||
'after' => '30 days ago'
|
||||
],
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'fields' => 'ids'
|
||||
]);
|
||||
|
||||
$enabled_services = get_option('archiver_services', array('wenpai' => true));
|
||||
$count = 0;
|
||||
|
||||
foreach ($posts as $post_id) {
|
||||
$url = get_permalink($post_id);
|
||||
foreach ($enabled_services as $service_id => $enabled) {
|
||||
if ($enabled) {
|
||||
$this->queue_for_update($url, $service_id);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存大小
|
||||
*/
|
||||
public function get_cache_size() {
|
||||
global $wpdb;
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
"SELECT ROUND(((data_length + index_length)), 2) as size
|
||||
FROM information_schema.TABLES
|
||||
WHERE table_schema = '" . DB_NAME . "'
|
||||
AND table_name = '" . $this->table_name . "'"
|
||||
);
|
||||
|
||||
return $result ? $result : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化缓存表
|
||||
*/
|
||||
public function optimize_cache_table() {
|
||||
global $wpdb;
|
||||
|
||||
// 优化表
|
||||
$wpdb->query("OPTIMIZE TABLE {$this->table_name}");
|
||||
|
||||
// 更新统计信息
|
||||
$wpdb->query("ANALYZE TABLE {$this->table_name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加存档计数
|
||||
*/
|
||||
private function increment_archived_count() {
|
||||
$count = get_option('archiver_total_archived', 0);
|
||||
update_option('archiver_total_archived', $count + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加失败计数
|
||||
*/
|
||||
private function increment_failed_count() {
|
||||
$count = get_option('archiver_failed_snapshots', 0);
|
||||
update_option('archiver_failed_snapshots', $count + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务健康状态
|
||||
*/
|
||||
public function get_service_health($service = null) {
|
||||
global $wpdb;
|
||||
|
||||
$where = $service ? $wpdb->prepare(" AND service = %s", $service) : "";
|
||||
|
||||
$result = $wpdb->get_row(
|
||||
"SELECT
|
||||
COUNT(*) as total_attempts,
|
||||
SUM(CASE WHEN snapshot_count > 0 THEN 1 ELSE 0 END) as successful,
|
||||
AVG(api_calls_saved) as avg_saves
|
||||
FROM {$this->table_name}
|
||||
WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)" . $where
|
||||
);
|
||||
|
||||
if ($result && $result->total_attempts > 0) {
|
||||
$result->success_rate = round(($result->successful / $result->total_attempts) * 100, 2);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册清理任务
|
||||
add_action('archiver_cleanup_cache', function() {
|
||||
$cache = new Archiver_Cache();
|
||||
$cleaned = $cache->cleanup_expired_cache();
|
||||
|
||||
if ($cleaned > 0) {
|
||||
error_log('[WP Archiver] Cleaned ' . $cleaned . ' expired cache entries');
|
||||
}
|
||||
|
||||
// 每周优化一次表
|
||||
if (date('w') == 0) { // 周日
|
||||
$cache->optimize_cache_table();
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load diff
514
js/archiver.js
514
js/archiver.js
|
@ -1,120 +1,430 @@
|
|||
(($) => {
|
||||
/**
|
||||
* WP Archiver 前端脚本
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready(() => {
|
||||
initAdminBarTrigger();
|
||||
initMetaboxButton();
|
||||
initTabSwitching();
|
||||
});
|
||||
|
||||
const initAdminBarTrigger = () => {
|
||||
const $triggerButton = $('#wp-admin-bar-archiver-trigger a');
|
||||
|
||||
if (!$triggerButton.length) return;
|
||||
const Archiver = {
|
||||
|
||||
$triggerButton.on('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const $menuItem = $(e.target).closest('li');
|
||||
$menuItem.addClass('archiver-active');
|
||||
config: {
|
||||
checkInterval: 5000,
|
||||
maxChecks: 12,
|
||||
fadeInDuration: 300,
|
||||
cachePrefix: 'archiver_cache_'
|
||||
},
|
||||
|
||||
try {
|
||||
const response = await wp.apiRequest({
|
||||
url: archiver.rest_url,
|
||||
method: 'POST',
|
||||
data: { url: archiver.url },
|
||||
beforeSend: (xhr) => {
|
||||
xhr.setRequestHeader('X-WP-Nonce', archiver.nonce);
|
||||
}
|
||||
});
|
||||
init: function() {
|
||||
$(document).ready(() => {
|
||||
this.initMetabox();
|
||||
this.initAdminBar();
|
||||
this.initAdminPage();
|
||||
this.initLazyLoading();
|
||||
});
|
||||
},
|
||||
|
||||
$menuItem.removeClass('archiver-active');
|
||||
|
||||
if (response.success) {
|
||||
$menuItem.addClass('archiver-success');
|
||||
console.log('Archiver:', response.message);
|
||||
} else {
|
||||
throw new Error(response.message || wp.i18n.__('Unknown error occurred', 'archiver'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Archiver Error:', error);
|
||||
$menuItem.addClass('archiver-failure');
|
||||
/**
|
||||
* 初始化元框
|
||||
*/
|
||||
initMetabox: function() {
|
||||
const $container = $('#archiver-snapshots');
|
||||
if (!$container.length) return;
|
||||
|
||||
const url = $container.data('url') || $('#archiver-url').val();
|
||||
if (!url) return;
|
||||
|
||||
// 延迟加载快照
|
||||
this.loadSnapshots(url, $container);
|
||||
|
||||
// 立即存档按钮
|
||||
$('#archiver-immediate-snapshot').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.triggerSnapshot(url);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化管理栏
|
||||
*/
|
||||
initAdminBar: function() {
|
||||
const $trigger = $('#wp-admin-bar-archiver-trigger a');
|
||||
if (!$trigger.length) return;
|
||||
|
||||
// 延迟加载快照计数
|
||||
const $countItem = $('#wp-admin-bar-archiver-snapshots');
|
||||
if ($countItem.length && archiver.url) {
|
||||
this.updateAdminBarCount(archiver.url);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
$menuItem.removeClass('archiver-success archiver-failure');
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
// 触发快照
|
||||
$trigger.on('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.triggerAdminBarSnapshot();
|
||||
});
|
||||
},
|
||||
|
||||
const initMetaboxButton = () => {
|
||||
const $immediateButton = $('#archiver-immediate-snapshot');
|
||||
|
||||
$immediateButton.on('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const $statusElement = $('#archiver-status');
|
||||
const url = $('#archiver-url').val();
|
||||
const nonce = $('#archiver_nonce').val();
|
||||
/**
|
||||
* 初始化管理页面
|
||||
*/
|
||||
initAdminPage: function() {
|
||||
// 标签切换
|
||||
$('.nav-tab').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const $tab = $(this);
|
||||
const tabId = $tab.data('tab');
|
||||
|
||||
$immediateButton.prop('disabled', true);
|
||||
$statusElement.show().text(archiver.i18n.triggering).removeClass('error success').addClass('processing');
|
||||
$('.nav-tab').removeClass('nav-tab-active');
|
||||
$tab.addClass('nav-tab-active');
|
||||
|
||||
try {
|
||||
const response = await $.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
action: 'archiver_immediate_snapshot',
|
||||
_ajax_nonce: nonce,
|
||||
url: url
|
||||
}
|
||||
});
|
||||
$('.archiver-tab-content').hide();
|
||||
$('#' + tabId).fadeIn(300);
|
||||
|
||||
if (response.success) {
|
||||
$statusElement.text(response.data.message).removeClass('processing').addClass('success');
|
||||
if (response.data.snapshots) {
|
||||
$('#archiver-snapshots ul').html(response.data.snapshots.join(''));
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.data.message || 'Unknown error');
|
||||
// 保存用户偏好
|
||||
if (typeof(Storage) !== "undefined") {
|
||||
localStorage.setItem('archiver_active_tab', tabId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Archiver Error:', error);
|
||||
$statusElement.text(`${archiver.i18n.error}: ${error.message || 'Request failed'}`)
|
||||
.removeClass('processing success')
|
||||
.addClass('error');
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
$immediateButton.prop('disabled', false);
|
||||
$statusElement.fadeOut(500, () => {
|
||||
$statusElement.removeClass('processing error success').empty();
|
||||
});
|
||||
|
||||
// 恢复上次的标签
|
||||
const savedTab = localStorage.getItem('archiver_active_tab');
|
||||
if (savedTab) {
|
||||
$(`.nav-tab[data-tab="${savedTab}"]`).trigger('click');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 延迟加载
|
||||
*/
|
||||
initLazyLoading: function() {
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const $element = $(entry.target);
|
||||
const url = $element.data('url');
|
||||
if (url && !$element.data('loaded')) {
|
||||
this.loadSnapshots(url, $element);
|
||||
$element.data('loaded', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
$('.archiver-lazy').each(function() {
|
||||
observer.observe(this);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
const initTabSwitching = () => {
|
||||
$('.nav-tab').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$('.nav-tab').removeClass('nav-tab-active');
|
||||
$(this).addClass('nav-tab-active');
|
||||
|
||||
$('.archiver-tab-content').hide();
|
||||
const tabId = $(this).data('tab');
|
||||
$('#' + tabId).show();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 加载快照
|
||||
*/
|
||||
loadSnapshots: function(url, $container) {
|
||||
// 检查本地缓存
|
||||
const cached = this.getCache(url);
|
||||
if (cached) {
|
||||
this.displaySnapshots(cached, $container);
|
||||
// 后台检查更新
|
||||
this.checkForUpdates(url, $container);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$('#archiver-spin-animation').length) {
|
||||
$('head').append(`
|
||||
<style id="archiver-spin-animation">
|
||||
@keyframes archiver-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
// 显示加载状态
|
||||
this.showLoading($container);
|
||||
|
||||
// 从服务器获取
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_get_snapshots',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
this.setCache(url, response.data);
|
||||
this.displaySnapshots(response.data, $container);
|
||||
} else {
|
||||
this.showError($container, response.data.message);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.showError($container, archiver.i18n.error);
|
||||
}
|
||||
</style>
|
||||
`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
})(jQuery);
|
||||
/**
|
||||
* 显示快照
|
||||
*/
|
||||
displaySnapshots: function(data, $container) {
|
||||
if (!data.html) {
|
||||
$container.html('<p>' + archiver.i18n.no_snapshots + '</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
$container.hide().html(data.html).fadeIn(this.config.fadeInDuration);
|
||||
|
||||
// 添加动画
|
||||
$container.find('li').each(function(index) {
|
||||
$(this).css({
|
||||
opacity: 0,
|
||||
transform: 'translateY(10px)'
|
||||
}).delay(index * 50).animate({
|
||||
opacity: 1,
|
||||
transform: 'translateY(0)'
|
||||
}, 300);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发快照
|
||||
*/
|
||||
triggerSnapshot: function(url) {
|
||||
const $button = $('#archiver-immediate-snapshot');
|
||||
const $status = $('#archiver-status');
|
||||
|
||||
$button.prop('disabled', true).addClass('updating-message');
|
||||
$status.show().removeClass('error success')
|
||||
.html('<span class="spinner is-active"></span> ' + archiver.i18n.triggering);
|
||||
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_immediate_snapshot',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
$status.removeClass('error').addClass('success')
|
||||
.html('<span class="dashicons dashicons-yes"></span> ' + response.data.message);
|
||||
|
||||
// 清除缓存并开始轮询
|
||||
this.clearCache(url);
|
||||
if (response.data.refresh) {
|
||||
this.startPolling(url);
|
||||
}
|
||||
} else {
|
||||
$status.removeClass('success').addClass('error')
|
||||
.html('<span class="dashicons dashicons-warning"></span> ' + response.data.message);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
$status.removeClass('success').addClass('error')
|
||||
.html('<span class="dashicons dashicons-warning"></span> ' + archiver.i18n.error);
|
||||
},
|
||||
complete: () => {
|
||||
$button.prop('disabled', false).removeClass('updating-message');
|
||||
setTimeout(() => {
|
||||
$status.fadeOut();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 管理栏触发快照
|
||||
*/
|
||||
triggerAdminBarSnapshot: function() {
|
||||
const $item = $('#wp-admin-bar-archiver');
|
||||
$item.addClass('archiver-active');
|
||||
|
||||
$.ajax({
|
||||
url: archiver.rest_url + 'trigger-snapshot',
|
||||
method: 'POST',
|
||||
beforeSend: (xhr) => {
|
||||
xhr.setRequestHeader('X-WP-Nonce', archiver.nonce);
|
||||
},
|
||||
data: {
|
||||
url: archiver.url
|
||||
},
|
||||
success: (response) => {
|
||||
$item.removeClass('archiver-active');
|
||||
if (response.success) {
|
||||
$item.addClass('archiver-success');
|
||||
this.clearCache(archiver.url);
|
||||
this.updateAdminBarCount(archiver.url);
|
||||
} else {
|
||||
$item.addClass('archiver-failure');
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
$item.removeClass('archiver-active').addClass('archiver-failure');
|
||||
},
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
$item.removeClass('archiver-success archiver-failure');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新管理栏计数
|
||||
*/
|
||||
updateAdminBarCount: function(url) {
|
||||
const $countItem = $('#wp-admin-bar-archiver-snapshots .ab-label');
|
||||
if (!$countItem.length) return;
|
||||
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_get_snapshots',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success && response.data.count !== undefined) {
|
||||
const count = response.data.count >= 10 ? '10+' : response.data.count;
|
||||
$countItem.text(archiver.i18n.view_all + ' (' + count + ')');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查更新
|
||||
*/
|
||||
checkForUpdates: function(url, $container) {
|
||||
$.ajax({
|
||||
url: archiver.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'archiver_check_cache_update',
|
||||
_ajax_nonce: archiver.ajax_nonce,
|
||||
url: url
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success && response.data.has_update) {
|
||||
this.clearCache(url);
|
||||
this.displaySnapshots(response.data, $container);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始轮询
|
||||
*/
|
||||
startPolling: function(url) {
|
||||
let checkCount = 0;
|
||||
const $container = $('#archiver-snapshots');
|
||||
|
||||
const interval = setInterval(() => {
|
||||
checkCount++;
|
||||
|
||||
if (checkCount > this.config.maxChecks) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkForUpdates(url, $container);
|
||||
}, this.config.checkInterval);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示加载状态
|
||||
*/
|
||||
showLoading: function($container) {
|
||||
const loadingHtml = `
|
||||
<div class="archiver-loading">
|
||||
<span class="spinner is-active"></span>
|
||||
${archiver.i18n.loading}
|
||||
</div>
|
||||
`;
|
||||
$container.html(loadingHtml);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示错误
|
||||
*/
|
||||
showError: function($container, message) {
|
||||
$container.html('<p class="archiver-error">' + message + '</p>');
|
||||
},
|
||||
|
||||
/**
|
||||
* 缓存管理
|
||||
*/
|
||||
getCache: function(url) {
|
||||
if (!this.isStorageAvailable()) return null;
|
||||
|
||||
try {
|
||||
const key = this.config.cachePrefix + this.hashCode(url);
|
||||
const cached = sessionStorage.getItem(key);
|
||||
|
||||
if (cached) {
|
||||
const data = JSON.parse(cached);
|
||||
// 5分钟缓存
|
||||
if (Date.now() - data.timestamp < 300000) {
|
||||
return data.content;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Cache read error:', e);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
setCache: function(url, data) {
|
||||
if (!this.isStorageAvailable()) return;
|
||||
|
||||
try {
|
||||
const key = this.config.cachePrefix + this.hashCode(url);
|
||||
const cacheData = {
|
||||
content: data,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
sessionStorage.setItem(key, JSON.stringify(cacheData));
|
||||
} catch (e) {
|
||||
console.warn('Cache write error:', e);
|
||||
}
|
||||
},
|
||||
|
||||
clearCache: function(url) {
|
||||
if (!this.isStorageAvailable()) return;
|
||||
|
||||
try {
|
||||
const key = this.config.cachePrefix + this.hashCode(url);
|
||||
sessionStorage.removeItem(key);
|
||||
} catch (e) {
|
||||
console.warn('Cache clear error:', e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 工具函数
|
||||
*/
|
||||
isStorageAvailable: function() {
|
||||
try {
|
||||
const test = '__archiver_test__';
|
||||
sessionStorage.setItem(test, test);
|
||||
sessionStorage.removeItem(test);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
hashCode: function(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash).toString();
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
Archiver.init();
|
||||
|
||||
// 暴露到全局
|
||||
window.WPArchiver = Archiver;
|
||||
|
||||
})(jQuery);
|
||||
|
|
422
languages/archiver-zh_CN-backup-202504060200460.po~
Normal file
422
languages/archiver-zh_CN-backup-202504060200460.po~
Normal file
|
@ -0,0 +1,422 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WP Archiver\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-06 09:41+0800\n"
|
||||
"PO-Revision-Date: 2025-04-06 09:56+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: 简体中文\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.6\n"
|
||||
"X-Poedit-Basepath: ..\n"
|
||||
"X-Poedit-Flags-xgettext: --add-comments=translators:\n"
|
||||
"X-Poedit-WPHeader: archiver.php\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
"X-Poedit-KeywordsList: "
|
||||
"__;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;_nx_noop:3c,1,2;__ngettext_noop:1,2\n"
|
||||
"X-Loco-Version: 2.6.11; wp-6.7.2\n"
|
||||
"X-Poedit-SearchPath-0: .\n"
|
||||
"X-Poedit-SearchPathExcluded-0: *.min.js\n"
|
||||
|
||||
#: includes/class-archiver-admin.php:25
|
||||
msgid "Archiver Settings"
|
||||
msgstr "时光机设置"
|
||||
|
||||
#: includes/class-archiver-admin.php:26 includes/class-archiver.php:25
|
||||
#: includes/class-archiver.php:223
|
||||
msgid "Archiver"
|
||||
msgstr "时光机"
|
||||
|
||||
#: includes/class-archiver-admin.php:35
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "您没有足够的权限访问此页面。"
|
||||
|
||||
#: includes/class-archiver-admin.php:42
|
||||
#, php-format
|
||||
msgid "Version: %s"
|
||||
msgstr "版本:%s"
|
||||
|
||||
#: includes/class-archiver-admin.php:43
|
||||
msgid "Document"
|
||||
msgstr "文档"
|
||||
|
||||
#: includes/class-archiver-admin.php:44
|
||||
msgid "Support"
|
||||
msgstr "支持"
|
||||
|
||||
#: includes/class-archiver-admin.php:48
|
||||
msgid "Archive Statistics"
|
||||
msgstr "存档统计"
|
||||
|
||||
#: includes/class-archiver-admin.php:49
|
||||
msgid "View statistics and records of Archive."
|
||||
msgstr "查看存档的统计数据和记录。"
|
||||
|
||||
#: includes/class-archiver-admin.php:53
|
||||
msgid "Pending URLs"
|
||||
msgstr "待定 URL"
|
||||
|
||||
#: includes/class-archiver-admin.php:57
|
||||
msgid "Total Archived"
|
||||
msgstr "总存档"
|
||||
|
||||
#: includes/class-archiver-admin.php:61
|
||||
msgid "Failed Snapshots"
|
||||
msgstr "失败快照"
|
||||
|
||||
#: includes/class-archiver-admin.php:65
|
||||
msgid "Last Run"
|
||||
msgstr "上次运行"
|
||||
|
||||
#: includes/class-archiver-admin.php:66
|
||||
msgid "Never"
|
||||
msgstr "从未"
|
||||
|
||||
#: includes/class-archiver-admin.php:69
|
||||
msgid "Next Run"
|
||||
msgstr "下次运行"
|
||||
|
||||
#: includes/class-archiver-admin.php:72
|
||||
msgid "Not scheduled"
|
||||
msgstr "无计划任务"
|
||||
|
||||
#: includes/class-archiver-admin.php:80
|
||||
msgid "General Settings"
|
||||
msgstr "常规设置"
|
||||
|
||||
#: includes/class-archiver-admin.php:81
|
||||
msgid "Snapshot Tools"
|
||||
msgstr "快照工具"
|
||||
|
||||
#: includes/class-archiver-admin.php:82
|
||||
msgid "About"
|
||||
msgstr "关于"
|
||||
|
||||
#: includes/class-archiver-admin.php:92
|
||||
msgid "Content Types to Archive"
|
||||
msgstr "要存档的内容类型"
|
||||
|
||||
#: includes/class-archiver-admin.php:93
|
||||
msgid ""
|
||||
"Select which content types should be automatically archived when published "
|
||||
"or updated."
|
||||
msgstr "选择发布或更新时应自动存档的内容类型。"
|
||||
|
||||
#: includes/class-archiver-admin.php:96
|
||||
msgid "Post Types"
|
||||
msgstr "文章类型"
|
||||
|
||||
#: includes/class-archiver-admin.php:111
|
||||
msgid "Update Frequency"
|
||||
msgstr "更新频率"
|
||||
|
||||
#: includes/class-archiver-admin.php:112
|
||||
msgid "Adjust the update frequency to avoid excessive requests."
|
||||
msgstr "调整更新频率,避免过多的请求。"
|
||||
|
||||
#: includes/class-archiver-admin.php:116
|
||||
msgid "Frequency"
|
||||
msgstr "频率"
|
||||
|
||||
#: includes/class-archiver-admin.php:120
|
||||
msgid "Hourly"
|
||||
msgstr "每小时"
|
||||
|
||||
#: includes/class-archiver-admin.php:123
|
||||
msgid "Twice Daily"
|
||||
msgstr "每天两次"
|
||||
|
||||
#: includes/class-archiver-admin.php:126
|
||||
msgid "Daily"
|
||||
msgstr "每天"
|
||||
|
||||
#: includes/class-archiver-admin.php:129
|
||||
msgid "Weekly"
|
||||
msgstr "每周"
|
||||
|
||||
#: includes/class-archiver-admin.php:132
|
||||
msgid "How often should the plugin check for new archives?"
|
||||
msgstr "插件应该多久检查一次新存档?"
|
||||
|
||||
#: includes/class-archiver-admin.php:136
|
||||
msgid "Save Settings"
|
||||
msgstr "保存设置"
|
||||
|
||||
#: includes/class-archiver-admin.php:145
|
||||
msgid "Pending Updates"
|
||||
msgstr "待更新"
|
||||
|
||||
#: includes/class-archiver-admin.php:152
|
||||
msgid "Run Manual Update Now"
|
||||
msgstr "立即运行手动更新"
|
||||
|
||||
#: includes/class-archiver-admin.php:153
|
||||
msgid "Process all pending URLs immediately."
|
||||
msgstr "立即处理所有待处理的 URL。"
|
||||
|
||||
#: includes/class-archiver-admin.php:156
|
||||
msgid "Bulk Actions"
|
||||
msgstr "批量操作"
|
||||
|
||||
#: includes/class-archiver-admin.php:161
|
||||
msgid "Select bulk action..."
|
||||
msgstr "选择批量操作..."
|
||||
|
||||
#: includes/class-archiver-admin.php:162
|
||||
msgid "Archive all published posts"
|
||||
msgstr "归档所有已发布文章"
|
||||
|
||||
#: includes/class-archiver-admin.php:163
|
||||
msgid "Clear pending queue"
|
||||
msgstr "清除待处理队列"
|
||||
|
||||
#: includes/class-archiver-admin.php:169
|
||||
msgid "Apply"
|
||||
msgstr "应用"
|
||||
|
||||
#: includes/class-archiver-admin.php:172
|
||||
msgid "Debug Tools"
|
||||
msgstr "调试工具"
|
||||
|
||||
#: includes/class-archiver-admin.php:178
|
||||
msgid "Clear Cache"
|
||||
msgstr "清除缓存"
|
||||
|
||||
#: includes/class-archiver-admin.php:179
|
||||
msgid "Clear all cached snapshot data."
|
||||
msgstr "清除所有缓存的快照数据。"
|
||||
|
||||
#: includes/class-archiver-admin.php:189
|
||||
msgid "About Internet Archive"
|
||||
msgstr "关于互联网档案馆"
|
||||
|
||||
#: includes/class-archiver-admin.php:191
|
||||
msgid ""
|
||||
"The Internet Archive is a non-profit digital library that preserves web "
|
||||
"pages, books, audio recordings, videos, and software for future generations. "
|
||||
"One of its most well-known services is the Wayback Machine, which allows "
|
||||
"users to view archived versions of web pages across time."
|
||||
msgstr ""
|
||||
"Internet Archive 是一个非营利性数字图书馆,它为子孙后代保存网页、书籍、录音、"
|
||||
"视频和软件。其最著名的服务之一是时间回溯器(Wayback Machine),它允许用户查看"
|
||||
"不同时期的网页存档版本。"
|
||||
|
||||
#: includes/class-archiver-admin.php:193
|
||||
msgid "Why Archive Your Website?"
|
||||
msgstr "为什么要存档您的网站?"
|
||||
|
||||
#: includes/class-archiver-admin.php:195
|
||||
msgid "Preserve your content against accidental deletion or server failures"
|
||||
msgstr "保护您的内容免遭意外删除或服务器故障"
|
||||
|
||||
#: includes/class-archiver-admin.php:196
|
||||
msgid "Maintain a public record of your published work"
|
||||
msgstr "保留已发表作品的公开记录"
|
||||
|
||||
#: includes/class-archiver-admin.php:197
|
||||
msgid "Protect against link rot (when external links stop working)"
|
||||
msgstr "防止链接失效(当外部链接无法访问时)"
|
||||
|
||||
#: includes/class-archiver-admin.php:198
|
||||
msgid "Establish historical context and content provenance"
|
||||
msgstr "建立历史背景和内容出处"
|
||||
|
||||
#: includes/class-archiver-admin.php:201
|
||||
msgid "About WP Archiver"
|
||||
msgstr "关于文派时光机(WP Archiver)"
|
||||
|
||||
#: includes/class-archiver-admin.php:202
|
||||
msgid ""
|
||||
"This plugin automatically archives your WordPress content to the Internet "
|
||||
"Archive whenever you publish or update posts, pages, or other content types. "
|
||||
"It helps ensure your valuable content is preserved for the long term."
|
||||
msgstr ""
|
||||
"每当您发布或更新文章、页面或其他内容类型时,此插件都会自动将您的 WordPress 内"
|
||||
"容存档到 Internet Archive。它有助于确保您的宝贵内容得到长期保存。"
|
||||
|
||||
#: includes/class-archiver-admin.php:204
|
||||
msgid "Best Practices"
|
||||
msgstr "最佳实践"
|
||||
|
||||
#: includes/class-archiver-admin.php:206
|
||||
msgid "Archive important content immediately after publication"
|
||||
msgstr "重要内容发布后立即归档"
|
||||
|
||||
#: includes/class-archiver-admin.php:207
|
||||
msgid "For large sites, use weekly archiving to avoid overwhelming the service"
|
||||
msgstr "对于大型网站,请使用每周归档以避免服务过载"
|
||||
|
||||
#: includes/class-archiver-admin.php:208
|
||||
msgid ""
|
||||
"Regularly check your archived content to ensure it was captured correctly"
|
||||
msgstr "定期检查存档内容,以确保其被正确捕获"
|
||||
|
||||
#: includes/class-archiver-admin.php:209
|
||||
msgid "Consider manually archiving key pages after major updates"
|
||||
msgstr "考虑在重大更新后手动归档关键页面"
|
||||
|
||||
#: includes/class-archiver-admin.php:212
|
||||
msgid "Thank you for helping preserve the web by archiving your content!"
|
||||
msgstr "感谢您通过存档内容来帮助保护网络!"
|
||||
|
||||
#: includes/class-archiver-admin.php:233
|
||||
msgid "Manual update triggered. The update will run in the background."
|
||||
msgstr "已触发手动更新。更新将在后台运行。"
|
||||
|
||||
#: includes/class-archiver-admin.php:250
|
||||
msgid "Please select at least one post type to archive."
|
||||
msgstr "请至少选择一个要存档的文章类型。"
|
||||
|
||||
#: includes/class-archiver-admin.php:261
|
||||
msgid "Invalid post types selected."
|
||||
msgstr "选择的文章类型无效。"
|
||||
|
||||
#: includes/class-archiver-admin.php:276
|
||||
msgid "Settings saved successfully."
|
||||
msgstr "设置保存成功。"
|
||||
|
||||
#: includes/class-archiver-admin.php:292
|
||||
msgid "Pending queue has been cleared."
|
||||
msgstr "待处理队列已被清除。"
|
||||
|
||||
#: includes/class-archiver-admin.php:300
|
||||
msgid "Please select a valid bulk action."
|
||||
msgstr "请选择有效的批量操作。"
|
||||
|
||||
#: includes/class-archiver-admin.php:322
|
||||
#, php-format
|
||||
msgid "%d posts have been added to the archive queue."
|
||||
msgstr "%d 个文章已添加到存档队列。"
|
||||
|
||||
#: includes/class-archiver-admin.php:337
|
||||
msgid "All cached data has been cleared."
|
||||
msgstr "所有缓存数据已被清除。"
|
||||
|
||||
#: includes/class-archiver-admin.php:345
|
||||
msgid "No pending updates."
|
||||
msgstr "没有待处理的更新。"
|
||||
|
||||
#: includes/class-archiver-admin.php:348
|
||||
#, php-format
|
||||
msgid "Total pending URLs: %d"
|
||||
msgstr "待处理的 URL 总数:%d"
|
||||
|
||||
#: includes/class-archiver-admin.php:355
|
||||
msgid "more"
|
||||
msgstr "更多"
|
||||
|
||||
#: includes/class-archiver.php:89 includes/class-archiver.php:105
|
||||
#: includes/class-archiver.php:124
|
||||
msgid "Archives"
|
||||
msgstr "存档"
|
||||
|
||||
#: includes/class-archiver.php:142
|
||||
msgid ""
|
||||
"No archives yet. A snapshot request has been scheduled and will be processed "
|
||||
"soon."
|
||||
msgstr "尚无存档。快照请求已安排并将很快处理。"
|
||||
|
||||
#: includes/class-archiver.php:144
|
||||
msgid "There are no archives of this URL."
|
||||
msgstr "此 URL 没有存档。"
|
||||
|
||||
#: includes/class-archiver.php:164
|
||||
msgid "See all snapshots ↗"
|
||||
msgstr "查看所有快照 ↗"
|
||||
|
||||
#: includes/class-archiver.php:167
|
||||
msgid "Archive Now"
|
||||
msgstr "立即存档"
|
||||
|
||||
#: includes/class-archiver.php:175
|
||||
msgid "Security check failed. Please try again."
|
||||
msgstr "安全检查失败。请重试。"
|
||||
|
||||
#: includes/class-archiver.php:180
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "您无权执行此操作。"
|
||||
|
||||
#: includes/class-archiver.php:186
|
||||
msgid "Invalid URL provided."
|
||||
msgstr "提供的 URL 无效。"
|
||||
|
||||
#: includes/class-archiver.php:203
|
||||
msgid "Snapshot created successfully!"
|
||||
msgstr "快照创建成功!"
|
||||
|
||||
#: includes/class-archiver.php:208
|
||||
msgid "Failed to retrieve snapshots."
|
||||
msgstr "无法检索快照。"
|
||||
|
||||
#: includes/class-archiver.php:235
|
||||
msgid "Snapshots"
|
||||
msgstr "快照"
|
||||
|
||||
#: includes/class-archiver.php:242
|
||||
msgid "Trigger Snapshot"
|
||||
msgstr "触发快照"
|
||||
|
||||
#: includes/class-archiver.php:445
|
||||
msgid "Triggering snapshot..."
|
||||
msgstr "正在触发快照..."
|
||||
|
||||
#: includes/class-archiver.php:446
|
||||
msgid "Snapshot triggered successfully!"
|
||||
msgstr "快照触发成功!"
|
||||
|
||||
#: includes/class-archiver.php:447 includes/class-archiver.php:661
|
||||
msgid "Failed to trigger snapshot."
|
||||
msgstr "无法触发快照。"
|
||||
|
||||
#: includes/class-archiver.php:475
|
||||
msgid ""
|
||||
"Archiver is currently disabled via the <code>archiver_can_run</code> filter."
|
||||
msgstr "时光机存档器当前已通过 <code>archiver_can_run</code> 过滤器禁用。"
|
||||
|
||||
#: includes/class-archiver.php:491
|
||||
msgid "You do not have permissions to trigger snapshots."
|
||||
msgstr "您没有触发快照的权限。"
|
||||
|
||||
#: includes/class-archiver.php:495
|
||||
msgid "Invalid URL parameter."
|
||||
msgstr "URL 参数无效。"
|
||||
|
||||
#: includes/class-archiver.php:500
|
||||
msgid "Snapshot request recorded and will be processed soon."
|
||||
msgstr "快照请求已记录并将很快处理。"
|
||||
|
||||
#: includes/class-archiver.php:643
|
||||
msgid "URL cannot be empty."
|
||||
msgstr "URL 不能为空。"
|
||||
|
||||
#: includes/class-archiver.php:647
|
||||
msgid "Snapshot already taken recently."
|
||||
msgstr "最近已拍摄快照。"
|
||||
|
||||
#: js/archiver.js:36
|
||||
msgid "Unknown error occurred"
|
||||
msgstr "发生未知错误"
|
||||
|
||||
#. Plugin Name of the plugin/theme
|
||||
msgid "WP Archiver"
|
||||
msgstr "文派时光机(WP Archiver)"
|
||||
|
||||
#. Plugin URI of the plugin/theme
|
||||
msgid "http://wenpai.org/plugins/wp-archiver"
|
||||
msgstr "http://wenpai.org/plugins/wp-archiver"
|
||||
|
||||
#. Description of the plugin/theme
|
||||
msgid "Archive your content using the Wayback Machine."
|
||||
msgstr "使用 Wayback Machine 存档您的内容。"
|
||||
|
||||
#. Author of the plugin/theme
|
||||
msgid "WenPai.org"
|
||||
msgstr "文派开源"
|
||||
|
||||
#. Author URI of the plugin/theme
|
||||
msgid "https://wenpai.org/"
|
||||
msgstr "https://wenpai.org/"
|
299
wp-archiver.php
299
wp-archiver.php
|
@ -2,8 +2,8 @@
|
|||
/**
|
||||
* Plugin Name: WP Archiver
|
||||
* Plugin URI: http://wenpai.org/plugins/wp-archiver
|
||||
* Description: Archive your content using the Wayback Machine.
|
||||
* Version: 1.3.2
|
||||
* Description: Archive your content using multiple archive services with advanced caching.
|
||||
* Version: 2.0.0
|
||||
* Author: WenPai.org
|
||||
* Author URI: https://wenpai.org/
|
||||
* License: GPL-2.0+
|
||||
|
@ -12,74 +12,265 @@
|
|||
* Domain Path: /languages
|
||||
*/
|
||||
|
||||
|
||||
if (!defined('WPINC')) {
|
||||
die;
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define('ARCHIVER_VERSION', '1.3.2');
|
||||
// 定义常量
|
||||
define('ARCHIVER_VERSION', '2.0.0');
|
||||
define('ARCHIVER_PLUGIN_DIR_URL', plugin_dir_url(__FILE__));
|
||||
define('ARCHIVER_PLUGIN_DIR_PATH', plugin_dir_path(__FILE__));
|
||||
define('ARCHIVER_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
||||
|
||||
require_once ARCHIVER_PLUGIN_DIR_PATH . 'includes/class-archiver.php';
|
||||
require_once ARCHIVER_PLUGIN_DIR_PATH . 'includes/class-archiver-admin.php';
|
||||
// 定义存档服务 - 修复原始的 wenpai.net 服务
|
||||
define('ARCHIVER_SERVICES', array(
|
||||
'wayback' => array(
|
||||
'name' => 'Internet Archive',
|
||||
'save_url' => 'https://web.archive.org/save/',
|
||||
'fetch_url' => 'https://web.archive.org/cdx/search/cdx',
|
||||
'view_url' => 'https://web.archive.org/web/',
|
||||
'enabled' => true
|
||||
),
|
||||
'wenpai' => array(
|
||||
'name' => 'WenPai Archive',
|
||||
'save_url' => 'https://web.wenpai.net/save/',
|
||||
'fetch_url' => 'https://web.wenpai.net/cdx/',
|
||||
'view_url' => 'https://web.wenpai.net/web/',
|
||||
'enabled' => true
|
||||
),
|
||||
'archive_today' => array(
|
||||
'name' => 'Archive.today',
|
||||
'save_url' => 'https://archive.today/?run=1&url=',
|
||||
'fetch_url' => 'https://archive.today/',
|
||||
'view_url' => 'https://archive.today/',
|
||||
'enabled' => false
|
||||
)
|
||||
));
|
||||
|
||||
function archiver_activate() {
|
||||
if (!get_option('archiver_post_types')) {
|
||||
update_option('archiver_post_types', array('post', 'page'));
|
||||
}
|
||||
if (!get_option('archiver_update_frequency')) {
|
||||
update_option('archiver_update_frequency', 'daily');
|
||||
}
|
||||
if (!get_option('archiver_urls_to_update')) {
|
||||
update_option('archiver_urls_to_update', array());
|
||||
}
|
||||
if (!wp_next_scheduled('archiver_process_urls')) {
|
||||
$frequency = get_option('archiver_update_frequency', 'daily');
|
||||
wp_schedule_event(time(), $frequency, 'archiver_process_urls');
|
||||
}
|
||||
}
|
||||
// 插件激活
|
||||
register_activation_hook(__FILE__, 'archiver_activate');
|
||||
function archiver_activate() {
|
||||
// 设置默认选项
|
||||
add_option('archiver_post_types', array('post', 'page'));
|
||||
add_option('archiver_update_frequency', 'daily');
|
||||
add_option('archiver_urls_to_update', array());
|
||||
add_option('archiver_cache_enabled', true);
|
||||
add_option('archiver_background_queue', array());
|
||||
add_option('archiver_total_archived', 0);
|
||||
add_option('archiver_failed_snapshots', 0);
|
||||
add_option('archiver_services', array('wenpai' => true));
|
||||
add_option('archiver_primary_service', 'wenpai');
|
||||
add_option('archiver_auto_archive', true);
|
||||
add_option('archiver_archive_on_publish', true);
|
||||
add_option('archiver_max_queue_size', 100);
|
||||
add_option('archiver_batch_size', 3);
|
||||
|
||||
function archiver_uninstall() {
|
||||
// 创建缓存表
|
||||
archiver_create_cache_table();
|
||||
|
||||
// 安排定时任务
|
||||
if (!wp_next_scheduled('archiver_process_urls')) {
|
||||
wp_schedule_event(time(), 'daily', 'archiver_process_urls');
|
||||
}
|
||||
|
||||
if (!wp_next_scheduled('archiver_cleanup_cache')) {
|
||||
wp_schedule_event(time(), 'daily', 'archiver_cleanup_cache');
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
wp_cache_flush();
|
||||
}
|
||||
|
||||
// 创建缓存表
|
||||
function archiver_create_cache_table() {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'archiver_cache';
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
url varchar(255) NOT NULL,
|
||||
url_hash varchar(32) NOT NULL,
|
||||
service varchar(50) NOT NULL DEFAULT 'wayback',
|
||||
snapshot_data longtext NOT NULL,
|
||||
snapshot_count int(11) DEFAULT 0,
|
||||
cache_type varchar(20) DEFAULT 'warm',
|
||||
last_accessed datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at datetime NOT NULL,
|
||||
api_calls_saved int(11) DEFAULT 0,
|
||||
status varchar(20) DEFAULT 'active',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY url_service (url_hash, service),
|
||||
KEY expires_at (expires_at),
|
||||
KEY cache_type (cache_type),
|
||||
KEY service (service)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
dbDelta($sql);
|
||||
|
||||
// 添加索引以提高查询性能
|
||||
$wpdb->query("CREATE INDEX IF NOT EXISTS idx_status_expires ON {$table_name} (status, expires_at)");
|
||||
}
|
||||
|
||||
// 插件停用
|
||||
register_deactivation_hook(__FILE__, 'archiver_deactivate');
|
||||
function archiver_deactivate() {
|
||||
// 删除定时任务
|
||||
$timestamp = wp_next_scheduled('archiver_process_urls');
|
||||
if ($timestamp) {
|
||||
wp_unschedule_event($timestamp, 'archiver_process_urls');
|
||||
}
|
||||
delete_option('archiver_post_types');
|
||||
delete_option('archiver_update_frequency');
|
||||
delete_option('archiver_urls_to_update');
|
||||
delete_option('archiver_last_run');
|
||||
}
|
||||
register_uninstall_hook(__FILE__, 'archiver_uninstall');
|
||||
|
||||
function archiver_load_textdomain() {
|
||||
load_plugin_textdomain(
|
||||
'archiver',
|
||||
false,
|
||||
dirname(plugin_basename(__FILE__)) . '/languages/'
|
||||
);
|
||||
}
|
||||
add_action('init', 'archiver_load_textdomain');
|
||||
|
||||
function archiver_run() {
|
||||
$archiver = Archiver::get_instance();
|
||||
if (is_admin()) {
|
||||
new Archiver_Admin($archiver);
|
||||
$timestamp = wp_next_scheduled('archiver_cleanup_cache');
|
||||
if ($timestamp) {
|
||||
wp_unschedule_event($timestamp, 'archiver_cleanup_cache');
|
||||
}
|
||||
$archiver->run();
|
||||
return $archiver;
|
||||
|
||||
// 清理缓存
|
||||
wp_cache_flush();
|
||||
}
|
||||
add_action('plugins_loaded', 'archiver_run');
|
||||
|
||||
// 插件卸载
|
||||
register_uninstall_hook(__FILE__, 'archiver_uninstall');
|
||||
function archiver_uninstall() {
|
||||
// 删除选项
|
||||
$options = array(
|
||||
'archiver_post_types',
|
||||
'archiver_update_frequency',
|
||||
'archiver_urls_to_update',
|
||||
'archiver_cache_enabled',
|
||||
'archiver_background_queue',
|
||||
'archiver_last_run',
|
||||
'archiver_total_archived',
|
||||
'archiver_failed_snapshots',
|
||||
'archiver_services',
|
||||
'archiver_primary_service',
|
||||
'archiver_auto_archive',
|
||||
'archiver_archive_on_publish',
|
||||
'archiver_max_queue_size',
|
||||
'archiver_batch_size'
|
||||
);
|
||||
|
||||
// Integrate UpdatePulse Server for updates using PUC v5.3
|
||||
require_once plugin_dir_path(__FILE__) . 'lib/plugin-update-checker/plugin-update-checker.php';
|
||||
use YahnisElsts\PluginUpdateChecker\v5p3\PucFactory;
|
||||
foreach ($options as $option) {
|
||||
delete_option($option);
|
||||
}
|
||||
|
||||
$WpArchiverUpdateChecker = PucFactory::buildUpdateChecker(
|
||||
'https://updates.weixiaoduo.com/wp-archiver.json',
|
||||
__FILE__,
|
||||
'wp-archiver'
|
||||
);
|
||||
// 删除缓存表
|
||||
global $wpdb;
|
||||
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}archiver_cache");
|
||||
|
||||
// 清理所有transients
|
||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_archiver_%'");
|
||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_archiver_%'");
|
||||
}
|
||||
|
||||
// 加载文本域
|
||||
add_action('init', 'archiver_load_textdomain');
|
||||
function archiver_load_textdomain() {
|
||||
load_plugin_textdomain('archiver', false, dirname(plugin_basename(__FILE__)) . '/languages/');
|
||||
}
|
||||
|
||||
// 延长 nonce 有效期(仅针对本插件)
|
||||
add_filter('nonce_life', function($lifespan) {
|
||||
if (isset($_GET['page']) && $_GET['page'] === 'archiver-settings') {
|
||||
return DAY_IN_SECONDS; // 24小时
|
||||
}
|
||||
return $lifespan;
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
function archiver_handle_error($message, $type = 'error') {
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log('[WP Archiver] ' . $type . ': ' . $message);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载类文件 - 注意顺序很重要!
|
||||
function archiver_load_dependencies() {
|
||||
$includes_dir = ARCHIVER_PLUGIN_DIR_PATH . 'includes/';
|
||||
|
||||
// 按正确顺序加载
|
||||
$files = array(
|
||||
'class-archiver-cache.php', // 缓存类必须先加载
|
||||
'class-archiver.php', // 主类
|
||||
'class-archiver-admin.php' // 管理类最后加载
|
||||
);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file_path = $includes_dir . $file;
|
||||
if (file_exists($file_path)) {
|
||||
require_once $file_path;
|
||||
} else {
|
||||
archiver_handle_error('Missing required file: ' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化插件
|
||||
add_action('plugins_loaded', 'archiver_init', 5);
|
||||
function archiver_init() {
|
||||
// 加载依赖
|
||||
archiver_load_dependencies();
|
||||
|
||||
// 运行插件
|
||||
archiver_run();
|
||||
}
|
||||
|
||||
// 运行插件主逻辑
|
||||
function archiver_run() {
|
||||
try {
|
||||
$archiver = Archiver::get_instance();
|
||||
|
||||
if (is_admin()) {
|
||||
new Archiver_Admin($archiver);
|
||||
}
|
||||
|
||||
$archiver->run();
|
||||
|
||||
return $archiver;
|
||||
|
||||
} catch (Exception $e) {
|
||||
archiver_handle_error('Failed to initialize: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 添加设置链接到插件列表
|
||||
add_filter('plugin_action_links_' . ARCHIVER_PLUGIN_BASENAME, 'archiver_add_action_links');
|
||||
function archiver_add_action_links($links) {
|
||||
$settings_link = '<a href="' . admin_url('tools.php?page=archiver-settings') . '">' . __('Settings', 'archiver') . '</a>';
|
||||
array_unshift($links, $settings_link);
|
||||
return $links;
|
||||
}
|
||||
|
||||
// 检查系统要求
|
||||
add_action('admin_init', 'archiver_check_requirements');
|
||||
function archiver_check_requirements() {
|
||||
if (version_compare(PHP_VERSION, '5.6', '<')) {
|
||||
add_action('admin_notices', function() {
|
||||
echo '<div class="notice notice-error"><p>';
|
||||
echo __('WP Archiver requires PHP 5.6 or higher.', 'archiver');
|
||||
echo '</p></div>';
|
||||
});
|
||||
}
|
||||
|
||||
if (version_compare(get_bloginfo('version'), '4.9', '<')) {
|
||||
add_action('admin_notices', function() {
|
||||
echo '<div class="notice notice-error"><p>';
|
||||
echo __('WP Archiver requires WordPress 4.9 or higher.', 'archiver');
|
||||
echo '</p></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 性能优化:限制队列大小
|
||||
add_filter('archiver_queue_limit', function($limit) {
|
||||
return get_option('archiver_max_queue_size', 100);
|
||||
});
|
||||
|
||||
// 性能优化:批处理大小
|
||||
add_filter('archiver_batch_size', function($size) {
|
||||
return get_option('archiver_batch_size', 3);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue