2.0 稳定版

This commit is contained in:
文派备案 2025-05-26 02:07:05 +08:00
parent 00a1ba5f27
commit 900b43143f
9 changed files with 4665 additions and 954 deletions

476
assets/css/archiver.css Normal file
View 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
View 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);

View file

@ -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

View 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

View file

@ -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);

View 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/"

View file

@ -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);
});