mirror of
https://github.com/WPMultisite/wp-multisite-dashboard.git
synced 2025-08-03 03:08:41 +08:00
补充中文语言包
This commit is contained in:
parent
0ecad06f54
commit
836b293850
18 changed files with 5061 additions and 2398 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
235
assets/dashboard-core.js
Normal file
235
assets/dashboard-core.js
Normal file
|
@ -0,0 +1,235 @@
|
|||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
const MSD_Core = {
|
||||
refreshInterval: null,
|
||||
refreshRate: 300000,
|
||||
isRefreshing: false,
|
||||
retryCount: 0,
|
||||
maxRetries: 3,
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.startAutoRefresh();
|
||||
this.setupErrorHandling();
|
||||
},
|
||||
|
||||
bindEvents() {
|
||||
$(document)
|
||||
.on('click', '.msd-refresh-btn', this.handleRefreshClick.bind(this));
|
||||
|
||||
$(window).on('beforeunload', () => {
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setupErrorHandling() {
|
||||
$(document).ajaxError((event, xhr, settings, error) => {
|
||||
if (settings.url && settings.url.includes('msd_')) {
|
||||
console.error('MSD Ajax Error:', error, xhr);
|
||||
this.showNotice(msdAjax.strings.error_occurred, 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
startAutoRefresh() {
|
||||
this.refreshInterval = setInterval(() => {
|
||||
if (!this.isRefreshing && document.visibilityState === 'visible') {
|
||||
if (window.MSD_Widgets && window.MSD_Widgets.loadAllWidgets) {
|
||||
window.MSD_Widgets.loadAllWidgets();
|
||||
}
|
||||
}
|
||||
}, this.refreshRate);
|
||||
},
|
||||
|
||||
handleRefreshClick(e) {
|
||||
e.preventDefault();
|
||||
const $btn = $(e.currentTarget);
|
||||
const widgetType = $btn.data('widget');
|
||||
|
||||
if ($btn.hasClass('refreshing')) return;
|
||||
|
||||
$btn.addClass('refreshing').prop('disabled', true);
|
||||
|
||||
setTimeout(() => {
|
||||
$btn.removeClass('refreshing').prop('disabled', false);
|
||||
}, 2000);
|
||||
|
||||
if (widgetType && window.MSD_Widgets) {
|
||||
window.MSD_Widgets.loadWidget(widgetType);
|
||||
this.showNotice(msdAjax.strings.refresh_success, 'success', 2000);
|
||||
} else if (window.MSD_Widgets) {
|
||||
window.MSD_Widgets.loadAllWidgets();
|
||||
this.showNotice(msdAjax.strings.refresh_success, 'success', 2000);
|
||||
}
|
||||
},
|
||||
|
||||
makeAjaxRequest(action, data, successCallback, errorCallback) {
|
||||
const ajaxData = {
|
||||
action: action,
|
||||
nonce: msdAjax.nonce,
|
||||
...data
|
||||
};
|
||||
|
||||
return $.post(msdAjax.ajaxurl, ajaxData)
|
||||
.done((response) => {
|
||||
if (response.success) {
|
||||
successCallback(response);
|
||||
} else {
|
||||
errorCallback(response.data || 'Unknown error');
|
||||
}
|
||||
})
|
||||
.fail((xhr, status, error) => {
|
||||
errorCallback(error);
|
||||
});
|
||||
},
|
||||
|
||||
showNotice(message, type = 'info', duration = 5000) {
|
||||
const $notice = $(`<div class="msd-notice ${type}"><p>${this.escapeHtml(message)}</p></div>`);
|
||||
|
||||
const $container = $('.wrap h1').first();
|
||||
if ($container.length) {
|
||||
$container.after($notice);
|
||||
} else {
|
||||
$('body').prepend($notice);
|
||||
}
|
||||
|
||||
if (duration > 0) {
|
||||
setTimeout(() => {
|
||||
$notice.fadeOut(300, () => $notice.remove());
|
||||
}, duration);
|
||||
}
|
||||
|
||||
$notice.on('click', () => {
|
||||
$notice.fadeOut(300, () => $notice.remove());
|
||||
});
|
||||
},
|
||||
|
||||
formatNumber(num) {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
},
|
||||
|
||||
formatTime(date) {
|
||||
return date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
decodeHtmlEntities(text) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.innerHTML = text;
|
||||
return textarea.value;
|
||||
},
|
||||
|
||||
truncateText(text, maxLength) {
|
||||
if (!text || text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return text.substring(0, maxLength).trim() + '...';
|
||||
},
|
||||
|
||||
isValidUrl(string) {
|
||||
try {
|
||||
const url = new URL(string);
|
||||
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
getStorageStatusClass(status) {
|
||||
const statusMap = {
|
||||
critical: 'critical',
|
||||
warning: 'warning',
|
||||
good: '',
|
||||
default: ''
|
||||
};
|
||||
return statusMap[status] || statusMap.default;
|
||||
},
|
||||
|
||||
getDefaultFavicon() {
|
||||
return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj48cmVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9IiNmMGYwZjAiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iMC4zNWVtIj5TPC90ZXh0Pjwvc3ZnPg==';
|
||||
},
|
||||
|
||||
getDefaultAvatar() {
|
||||
return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIj48Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIyMCIgZmlsbD0iI2Y2ZjdmNyIgc3Ryb2tlPSIjZGRkIi8+PGNpcmNsZSBjeD0iMjAiIGN5PSIxNSIgcj0iNiIgZmlsbD0iIzk5OSIvPjxlbGxpcHNlIGN4PSIyMCIgY3k9IjMzIiByeD0iMTAiIHJ5PSI3IiBmaWxsPSIjOTk5Ii8+PC9zdmc+';
|
||||
},
|
||||
|
||||
formatNewsDate(dateString) {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now - date);
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 1) {
|
||||
return 'Yesterday';
|
||||
} else if (diffDays < 7) {
|
||||
return `${diffDays} days ago`;
|
||||
} else {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
getUserStatusClass(status) {
|
||||
const statusMap = {
|
||||
'active': 'good',
|
||||
'recent': 'good',
|
||||
'inactive': 'warning',
|
||||
'very_inactive': 'critical',
|
||||
'never_logged_in': 'neutral'
|
||||
};
|
||||
return statusMap[status] || 'neutral';
|
||||
},
|
||||
|
||||
getUserStatusLabel(status) {
|
||||
const statusLabels = {
|
||||
'active': 'Active',
|
||||
'recent': 'Recent',
|
||||
'inactive': 'Inactive',
|
||||
'very_inactive': 'Very Inactive',
|
||||
'never_logged_in': 'Never Logged In'
|
||||
};
|
||||
return statusLabels[status] || 'Unknown';
|
||||
},
|
||||
|
||||
getRegistrationLabel(registration) {
|
||||
const labels = {
|
||||
'none': 'Disabled',
|
||||
'user': 'Users Only',
|
||||
'blog': 'Sites Only',
|
||||
'all': 'Users & Sites'
|
||||
};
|
||||
return labels[registration] || 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
window.MSD_Core = MSD_Core;
|
||||
MSD_Core.init();
|
||||
|
||||
$('head').append(`
|
||||
<style>
|
||||
body.modal-open { overflow: hidden; }
|
||||
.msd-refresh-btn.refreshing { opacity: 0.6; pointer-events: none; }
|
||||
.msd-error-state { text-align: center; padding: 20px; color: var(--msd-text-light); }
|
||||
.msd-emoji-icon { font-size: 18px !important; display: inline-block; filter: grayscale(55%); }
|
||||
</style>
|
||||
`);
|
||||
});
|
376
assets/dashboard-modals.js
Normal file
376
assets/dashboard-modals.js
Normal file
|
@ -0,0 +1,376 @@
|
|||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
const MSD_Modals = {
|
||||
init() {
|
||||
this.bindEvents();
|
||||
// 延迟初始化 sortable,等待页面完全加载
|
||||
$(document).ready(() => {
|
||||
setTimeout(() => {
|
||||
this.initSortable();
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
|
||||
bindEvents() {
|
||||
$(document)
|
||||
.on('click', '#msd-add-link', this.addQuickLinkRow.bind(this))
|
||||
.on('click', '.msd-remove-link', this.removeQuickLinkRow.bind(this))
|
||||
.on('click', '#msd-add-news-source', this.addNewsSourceRow.bind(this))
|
||||
.on('click', '.msd-remove-source', this.removeNewsSourceRow.bind(this))
|
||||
.on('click', '.msd-modal-close, .msd-modal', this.handleModalClose.bind(this))
|
||||
.on('click', '.msd-modal-content', function(e) { e.stopPropagation(); })
|
||||
.on('click', '.msd-news-settings button', this.showNewsSourcesModal.bind(this));
|
||||
|
||||
window.MSD = {
|
||||
showQuickLinksModal: this.showQuickLinksModal.bind(this),
|
||||
hideQuickLinksModal: this.hideQuickLinksModal.bind(this),
|
||||
saveQuickLinks: this.saveQuickLinks.bind(this),
|
||||
showNewsSourcesModal: this.showNewsSourcesModal.bind(this),
|
||||
hideNewsSourcesModal: this.hideNewsSourcesModal.bind(this),
|
||||
saveNewsSources: this.saveNewsSources.bind(this),
|
||||
showContactInfoModal: this.showContactInfoModal.bind(this),
|
||||
hideContactInfoModal: this.hideContactInfoModal.bind(this),
|
||||
saveContactInfo: this.saveContactInfo.bind(this),
|
||||
selectQRImage: this.selectQRImage.bind(this),
|
||||
removeQRCode: this.removeQRCode.bind(this),
|
||||
clearNewsCache: this.clearNewsCache.bind(this)
|
||||
};
|
||||
},
|
||||
|
||||
initSortable() {
|
||||
// 使用延迟初始化,确保DOM元素存在
|
||||
$(document).ready(() => {
|
||||
setTimeout(() => {
|
||||
if ($.ui && $.ui.sortable && $('#msd-sortable-links').length && !$('#msd-sortable-links').hasClass('ui-sortable')) {
|
||||
$('#msd-sortable-links').sortable({
|
||||
tolerance: 'pointer',
|
||||
cursor: 'move',
|
||||
placeholder: 'ui-sortable-placeholder',
|
||||
helper: function(e, ui) {
|
||||
ui.addClass('ui-sortable-helper');
|
||||
return ui;
|
||||
},
|
||||
stop: (event, ui) => {
|
||||
this.saveQuickLinksOrder();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
|
||||
saveQuickLinksOrder() {
|
||||
const order = [];
|
||||
$('#msd-sortable-links .msd-quick-link-item').each(function() {
|
||||
const index = $(this).data('index');
|
||||
if (index !== undefined) {
|
||||
order.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
if (order.length > 0 && window.MSD_Core) {
|
||||
window.MSD_Core.makeAjaxRequest('msd_reorder_quick_links', { order },
|
||||
() => {
|
||||
window.MSD_Core.showNotice(msdAjax.strings.save_success, 'success', 2000);
|
||||
},
|
||||
() => {
|
||||
window.MSD_Core.showNotice('Failed to save order', 'error');
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
handleModalClose(e) {
|
||||
if (e.target === e.currentTarget || $(e.target).hasClass('msd-modal-close')) {
|
||||
this.hideQuickLinksModal();
|
||||
this.hideNewsSourcesModal();
|
||||
this.hideContactInfoModal();
|
||||
}
|
||||
},
|
||||
|
||||
showQuickLinksModal() {
|
||||
$('#msd-quick-links-modal').fadeIn(200);
|
||||
$('body').addClass('modal-open');
|
||||
},
|
||||
|
||||
hideQuickLinksModal() {
|
||||
$('#msd-quick-links-modal').fadeOut(200);
|
||||
$('body').removeClass('modal-open');
|
||||
},
|
||||
|
||||
showNewsSourcesModal() {
|
||||
$('#msd-news-sources-modal').fadeIn(200);
|
||||
$('body').addClass('modal-open');
|
||||
},
|
||||
|
||||
hideNewsSourcesModal() {
|
||||
$('#msd-news-sources-modal').fadeOut(200);
|
||||
$('body').removeClass('modal-open');
|
||||
},
|
||||
|
||||
showContactInfoModal() {
|
||||
$('#msd-contact-info-modal').fadeIn(200);
|
||||
$('body').addClass('modal-open');
|
||||
},
|
||||
|
||||
hideContactInfoModal() {
|
||||
$('#msd-contact-info-modal').fadeOut(200);
|
||||
$('body').removeClass('modal-open');
|
||||
},
|
||||
|
||||
addQuickLinkRow() {
|
||||
const html = `
|
||||
<div class="msd-link-item">
|
||||
<div class="msd-link-row">
|
||||
<input type="text" placeholder="Link Title" class="msd-link-title" required>
|
||||
<input type="url" placeholder="https://example.com" class="msd-link-url" required>
|
||||
</div>
|
||||
<div class="msd-link-options">
|
||||
<input type="text" placeholder="dashicons-admin-home or 🏠" class="msd-link-icon">
|
||||
<label class="msd-checkbox-label">
|
||||
<input type="checkbox" class="msd-link-newtab">
|
||||
Open in new tab
|
||||
</label>
|
||||
<button type="button" class="msd-remove-link">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$('#msd-quick-links-editor').append(html);
|
||||
},
|
||||
|
||||
removeQuickLinkRow(e) {
|
||||
$(e.currentTarget).closest('.msd-link-item').fadeOut(200, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
},
|
||||
|
||||
addNewsSourceRow() {
|
||||
const html = `
|
||||
<div class="msd-news-source-item">
|
||||
<div class="msd-source-row">
|
||||
<input type="text" placeholder="Source Name" class="msd-news-name" required>
|
||||
<input type="url" placeholder="RSS Feed URL" class="msd-news-url" required>
|
||||
</div>
|
||||
<div class="msd-source-options">
|
||||
<label class="msd-checkbox-label">
|
||||
<input type="checkbox" class="msd-news-enabled" checked>
|
||||
Enabled
|
||||
</label>
|
||||
<button type="button" class="msd-remove-source">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$('#msd-news-sources-editor').append(html);
|
||||
},
|
||||
|
||||
removeNewsSourceRow(e) {
|
||||
$(e.currentTarget).closest('.msd-news-source-item').fadeOut(200, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
},
|
||||
|
||||
saveQuickLinks() {
|
||||
const links = [];
|
||||
let hasErrors = false;
|
||||
|
||||
// Clear previous errors
|
||||
$('.msd-link-item').removeClass('error');
|
||||
$('.msd-link-url').removeClass('error');
|
||||
|
||||
$('.msd-link-item').each(function() {
|
||||
const $item = $(this);
|
||||
const title = $item.find('.msd-link-title').val().trim();
|
||||
const url = $item.find('.msd-link-url').val().trim();
|
||||
const icon = $item.find('.msd-link-icon').val().trim();
|
||||
const newTab = $item.find('.msd-link-newtab').is(':checked');
|
||||
|
||||
if (title && url) {
|
||||
if (!MSD_Modals.isValidUrl(url)) {
|
||||
$item.find('.msd-link-url').addClass('error');
|
||||
hasErrors = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$item.find('.msd-link-url').removeClass('error');
|
||||
links.push({ title, url, icon, new_tab: newTab });
|
||||
} else if (title || url) {
|
||||
$item.addClass('error');
|
||||
hasErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.showNotice('Please fill in all required fields correctly', 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the save button to prevent double submission
|
||||
const $saveBtn = $('.msd-modal-footer .button-primary');
|
||||
$saveBtn.prop('disabled', true).text('Saving...');
|
||||
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.makeAjaxRequest('msd_save_quick_links', { links }, (response) => {
|
||||
window.MSD_Core.showNotice(response.data.message, 'success');
|
||||
this.hideQuickLinksModal();
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
}, (error) => {
|
||||
window.MSD_Core.showNotice('Failed to save quick links', 'error');
|
||||
$saveBtn.prop('disabled', false).text('Save Links');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
saveNewsSources() {
|
||||
const sources = [];
|
||||
let hasErrors = false;
|
||||
|
||||
// Clear previous errors
|
||||
$('.msd-news-source-item').removeClass('error');
|
||||
$('.msd-news-url').removeClass('error');
|
||||
|
||||
$('.msd-news-source-item').each(function() {
|
||||
const $item = $(this);
|
||||
const name = $item.find('.msd-news-name').val().trim();
|
||||
const url = $item.find('.msd-news-url').val().trim();
|
||||
const enabled = $item.find('.msd-news-enabled').is(':checked');
|
||||
|
||||
if (name && url) {
|
||||
if (!MSD_Modals.isValidUrl(url)) {
|
||||
$item.find('.msd-news-url').addClass('error');
|
||||
hasErrors = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$item.find('.msd-news-url').removeClass('error');
|
||||
sources.push({ name, url, enabled });
|
||||
} else if (name || url) {
|
||||
$item.addClass('error');
|
||||
hasErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.showNotice('Please fill in all required fields correctly', 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the save button to prevent double submission
|
||||
const $saveBtn = $('#msd-news-sources-modal .button-primary');
|
||||
$saveBtn.prop('disabled', true).text('Saving...');
|
||||
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.makeAjaxRequest('msd_save_news_sources', { sources }, (response) => {
|
||||
window.MSD_Core.showNotice(response.data.message, 'success');
|
||||
this.hideNewsSourcesModal();
|
||||
if (window.MSD_Widgets) {
|
||||
window.MSD_Widgets.loadWidget('custom_news');
|
||||
}
|
||||
}, (error) => {
|
||||
window.MSD_Core.showNotice('Failed to save news sources', 'error');
|
||||
$saveBtn.prop('disabled', false).text('Save News Sources');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
saveContactInfo() {
|
||||
const contactInfo = {
|
||||
name: $('#msd-contact-name').val().trim(),
|
||||
email: $('#msd-contact-email').val().trim(),
|
||||
phone: $('#msd-contact-phone').val().trim(),
|
||||
website: $('#msd-contact-website').val().trim(),
|
||||
description: $('#msd-contact-description').val().trim(),
|
||||
qq: $('#msd-contact-qq').val().trim(),
|
||||
wechat: $('#msd-contact-wechat').val().trim(),
|
||||
whatsapp: $('#msd-contact-whatsapp').val().trim(),
|
||||
telegram: $('#msd-contact-telegram').val().trim(),
|
||||
qr_code: $('#msd-contact-qr-code').val().trim()
|
||||
};
|
||||
|
||||
if (!contactInfo.name || !contactInfo.email) {
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.showNotice('Name and email are required', 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the save button to prevent double submission
|
||||
const $saveBtn = $('#msd-contact-info-modal .button-primary');
|
||||
$saveBtn.prop('disabled', true).text('Saving...');
|
||||
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.makeAjaxRequest('msd_save_contact_info', contactInfo, (response) => {
|
||||
window.MSD_Core.showNotice(response.data.message, 'success');
|
||||
this.hideContactInfoModal();
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
}, (error) => {
|
||||
window.MSD_Core.showNotice('Failed to save contact information', 'error');
|
||||
$saveBtn.prop('disabled', false).text('Save Contact Info');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
selectQRImage() {
|
||||
if (wp && wp.media) {
|
||||
const frame = wp.media({
|
||||
title: 'Select QR Code Image',
|
||||
button: { text: 'Use Image' },
|
||||
multiple: false
|
||||
});
|
||||
|
||||
frame.on('select', function() {
|
||||
const attachment = frame.state().get('selection').first().toJSON();
|
||||
$('#msd-contact-qr-code').val(attachment.url);
|
||||
$('#msd-qr-preview img').attr('src', attachment.url);
|
||||
$('#msd-qr-preview').show();
|
||||
});
|
||||
|
||||
frame.open();
|
||||
} else {
|
||||
const url = prompt('Enter QR code image URL:');
|
||||
if (url) {
|
||||
$('#msd-contact-qr-code').val(url);
|
||||
$('#msd-qr-preview img').attr('src', url);
|
||||
$('#msd-qr-preview').show();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeQRCode() {
|
||||
$('#msd-contact-qr-code').val('');
|
||||
$('#msd-qr-preview').hide();
|
||||
},
|
||||
|
||||
clearNewsCache() {
|
||||
if (window.MSD_Core) {
|
||||
window.MSD_Core.makeAjaxRequest('msd_refresh_widget_data', { widget: 'custom_news' }, (response) => {
|
||||
window.MSD_Core.showNotice('News cache cleared successfully', 'success');
|
||||
if (window.MSD_Widgets) {
|
||||
window.MSD_Widgets.loadWidget('custom_news');
|
||||
}
|
||||
}, (error) => {
|
||||
window.MSD_Core.showNotice('Failed to clear news cache', 'error');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isValidUrl(string) {
|
||||
if (window.MSD_Core && window.MSD_Core.isValidUrl) {
|
||||
return window.MSD_Core.isValidUrl(string);
|
||||
}
|
||||
try {
|
||||
const url = new URL(string);
|
||||
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.MSD_Modals = MSD_Modals;
|
||||
MSD_Modals.init();
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -1634,8 +1634,8 @@ div#network_dashboard_right_now .inside {
|
|||
|
||||
.msd-widget-toggle .description {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--msd-text-light);
|
||||
font-size: 11px;
|
||||
color: #8c8c8c;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
@ -1869,3 +1869,33 @@ body.modal-open {
|
|||
display: inline-block;
|
||||
filter: grayscale(55%);
|
||||
}
|
||||
|
||||
.msd-update-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
margin-bottom: var(--msd-spacing);
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: var(--msd-radius-small);
|
||||
color: #856404;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.msd-update-notice .dashicons {
|
||||
color: #dba617;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.msd-update-link {
|
||||
color: var(--msd-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.msd-update-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
333
includes/class-admin-interface.php
Normal file
333
includes/class-admin-interface.php
Normal file
|
@ -0,0 +1,333 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Admin_Interface {
|
||||
|
||||
public function __construct() {
|
||||
add_action('admin_footer', [$this, 'render_modals']);
|
||||
}
|
||||
|
||||
public function render_modals() {
|
||||
$screen = get_current_screen();
|
||||
if ($screen && $screen->id === 'dashboard-network') {
|
||||
include_once WP_MSD_PLUGIN_DIR . 'templates/admin-modals.php';
|
||||
}
|
||||
}
|
||||
|
||||
public function add_network_widgets() {
|
||||
$plugin_core = WP_MSD_Plugin_Core::get_instance();
|
||||
$enabled_widgets = $plugin_core->get_enabled_widgets();
|
||||
|
||||
$widgets = [
|
||||
'msd_network_overview' => ['Network Overview', 'render_network_overview_widget'],
|
||||
'msd_quick_site_management' => ['Quick Site Management', 'render_quick_site_widget'],
|
||||
'msd_storage_performance' => ['Storage Usage', 'render_storage_performance_widget'],
|
||||
'msd_server_info' => ['Server Information', 'render_server_info_widget'],
|
||||
'msd_quick_links' => ['Quick Links', 'render_quick_links_widget'],
|
||||
'msd_version_info' => ['Version Information', 'render_version_info_widget'],
|
||||
'msd_custom_news' => ['Network News', 'render_custom_news_widget'],
|
||||
'msd_user_management' => ['User Management', 'render_user_management_widget'],
|
||||
'msd_contact_info' => ['Contact Information', 'render_contact_info_widget'],
|
||||
'msd_last_edits' => ['Recent Network Activity', 'render_last_edits_widget'],
|
||||
'msd_todo_widget' => ['Todo List', 'render_todo_widget']
|
||||
];
|
||||
|
||||
foreach ($widgets as $widget_id => $widget_data) {
|
||||
if (!empty($enabled_widgets[$widget_id])) {
|
||||
wp_add_dashboard_widget(
|
||||
$widget_id,
|
||||
$widget_data[0],
|
||||
[$this, $widget_data[1]]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render_network_overview_widget() {
|
||||
echo '<div id="msd-network-overview" class="msd-widget-content" data-widget="network_overview">';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_quick_site_widget() {
|
||||
echo '<div id="msd-quick-sites" class="msd-widget-content" data-widget="site_list">';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_storage_performance_widget() {
|
||||
echo '<div id="msd-storage-performance" class="msd-widget-content" data-widget="storage_data">';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_server_info_widget() {
|
||||
echo '<div id="msd-server-info" class="msd-widget-content" data-widget="server_info">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="server_info">↻</button>';
|
||||
$this->render_server_info_content();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function render_server_info_content() {
|
||||
global $wpdb, $wp_version;
|
||||
|
||||
$data = [
|
||||
'PHP Version' => phpversion(),
|
||||
'MySQL Version' => $wpdb->db_version(),
|
||||
'Server Software' => $_SERVER["SERVER_SOFTWARE"] ?? 'Unknown',
|
||||
'Server Time' => current_time('Y-m-d H:i:s'),
|
||||
'Memory Limit' => ini_get('memory_limit'),
|
||||
'Max Upload Size' => size_format(wp_max_upload_size()),
|
||||
];
|
||||
|
||||
$icons = [
|
||||
'PHP Version' => 'dashicons-editor-code',
|
||||
'MySQL Version' => 'dashicons-database',
|
||||
'Server Software' => 'dashicons-admin-tools',
|
||||
'Server Time' => 'dashicons-clock',
|
||||
'Memory Limit' => 'dashicons-performance',
|
||||
'Max Upload Size' => 'dashicons-upload',
|
||||
];
|
||||
|
||||
echo '<div class="msd-server-specs">';
|
||||
foreach ($data as $label => $value) {
|
||||
$icon = $icons[$label] ?? 'dashicons-info';
|
||||
echo '<div class="msd-spec-item">';
|
||||
echo '<span class="msd-spec-icon dashicons ' . esc_attr($icon) . '"></span>';
|
||||
echo '<span class="msd-spec-label">' . esc_html($label) . '</span>';
|
||||
echo '<span class="msd-spec-value">' . esc_html($value) . '</span>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_version_info_widget() {
|
||||
echo '<div id="msd-version-info" class="msd-widget-content" data-widget="version_info">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="version_info">↻</button>';
|
||||
$this->render_version_info_content();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function render_version_info_content() {
|
||||
$plugin_data = get_plugin_data(WP_MSD_PLUGIN_DIR . 'wp-multisite-dashboard.php');
|
||||
global $wpdb;
|
||||
|
||||
echo '<div class="msd-version-header">';
|
||||
echo '<h3><span class="dashicons dashicons-admin-multisite"></span> ' . esc_html($plugin_data['Name']) . '</h3>';
|
||||
echo '<div class="msd-version-actions">';
|
||||
echo '<a href="https://wpmultisite.com/document/wp-multisite-dashboard" target="_blank" class="msd-help-btn msd-help-docs" title="Documentation">';
|
||||
echo '<span class="dashicons dashicons-book"></span>';
|
||||
echo '</a>';
|
||||
echo '<a href="https://wpmultisite.com/support/" target="_blank" class="msd-help-btn msd-help-support" title="Support">';
|
||||
echo '<span class="dashicons dashicons-admin-comments"></span>';
|
||||
echo '</a>';
|
||||
echo '<a href="https://github.com/wpmultisite/wp-multisite-dashboard" target="_blank" class="msd-help-btn msd-help-github" title="GitHub">';
|
||||
echo '<span class="dashicons dashicons-admin-links"></span>';
|
||||
echo '</a>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
$plugin_core = WP_MSD_Plugin_Core::get_instance();
|
||||
$update_checker = $plugin_core->get_update_checker();
|
||||
$update_available = false;
|
||||
|
||||
if ($update_checker) {
|
||||
$update = $update_checker->checkForUpdates();
|
||||
if ($update && version_compare($update->version, WP_MSD_VERSION, '>')) {
|
||||
$update_available = [
|
||||
'version' => $update->version,
|
||||
'details_url' => $update->details_url ?? '#'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($update_available) {
|
||||
echo '<div class="msd-update-notice">';
|
||||
echo '<span class="dashicons dashicons-update"></span>';
|
||||
echo '<span>' . sprintf(__('Version %s available! ', 'wp-multisite-dashboard'), esc_html($update_available['version'])) . '</span>';
|
||||
echo '<a href="' . esc_url($update_available['details_url']) . '" target="_blank" class="msd-update-link">' . __('View Details', 'wp-multisite-dashboard') . '</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '<div class="msd-version-specs">';
|
||||
|
||||
echo '<div class="msd-version-item">';
|
||||
echo '<span class="msd-version-icon dashicons dashicons-tag"></span>';
|
||||
echo '<span class="msd-version-label">Plugin Version</span>';
|
||||
echo '<span class="msd-version-value">' . esc_html($plugin_data['Version']) . '</span>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-version-item">';
|
||||
echo '<span class="msd-version-icon dashicons dashicons-admin-links"></span>';
|
||||
echo '<span class="msd-version-label">Author URI</span>';
|
||||
echo '<span class="msd-version-value"><a href="' . esc_url($plugin_data['AuthorURI']) . '" target="_blank">' . esc_html($plugin_data['AuthorURI']) . '</a></span>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-version-item">';
|
||||
echo '<span class="msd-version-icon dashicons dashicons-editor-code"></span>';
|
||||
echo '<span class="msd-version-label">Required PHP</span>';
|
||||
echo '<span class="msd-version-value msd-status-good">' . esc_html($plugin_data['RequiresPHP']) . '</span>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-version-item">';
|
||||
echo '<span class="msd-version-icon dashicons dashicons-database"></span>';
|
||||
echo '<span class="msd-version-label">Database Tables</span>';
|
||||
$activity_table = $wpdb->base_prefix . 'msd_activity_log';
|
||||
$activity_exists = $wpdb->get_var("SHOW TABLES LIKE '{$activity_table}'") === $activity_table;
|
||||
if ($activity_exists) {
|
||||
echo '<span class="msd-version-value msd-db-status-good">✓ Activity table created</span>';
|
||||
} else {
|
||||
echo '<span class="msd-version-value msd-db-status-warning">⚠ Activity table missing</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_custom_news_widget() {
|
||||
echo '<div id="msd-custom-news" class="msd-widget-content" data-widget="custom_news">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="custom_news">↻</button>';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_user_management_widget() {
|
||||
echo '<div id="msd-user-management" class="msd-widget-content" data-widget="user_management">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="user_management">↻</button>';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_contact_info_widget() {
|
||||
echo '<div id="msd-contact-info" class="msd-widget-content" data-widget="contact_info">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="contact_info">↻</button>';
|
||||
$this->render_contact_info_content();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function render_contact_info_content() {
|
||||
$contact_info = get_site_option('msd_contact_info', [
|
||||
'name' => get_network_option(null, 'site_name'),
|
||||
'email' => get_network_option(null, 'admin_email'),
|
||||
'phone' => '',
|
||||
'website' => network_home_url(),
|
||||
'description' => 'Network Administrator Contact Information',
|
||||
'qq' => '',
|
||||
'wechat' => '',
|
||||
'whatsapp' => '',
|
||||
'telegram' => '',
|
||||
'qr_code' => ''
|
||||
]);
|
||||
|
||||
echo '<div class="msd-contact-card">';
|
||||
echo '<div class="msd-contact-header">';
|
||||
echo '<h3><span class="dashicons dashicons-coffee"></span> ' . esc_html($contact_info['name']) . '</h3>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-contact-details">';
|
||||
|
||||
if (!empty($contact_info['description'])) {
|
||||
echo '<p class="msd-contact-description">' . esc_html($contact_info['description']) . '</p>';
|
||||
}
|
||||
|
||||
echo '<div class="msd-contact-item">';
|
||||
echo '<span class="dashicons dashicons-email"></span>';
|
||||
echo '<a href="mailto:' . esc_attr($contact_info['email']) . '">' . esc_html($contact_info['email']) . '</a>';
|
||||
echo '</div>';
|
||||
|
||||
if (!empty($contact_info['phone'])) {
|
||||
echo '<div class="msd-contact-item">';
|
||||
echo '<span class="dashicons dashicons-phone"></span>';
|
||||
echo '<a href="tel:' . esc_attr($contact_info['phone']) . '">' . esc_html($contact_info['phone']) . '</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '<div class="msd-contact-item">';
|
||||
echo '<span class="dashicons dashicons-admin-links"></span>';
|
||||
echo '<a href="' . esc_url($contact_info['website']) . '" target="_blank">' . esc_html($contact_info['website']) . '</a>';
|
||||
echo '</div>';
|
||||
|
||||
$im_fields = [
|
||||
'qq' => ['QQ', 'dashicons-admin-users'],
|
||||
'wechat' => ['WeChat', 'dashicons-format-chat'],
|
||||
'whatsapp' => ['WhatsApp', 'dashicons-smartphone'],
|
||||
'telegram' => ['Telegram', 'dashicons-email-alt']
|
||||
];
|
||||
|
||||
foreach ($im_fields as $field => $data) {
|
||||
if (!empty($contact_info[$field])) {
|
||||
echo '<div class="msd-contact-item">';
|
||||
echo '<span class="dashicons ' . $data[1] . '"></span>';
|
||||
echo '<span>' . $data[0] . ': ' . esc_html($contact_info[$field]) . '</span>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($contact_info['qr_code'])) {
|
||||
echo '<div class="msd-contact-qr">';
|
||||
echo '<img src="' . esc_url($contact_info['qr_code']) . '" alt="QR Code" class="msd-qr-image">';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-contact-actions">';
|
||||
echo '<button class="button button-small" onclick="MSD.showContactInfoModal()">' . __('Edit Contact Info', 'wp-multisite-dashboard') . '</button>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_last_edits_widget() {
|
||||
echo '<div id="msd-last-edits" class="msd-widget-content" data-widget="last_edits">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="last_edits">↻</button>';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_quick_links_widget() {
|
||||
$quick_links = get_site_option('msd_quick_links', []);
|
||||
|
||||
echo '<div id="msd-quick-links" class="msd-widget-content">';
|
||||
|
||||
if (empty($quick_links)) {
|
||||
echo '<div class="msd-empty-state">';
|
||||
echo '<p>' . __('No quick links configured.', 'wp-multisite-dashboard') . '</p>';
|
||||
echo '<button class="button button-primary button-small" onclick="MSD.showQuickLinksModal()">' . __('Add Links', 'wp-multisite-dashboard') . '</button>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<div class="msd-quick-links-grid" id="msd-sortable-links">';
|
||||
foreach ($quick_links as $index => $link) {
|
||||
$target = !empty($link['new_tab']) ? '_blank' : '_self';
|
||||
echo '<a href="' . esc_url($link['url']) . '" target="' . $target . '" class="msd-quick-link-item" data-index="' . $index . '">';
|
||||
|
||||
if (!empty($link['icon'])) {
|
||||
if (strpos($link['icon'], 'dashicons-') === 0) {
|
||||
echo '<span class="dashicons ' . esc_attr($link['icon']) . '"></span>';
|
||||
} elseif (mb_strlen($link['icon']) <= 4 && preg_match('/[\x{1F000}-\x{1F9FF}]/u', $link['icon'])) {
|
||||
echo '<span class="msd-emoji-icon">' . esc_html($link['icon']) . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
echo '<span>' . esc_html($link['title']) . '</span>';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<div class="msd-widget-footer">';
|
||||
echo '<button class="button button-secondary button-small" onclick="MSD.showQuickLinksModal()">' . __('Edit Links', 'wp-multisite-dashboard') . '</button>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public function render_todo_widget() {
|
||||
echo '<div id="msd-todo-widget" class="msd-widget-content" data-widget="todo_items">';
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="todo_items">↻</button>';
|
||||
echo '<div class="msd-loading"><span class="msd-spinner"></span>' . __('Loading...', 'wp-multisite-dashboard') . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
744
includes/class-ajax-handler.php
Normal file
744
includes/class-ajax-handler.php
Normal file
|
@ -0,0 +1,744 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Ajax_Handler {
|
||||
|
||||
public function __construct() {
|
||||
$this->register_ajax_actions();
|
||||
}
|
||||
|
||||
private function register_ajax_actions() {
|
||||
$ajax_actions = [
|
||||
'msd_get_network_overview',
|
||||
'msd_get_site_list',
|
||||
'msd_get_storage_data',
|
||||
'msd_get_server_info',
|
||||
'msd_get_version_info',
|
||||
'msd_get_custom_news',
|
||||
'msd_get_network_settings',
|
||||
'msd_get_user_management',
|
||||
'msd_get_last_edits',
|
||||
'msd_get_todo_items',
|
||||
'msd_save_news_sources',
|
||||
'msd_save_quick_links',
|
||||
'msd_save_contact_info',
|
||||
'msd_save_todo_item',
|
||||
'msd_update_todo_item',
|
||||
'msd_delete_todo_item',
|
||||
'msd_toggle_todo_complete',
|
||||
'msd_reorder_quick_links',
|
||||
'msd_toggle_widget',
|
||||
'msd_refresh_widget_data',
|
||||
'msd_clear_cache',
|
||||
'msd_manage_user_action',
|
||||
'msd_check_plugin_update',
|
||||
'msd_clear_widget_cache'
|
||||
];
|
||||
|
||||
foreach ($ajax_actions as $action) {
|
||||
add_action("wp_ajax_{$action}", [$this, str_replace('msd_', '', $action)]);
|
||||
}
|
||||
}
|
||||
|
||||
public function get_network_overview() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$overview = [
|
||||
'total_posts' => $network_data->get_total_posts(),
|
||||
'total_pages' => $network_data->get_total_pages(),
|
||||
'multisite_config' => $network_data->get_multisite_configuration(),
|
||||
'network_info' => $network_data->get_network_information(),
|
||||
'critical_alerts' => 0,
|
||||
'network_status' => $network_data->get_overall_network_status(),
|
||||
'last_updated' => current_time('mysql')
|
||||
];
|
||||
|
||||
wp_send_json_success($overview);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load network overview', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_site_list() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$sites = $network_data->get_recent_active_sites(10);
|
||||
wp_send_json_success($sites);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load sites', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_storage_data() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$storage_data = $network_data->get_storage_usage_data(5);
|
||||
wp_send_json_success($storage_data);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load storage data', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_server_info() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb, $wp_version;
|
||||
|
||||
$server_info = [
|
||||
'wordpress_version' => $wp_version,
|
||||
'php_version' => phpversion(),
|
||||
'mysql_version' => $wpdb->db_version(),
|
||||
'server_software' => $_SERVER["SERVER_SOFTWARE"] ?? 'Unknown',
|
||||
'server_time' => current_time('Y-m-d H:i:s'),
|
||||
'php_memory_limit' => ini_get('memory_limit'),
|
||||
'max_upload_size' => size_format(wp_max_upload_size()),
|
||||
'active_plugins' => count(get_option('active_plugins', [])),
|
||||
'total_users' => count_users()['total_users'],
|
||||
'last_updated' => current_time('mysql')
|
||||
];
|
||||
|
||||
wp_send_json_success($server_info);
|
||||
}
|
||||
|
||||
public function get_version_info() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin_data = get_plugin_data(WP_MSD_PLUGIN_DIR . 'wp-multisite-dashboard.php');
|
||||
global $wpdb;
|
||||
|
||||
$activity_table = $wpdb->base_prefix . 'msd_activity_log';
|
||||
$activity_exists = $wpdb->get_var("SHOW TABLES LIKE '{$activity_table}'") === $activity_table;
|
||||
|
||||
$plugin_core = WP_MSD_Plugin_Core::get_instance();
|
||||
$update_checker = $plugin_core->get_update_checker();
|
||||
$update_available = false;
|
||||
|
||||
if ($update_checker) {
|
||||
$update = $update_checker->checkForUpdates();
|
||||
if ($update && version_compare($update->version, WP_MSD_VERSION, '>')) {
|
||||
$update_available = [
|
||||
'version' => $update->version,
|
||||
'details_url' => $update->details_url ?? '#'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$version_info = [
|
||||
'plugin_name' => $plugin_data['Name'],
|
||||
'plugin_version' => $plugin_data['Version'],
|
||||
'plugin_author' => $plugin_data['Author'],
|
||||
'plugin_uri' => $plugin_data['AuthorURI'],
|
||||
'text_domain' => $plugin_data['TextDomain'],
|
||||
'required_php' => $plugin_data['RequiresPHP'],
|
||||
'description' => strip_tags($plugin_data['Description']),
|
||||
'database_status' => $activity_exists ? 'active' : 'missing',
|
||||
'database_message' => $activity_exists ? 'Activity table created' : 'Activity table missing',
|
||||
'update_available' => $update_available ? true : false,
|
||||
'update_info' => $update_available ?: null,
|
||||
'last_updated' => current_time('mysql')
|
||||
];
|
||||
|
||||
wp_send_json_success($version_info);
|
||||
}
|
||||
|
||||
public function get_custom_news() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$news_sources = get_site_option('msd_news_sources', []);
|
||||
$news_items = [];
|
||||
|
||||
foreach ($news_sources as $source) {
|
||||
if (!$source['enabled']) continue;
|
||||
|
||||
$feed_items = $this->fetch_rss_feed($source['url'], 5);
|
||||
foreach ($feed_items as $item) {
|
||||
$item['source'] = $source['name'];
|
||||
$news_items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
usort($news_items, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
$news_items = array_slice($news_items, 0, 10);
|
||||
|
||||
wp_send_json_success($news_items);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load news', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_network_settings() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$settings_data = $network_data->get_network_settings_overview();
|
||||
wp_send_json_success($settings_data);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load network settings', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_user_management() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user_manager = new WP_MSD_User_Manager();
|
||||
$user_data = $user_manager->get_recent_users_data();
|
||||
wp_send_json_success($user_data);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load user data', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_last_edits() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$last_edits = $network_data->get_recent_network_activity(10);
|
||||
wp_send_json_success($last_edits);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load recent activity', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_todo_items() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$todos = get_user_meta(get_current_user_id(), 'msd_todos', true);
|
||||
if (!is_array($todos)) {
|
||||
$todos = [];
|
||||
}
|
||||
|
||||
foreach ($todos as &$todo) {
|
||||
if (isset($todo['created_at'])) {
|
||||
$todo['created_at_human'] = human_time_diff(strtotime($todo['created_at'])) . ' ago';
|
||||
}
|
||||
if (isset($todo['updated_at'])) {
|
||||
$todo['updated_at_human'] = human_time_diff(strtotime($todo['updated_at'])) . ' ago';
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success($todos);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to load todo items', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function save_news_sources() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sources = [];
|
||||
if (isset($_POST['sources']) && is_array($_POST['sources'])) {
|
||||
foreach ($_POST['sources'] as $source) {
|
||||
if (!empty($source['name']) && !empty($source['url'])) {
|
||||
$sources[] = [
|
||||
'name' => sanitize_text_field($source['name']),
|
||||
'url' => esc_url_raw($source['url']),
|
||||
'enabled' => !empty($source['enabled'])
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_site_option('msd_news_sources', $sources);
|
||||
|
||||
$cache_keys = [];
|
||||
foreach ($sources as $source) {
|
||||
$cache_keys[] = 'msd_rss_' . md5($source['url']);
|
||||
}
|
||||
|
||||
foreach ($cache_keys as $key) {
|
||||
delete_site_transient($key);
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('News sources saved successfully', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
public function save_quick_links() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$links = [];
|
||||
if (isset($_POST['links']) && is_array($_POST['links'])) {
|
||||
foreach ($_POST['links'] as $link) {
|
||||
if (!empty($link['title']) && !empty($link['url'])) {
|
||||
$links[] = [
|
||||
'title' => sanitize_text_field($link['title']),
|
||||
'url' => esc_url_raw($link['url']),
|
||||
'icon' => sanitize_text_field($link['icon']),
|
||||
'new_tab' => !empty($link['new_tab'])
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_site_option('msd_quick_links', $links);
|
||||
wp_send_json_success(['message' => __('Quick links saved successfully', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
public function save_contact_info() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contact_info = [
|
||||
'name' => sanitize_text_field($_POST['name'] ?? ''),
|
||||
'email' => sanitize_email($_POST['email'] ?? ''),
|
||||
'phone' => sanitize_text_field($_POST['phone'] ?? ''),
|
||||
'website' => esc_url_raw($_POST['website'] ?? ''),
|
||||
'description' => sanitize_textarea_field($_POST['description'] ?? ''),
|
||||
'qq' => sanitize_text_field($_POST['qq'] ?? ''),
|
||||
'wechat' => sanitize_text_field($_POST['wechat'] ?? ''),
|
||||
'whatsapp' => sanitize_text_field($_POST['whatsapp'] ?? ''),
|
||||
'telegram' => sanitize_text_field($_POST['telegram'] ?? ''),
|
||||
'qr_code' => esc_url_raw($_POST['qr_code'] ?? '')
|
||||
];
|
||||
|
||||
update_site_option('msd_contact_info', $contact_info);
|
||||
wp_send_json_success(['message' => __('Contact information saved successfully', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
public function save_todo_item() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$title = sanitize_text_field($_POST['title'] ?? '');
|
||||
$description = sanitize_textarea_field($_POST['description'] ?? '');
|
||||
|
||||
if (empty($title)) {
|
||||
wp_send_json_error(__('Title is required', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$todos = get_user_meta(get_current_user_id(), 'msd_todos', true);
|
||||
if (!is_array($todos)) {
|
||||
$todos = [];
|
||||
}
|
||||
|
||||
$new_todo = [
|
||||
'id' => uniqid(),
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'completed' => false,
|
||||
'priority' => sanitize_text_field($_POST['priority'] ?? 'medium'),
|
||||
'created_at' => current_time('mysql'),
|
||||
'updated_at' => current_time('mysql')
|
||||
];
|
||||
|
||||
$todos[] = $new_todo;
|
||||
|
||||
$result = update_user_meta(get_current_user_id(), 'msd_todos', $todos);
|
||||
|
||||
if ($result) {
|
||||
wp_send_json_success(['message' => __('Todo item created', 'wp-multisite-dashboard')]);
|
||||
} else {
|
||||
wp_send_json_error(__('Failed to create todo item', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to create todo item', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update_todo_item() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$id = sanitize_text_field($_POST['id'] ?? '');
|
||||
$title = sanitize_text_field($_POST['title'] ?? '');
|
||||
$description = sanitize_textarea_field($_POST['description'] ?? '');
|
||||
|
||||
if (empty($id) || empty($title)) {
|
||||
wp_send_json_error(__('ID and title are required', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$todos = get_user_meta(get_current_user_id(), 'msd_todos', true);
|
||||
if (!is_array($todos)) {
|
||||
wp_send_json_error(__('No todos found', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$updated = false;
|
||||
foreach ($todos as &$todo) {
|
||||
if ($todo['id'] === $id) {
|
||||
$todo['title'] = $title;
|
||||
$todo['description'] = $description;
|
||||
if (isset($_POST['priority'])) {
|
||||
$todo['priority'] = sanitize_text_field($_POST['priority']);
|
||||
}
|
||||
$todo['updated_at'] = current_time('mysql');
|
||||
$updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
$result = update_user_meta(get_current_user_id(), 'msd_todos', $todos);
|
||||
if ($result) {
|
||||
wp_send_json_success(['message' => __('Todo item updated', 'wp-multisite-dashboard')]);
|
||||
} else {
|
||||
wp_send_json_error(__('Failed to update todo item', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} else {
|
||||
wp_send_json_error(__('Todo item not found', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to update todo item', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_todo_item() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$id = sanitize_text_field($_POST['id'] ?? '');
|
||||
|
||||
if (empty($id)) {
|
||||
wp_send_json_error(__('ID is required', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$todos = get_user_meta(get_current_user_id(), 'msd_todos', true);
|
||||
if (!is_array($todos)) {
|
||||
wp_send_json_error(__('No todos found', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$filtered_todos = array_filter($todos, function($todo) use ($id) {
|
||||
return $todo['id'] !== $id;
|
||||
});
|
||||
|
||||
if (count($filtered_todos) < count($todos)) {
|
||||
$result = update_user_meta(get_current_user_id(), 'msd_todos', array_values($filtered_todos));
|
||||
if ($result) {
|
||||
wp_send_json_success(['message' => __('Todo item deleted', 'wp-multisite-dashboard')]);
|
||||
} else {
|
||||
wp_send_json_error(__('Failed to delete todo item', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} else {
|
||||
wp_send_json_error(__('Todo item not found', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to delete todo item', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggle_todo_complete() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$id = sanitize_text_field($_POST['id'] ?? '');
|
||||
|
||||
if (empty($id)) {
|
||||
wp_send_json_error(__('ID is required', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$todos = get_user_meta(get_current_user_id(), 'msd_todos', true);
|
||||
if (!is_array($todos)) {
|
||||
wp_send_json_error(__('No todos found', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$updated = false;
|
||||
foreach ($todos as &$todo) {
|
||||
if ($todo['id'] === $id) {
|
||||
$todo['completed'] = !$todo['completed'];
|
||||
$todo['updated_at'] = current_time('mysql');
|
||||
$updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
$result = update_user_meta(get_current_user_id(), 'msd_todos', $todos);
|
||||
if ($result) {
|
||||
wp_send_json_success(['message' => __('Todo status updated', 'wp-multisite-dashboard')]);
|
||||
} else {
|
||||
wp_send_json_error(__('Failed to update todo status', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} else {
|
||||
wp_send_json_error(__('Todo item not found', 'wp-multisite-dashboard'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to update todo status', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function reorder_quick_links() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order = $_POST['order'] ?? [];
|
||||
if (!is_array($order)) {
|
||||
wp_send_json_error(__('Invalid order data', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$current_links = get_site_option('msd_quick_links', []);
|
||||
$reordered_links = [];
|
||||
|
||||
foreach ($order as $index) {
|
||||
$index = intval($index);
|
||||
if (isset($current_links[$index])) {
|
||||
$reordered_links[] = $current_links[$index];
|
||||
}
|
||||
}
|
||||
|
||||
update_site_option('msd_quick_links', $reordered_links);
|
||||
wp_send_json_success(['message' => __('Links reordered successfully', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
public function toggle_widget() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$widget_id = sanitize_text_field($_POST['widget_id'] ?? '');
|
||||
$enabled = !empty($_POST['enabled']);
|
||||
|
||||
if (empty($widget_id)) {
|
||||
wp_send_json_error(__('Invalid widget ID', 'wp-multisite-dashboard'));
|
||||
}
|
||||
|
||||
$enabled_widgets = get_site_option('msd_enabled_widgets', []);
|
||||
$enabled_widgets[$widget_id] = $enabled ? 1 : 0;
|
||||
update_site_option('msd_enabled_widgets', $enabled_widgets);
|
||||
|
||||
wp_send_json_success(['message' => __('Widget settings updated', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
public function refresh_widget_data() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$widget = sanitize_text_field($_POST['widget'] ?? '');
|
||||
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->clear_widget_cache($widget);
|
||||
|
||||
wp_send_json_success(['message' => __('Cache cleared', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
public function clear_cache() {
|
||||
if (!current_user_can('manage_network')) {
|
||||
wp_send_json_error(__('Insufficient permissions', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce = $_POST['nonce'] ?? '';
|
||||
if (!wp_verify_nonce($nonce, 'msd_ajax_nonce') && !wp_verify_nonce($nonce, 'msd_clear_cache')) {
|
||||
wp_send_json_error(__('Invalid nonce', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_type = sanitize_text_field($_POST['cache_type'] ?? 'all');
|
||||
|
||||
try {
|
||||
switch ($cache_type) {
|
||||
case 'network':
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->clear_all_caches();
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
default:
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->clear_all_caches();
|
||||
wp_cache_flush();
|
||||
break;
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Cache cleared successfully', 'wp-multisite-dashboard')]);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to clear cache', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function manage_user_action() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$action = sanitize_text_field($_POST['user_action'] ?? '');
|
||||
$user_id = intval($_POST['user_id'] ?? 0);
|
||||
$additional_data = $_POST['additional_data'] ?? [];
|
||||
|
||||
$user_manager = new WP_MSD_User_Manager();
|
||||
$result = $user_manager->perform_single_user_action($action, $user_id, $additional_data);
|
||||
|
||||
if ($result['success']) {
|
||||
wp_send_json_success($result);
|
||||
} else {
|
||||
wp_send_json_error($result['message']);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(__('Failed to perform user action', 'wp-multisite-dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
public function check_plugin_update() {
|
||||
if (!$this->verify_ajax_request()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin_core = WP_MSD_Plugin_Core::get_instance();
|
||||
$update_checker = $plugin_core->get_update_checker();
|
||||
|
||||
if (!$update_checker) {
|
||||
wp_send_json_success(['message' => __('No updates available', 'wp-multisite-dashboard')]);
|
||||
return;
|
||||
}
|
||||
|
||||
$update = $update_checker->checkForUpdates();
|
||||
|
||||
if ($update && version_compare($update->version, WP_MSD_VERSION, '>')) {
|
||||
wp_send_json_success([
|
||||
'version' => $update->version,
|
||||
'details_url' => $update->details_url ?? '#'
|
||||
]);
|
||||
} else {
|
||||
wp_send_json_success(['message' => __('No updates available', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
}
|
||||
|
||||
public function clear_widget_cache() {
|
||||
if (!current_user_can('manage_network')) {
|
||||
wp_send_json_error(__('Insufficient permissions', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce = $_POST['nonce'] ?? '';
|
||||
if (!wp_verify_nonce($nonce, 'msd_ajax_nonce') && !wp_verify_nonce($nonce, 'msd_clear_cache')) {
|
||||
wp_send_json_error(__('Invalid nonce', 'wp-multisite-dashboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
delete_site_transient('msd_detected_widgets');
|
||||
|
||||
wp_send_json_success(['message' => __('Widget cache cleared successfully', 'wp-multisite-dashboard')]);
|
||||
}
|
||||
|
||||
private function verify_ajax_request() {
|
||||
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'msd_ajax_nonce')) {
|
||||
wp_send_json_error(__('Invalid nonce', 'wp-multisite-dashboard'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!current_user_can('manage_network')) {
|
||||
wp_send_json_error(__('Insufficient permissions', 'wp-multisite-dashboard'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function fetch_rss_feed($url, $limit = 5) {
|
||||
$cache_key = 'msd_rss_' . md5($url);
|
||||
$cached = get_site_transient($cache_key);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 15,
|
||||
'headers' => [
|
||||
'User-Agent' => 'WP-Multisite-Dashboard/' . WP_MSD_VERSION
|
||||
]
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$feed_items = [];
|
||||
|
||||
try {
|
||||
$xml = simplexml_load_string($body);
|
||||
if ($xml === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = $xml->channel->item ?? $xml->entry ?? [];
|
||||
$count = 0;
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($count >= $limit) break;
|
||||
|
||||
$title = (string)($item->title ?? '');
|
||||
$link = (string)($item->link ?? $item->link['href'] ?? '');
|
||||
$description = (string)($item->description ?? $item->summary ?? '');
|
||||
$date = (string)($item->pubDate ?? $item->updated ?? '');
|
||||
|
||||
if (!empty($title) && !empty($link)) {
|
||||
$description = html_entity_decode($description, ENT_QUOTES, 'UTF-8');
|
||||
$description = wp_trim_words(strip_tags($description), 20);
|
||||
|
||||
$feed_items[] = [
|
||||
'title' => html_entity_decode($title, ENT_QUOTES, 'UTF-8'),
|
||||
'link' => $link,
|
||||
'description' => $description,
|
||||
'date' => $date
|
||||
];
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
set_site_transient($cache_key, $feed_items, 3600);
|
||||
return $feed_items;
|
||||
}
|
||||
}
|
331
includes/class-helpers.php
Normal file
331
includes/class-helpers.php
Normal file
|
@ -0,0 +1,331 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Helpers {
|
||||
|
||||
public static function render_dashboard_notice($message, $type = 'info', $dismissible = true) {
|
||||
$classes = ['notice', "notice-{$type}"];
|
||||
|
||||
if ($dismissible) {
|
||||
$classes[] = 'is-dismissible';
|
||||
}
|
||||
|
||||
$class_string = implode(' ', $classes);
|
||||
|
||||
echo "<div class=\"{$class_string}\">";
|
||||
echo "<p>" . esc_html($message) . "</p>";
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
public static function render_widget_header($title, $widget_id = null, $show_refresh = true) {
|
||||
echo '<div class="msd-widget-header">';
|
||||
|
||||
if ($show_refresh && $widget_id) {
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="' . esc_attr($widget_id) . '">↻</button>';
|
||||
}
|
||||
|
||||
if ($title) {
|
||||
echo '<h3 class="msd-widget-title">' . esc_html($title) . '</h3>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_loading_state($message = null) {
|
||||
$message = $message ?: __('Loading...', 'wp-multisite-dashboard');
|
||||
|
||||
echo '<div class="msd-loading">';
|
||||
echo '<span class="msd-spinner"></span>';
|
||||
echo esc_html($message);
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_empty_state($message, $action_text = null, $action_url = null) {
|
||||
echo '<div class="msd-empty-state">';
|
||||
echo '<p>' . esc_html($message) . '</p>';
|
||||
|
||||
if ($action_text && $action_url) {
|
||||
echo '<a href="' . esc_url($action_url) . '" class="button button-primary">' . esc_html($action_text) . '</a>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_error_state($message, $retry_action = null) {
|
||||
echo '<div class="msd-error-state">';
|
||||
echo '<p>' . esc_html($message) . '</p>';
|
||||
|
||||
if ($retry_action) {
|
||||
echo '<button class="button msd-retry-btn" onclick="' . esc_attr($retry_action) . '">Try Again</button>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function get_priority_badge($priority) {
|
||||
$badges = [
|
||||
'low' => ['Low Priority', 'msd-priority-low'],
|
||||
'medium' => ['Medium Priority', 'msd-priority-medium'],
|
||||
'high' => ['High Priority', 'msd-priority-high']
|
||||
];
|
||||
|
||||
if (!isset($badges[$priority])) {
|
||||
$priority = 'medium';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<span class="msd-priority-badge %s">%s</span>',
|
||||
esc_attr($badges[$priority][1]),
|
||||
esc_html($badges[$priority][0])
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_status_badge($status, $label = null) {
|
||||
$badges = [
|
||||
'active' => ['Active', 'msd-status-good'],
|
||||
'inactive' => ['Inactive', 'msd-status-warning'],
|
||||
'critical' => ['Critical', 'msd-status-critical'],
|
||||
'warning' => ['Warning', 'msd-status-warning'],
|
||||
'good' => ['Good', 'msd-status-good'],
|
||||
'neutral' => ['Neutral', 'msd-status-neutral']
|
||||
];
|
||||
|
||||
if (!isset($badges[$status])) {
|
||||
$status = 'neutral';
|
||||
}
|
||||
|
||||
$display_label = $label ?: $badges[$status][0];
|
||||
|
||||
return sprintf(
|
||||
'<span class="msd-status-badge %s">%s</span>',
|
||||
esc_attr($badges[$status][1]),
|
||||
esc_html($display_label)
|
||||
);
|
||||
}
|
||||
|
||||
public static function format_file_size($bytes) {
|
||||
if ($bytes >= 1073741824) {
|
||||
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||
} elseif ($bytes >= 1048576) {
|
||||
return number_format($bytes / 1048576, 2) . ' MB';
|
||||
} elseif ($bytes >= 1024) {
|
||||
return number_format($bytes / 1024, 2) . ' KB';
|
||||
} else {
|
||||
return $bytes . ' bytes';
|
||||
}
|
||||
}
|
||||
|
||||
public static function format_time_ago($timestamp) {
|
||||
if (empty($timestamp)) {
|
||||
return __('Never', 'wp-multisite-dashboard');
|
||||
}
|
||||
|
||||
if (is_string($timestamp)) {
|
||||
$timestamp = strtotime($timestamp);
|
||||
}
|
||||
|
||||
if (!$timestamp) {
|
||||
return __('Unknown', 'wp-multisite-dashboard');
|
||||
}
|
||||
|
||||
return human_time_diff($timestamp) . ' ago';
|
||||
}
|
||||
|
||||
public static function render_progress_bar($percentage, $label = null, $status = 'good') {
|
||||
$percentage = max(0, min(100, intval($percentage)));
|
||||
$status_class = "msd-progress-{$status}";
|
||||
|
||||
echo '<div class="msd-progress-container">';
|
||||
|
||||
if ($label) {
|
||||
echo '<div class="msd-progress-label">' . esc_html($label) . '</div>';
|
||||
}
|
||||
|
||||
echo '<div class="msd-progress-bar">';
|
||||
echo '<div class="msd-progress-fill ' . esc_attr($status_class) . '" style="width: ' . $percentage . '%"></div>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-progress-text">' . $percentage . '%</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_data_table($headers, $rows, $empty_message = null) {
|
||||
if (empty($rows)) {
|
||||
if ($empty_message) {
|
||||
self::render_empty_state($empty_message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="msd-data-table-wrapper">';
|
||||
echo '<table class="msd-data-table">';
|
||||
|
||||
if (!empty($headers)) {
|
||||
echo '<thead><tr>';
|
||||
foreach ($headers as $header) {
|
||||
echo '<th>' . esc_html($header) . '</th>';
|
||||
}
|
||||
echo '</tr></thead>';
|
||||
}
|
||||
|
||||
echo '<tbody>';
|
||||
foreach ($rows as $row) {
|
||||
echo '<tr>';
|
||||
foreach ($row as $cell) {
|
||||
echo '<td>' . $cell . '</td>';
|
||||
}
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</tbody>';
|
||||
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_action_buttons($actions) {
|
||||
if (empty($actions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="msd-action-buttons">';
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$class = 'button ' . ($action['primary'] ?? false ? 'button-primary' : 'button-secondary');
|
||||
$attributes = '';
|
||||
|
||||
if (!empty($action['attributes'])) {
|
||||
foreach ($action['attributes'] as $attr => $value) {
|
||||
$attributes .= ' ' . esc_attr($attr) . '="' . esc_attr($value) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($action['url'])) {
|
||||
echo '<a href="' . esc_url($action['url']) . '" class="' . esc_attr($class) . '"' . $attributes . '>';
|
||||
echo esc_html($action['text']);
|
||||
echo '</a>';
|
||||
} else {
|
||||
echo '<button type="button" class="' . esc_attr($class) . '"' . $attributes . '>';
|
||||
echo esc_html($action['text']);
|
||||
echo '</button>';
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function sanitize_widget_data($data) {
|
||||
if (is_array($data)) {
|
||||
return array_map([self::class, 'sanitize_widget_data'], $data);
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
return sanitize_text_field($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function validate_nonce($nonce, $action) {
|
||||
return wp_verify_nonce($nonce, $action);
|
||||
}
|
||||
|
||||
public static function can_manage_network() {
|
||||
return current_user_can('manage_network');
|
||||
}
|
||||
|
||||
public static function get_current_screen_id() {
|
||||
$screen = get_current_screen();
|
||||
return $screen ? $screen->id : '';
|
||||
}
|
||||
|
||||
public static function is_network_admin_page($page_slug = null) {
|
||||
if (!is_network_admin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($page_slug) {
|
||||
return isset($_GET['page']) && $_GET['page'] === $page_slug;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function format_number($num) {
|
||||
if ($num >= 1000000) {
|
||||
return number_format($num / 1000000, 1) . 'M';
|
||||
} elseif ($num >= 1000) {
|
||||
return number_format($num / 1000, 1) . 'K';
|
||||
}
|
||||
return number_format($num);
|
||||
}
|
||||
|
||||
public static function truncate_text($text, $length = 50, $suffix = '...') {
|
||||
if (strlen($text) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
return substr($text, 0, $length) . $suffix;
|
||||
}
|
||||
|
||||
public static function escape_js($text) {
|
||||
return esc_js($text);
|
||||
}
|
||||
|
||||
public static function get_default_avatar_url() {
|
||||
return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIj48Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIyMCIgZmlsbD0iI2Y2ZjdmNyIgc3Ryb2tlPSIjZGRkIi8+PGNpcmNsZSBjeD0iMjAiIGN5PSIxNSIgcj0iNiIgZmlsbD0iIzk5OSIvPjxlbGxpcHNlIGN4PSIyMCIgY3k9IjMzIiByeD0iMTAiIHJ5PSI3IiBmaWxsPSIjOTk5Ii8+PC9zdmc+';
|
||||
}
|
||||
|
||||
public static function get_default_favicon_url() {
|
||||
return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj48cmVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9IiNmMGYwZjAiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iMC4zNWVtIj5TPC90ZXh0Pjwvc3ZnPg==';
|
||||
}
|
||||
|
||||
public static function is_valid_url($url) {
|
||||
return filter_var($url, FILTER_VALIDATE_URL) !== false;
|
||||
}
|
||||
|
||||
public static function is_valid_email($email) {
|
||||
return is_email($email);
|
||||
}
|
||||
|
||||
public static function get_storage_status_class($percentage) {
|
||||
if ($percentage > 90) {
|
||||
return 'critical';
|
||||
} elseif ($percentage > 75) {
|
||||
return 'warning';
|
||||
}
|
||||
return 'good';
|
||||
}
|
||||
|
||||
public static function get_user_status_class($status) {
|
||||
$status_map = [
|
||||
'active' => 'good',
|
||||
'recent' => 'good',
|
||||
'inactive' => 'warning',
|
||||
'very_inactive' => 'critical',
|
||||
'never_logged_in' => 'neutral'
|
||||
];
|
||||
return $status_map[$status] ?? 'neutral';
|
||||
}
|
||||
|
||||
public static function get_user_status_label($status) {
|
||||
$status_labels = [
|
||||
'active' => __('Active', 'wp-multisite-dashboard'),
|
||||
'recent' => __('Recent', 'wp-multisite-dashboard'),
|
||||
'inactive' => __('Inactive', 'wp-multisite-dashboard'),
|
||||
'very_inactive' => __('Very Inactive', 'wp-multisite-dashboard'),
|
||||
'never_logged_in' => __('Never Logged In', 'wp-multisite-dashboard')
|
||||
];
|
||||
return $status_labels[$status] ?? __('Unknown', 'wp-multisite-dashboard');
|
||||
}
|
||||
|
||||
public static function decode_html_entities($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
public static function strip_tags_and_limit($text, $limit = 100) {
|
||||
$text = strip_tags($text);
|
||||
return self::truncate_text($text, $limit);
|
||||
}
|
||||
}
|
294
includes/class-plugin-core.php
Normal file
294
includes/class-plugin-core.php
Normal file
|
@ -0,0 +1,294 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Plugin_Core {
|
||||
|
||||
private static $instance = null;
|
||||
private $enabled_widgets = [];
|
||||
private $update_checker = null;
|
||||
private $ajax_handler = null;
|
||||
private $admin_interface = null;
|
||||
private $settings_manager = null;
|
||||
|
||||
public static function get_instance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
add_action('init', [$this, 'init']);
|
||||
add_action('admin_init', [$this, 'admin_init']);
|
||||
add_action('network_admin_menu', [$this, 'add_admin_menu']);
|
||||
|
||||
$this->init_update_checker();
|
||||
$this->init_components();
|
||||
}
|
||||
|
||||
private function init_components() {
|
||||
$this->ajax_handler = new WP_MSD_Ajax_Handler();
|
||||
$this->admin_interface = new WP_MSD_Admin_Interface();
|
||||
$this->settings_manager = new WP_MSD_Settings_Manager();
|
||||
}
|
||||
|
||||
private function init_update_checker() {
|
||||
if (file_exists(plugin_dir_path(__FILE__) . '../lib/plugin-update-checker/plugin-update-checker.php')) {
|
||||
require_once plugin_dir_path(__FILE__) . '../lib/plugin-update-checker/plugin-update-checker.php';
|
||||
|
||||
if (class_exists('YahnisElsts\PluginUpdateChecker\v5p3\PucFactory')) {
|
||||
$this->update_checker = \YahnisElsts\PluginUpdateChecker\v5p3\PucFactory::buildUpdateChecker(
|
||||
'https://updates.weixiaoduo.com/wp-multisite-dashboard.json',
|
||||
WP_MSD_PLUGIN_DIR . 'wp-multisite-dashboard.php',
|
||||
'wp-multisite-dashboard'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function init() {
|
||||
load_plugin_textdomain('wp-multisite-dashboard');
|
||||
|
||||
$this->enabled_widgets = get_site_option('msd_enabled_widgets', [
|
||||
'msd_network_overview' => 1,
|
||||
'msd_quick_site_management' => 1,
|
||||
'msd_storage_performance' => 1,
|
||||
'msd_server_info' => 1,
|
||||
'msd_quick_links' => 1,
|
||||
'msd_version_info' => 1,
|
||||
'msd_custom_news' => 1,
|
||||
'msd_network_settings' => 1,
|
||||
'msd_user_management' => 1,
|
||||
'msd_contact_info' => 1,
|
||||
'msd_last_edits' => 1,
|
||||
'msd_todo_widget' => 1
|
||||
]);
|
||||
|
||||
$this->enhance_network_dashboard();
|
||||
}
|
||||
|
||||
public function admin_init() {
|
||||
if (!current_user_can('manage_network')) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action('wp_network_dashboard_setup', [$this->admin_interface, 'add_network_widgets']);
|
||||
add_action('wp_network_dashboard_setup', [$this, 'manage_system_widgets'], 20);
|
||||
add_action('wp_network_dashboard_setup', [$this, 'cache_detected_widgets'], 30);
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
|
||||
}
|
||||
|
||||
public function add_admin_menu() {
|
||||
add_submenu_page(
|
||||
'settings.php',
|
||||
'Dashboard Settings',
|
||||
'Dashboard Settings',
|
||||
'manage_network',
|
||||
'msd-settings',
|
||||
[$this->settings_manager, 'render_settings_page']
|
||||
);
|
||||
}
|
||||
|
||||
public function enqueue_admin_scripts($hook) {
|
||||
$allowed_hooks = ['index.php', 'dashboard.php', 'settings_page_msd-settings'];
|
||||
|
||||
if (!in_array($hook, $allowed_hooks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'msd-dashboard-core',
|
||||
WP_MSD_PLUGIN_URL . 'assets/dashboard-core.js',
|
||||
['jquery'],
|
||||
WP_MSD_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'msd-dashboard-widgets',
|
||||
WP_MSD_PLUGIN_URL . 'assets/dashboard-widgets.js',
|
||||
['msd-dashboard-core'],
|
||||
WP_MSD_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'msd-dashboard-modals',
|
||||
WP_MSD_PLUGIN_URL . 'assets/dashboard-modals.js',
|
||||
['msd-dashboard-core', 'jquery-ui-sortable'],
|
||||
WP_MSD_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'msd-dashboard',
|
||||
WP_MSD_PLUGIN_URL . 'assets/dashboard.css',
|
||||
[],
|
||||
WP_MSD_VERSION
|
||||
);
|
||||
|
||||
wp_localize_script('msd-dashboard-core', 'msdAjax', [
|
||||
'ajaxurl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('msd_ajax_nonce'),
|
||||
'strings' => [
|
||||
'confirm_action' => __('Are you sure?', 'wp-multisite-dashboard'),
|
||||
'loading' => __('Loading...', 'wp-multisite-dashboard'),
|
||||
'error_occurred' => __('An error occurred', 'wp-multisite-dashboard'),
|
||||
'refresh_success' => __('Data refreshed successfully', 'wp-multisite-dashboard'),
|
||||
'confirm_delete' => __('Are you sure you want to delete this item?', 'wp-multisite-dashboard'),
|
||||
'save_success' => __('Saved successfully', 'wp-multisite-dashboard')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
private function enhance_network_dashboard() {
|
||||
add_filter('dashboard_recent_posts_query_args', [$this, 'enhance_recent_posts']);
|
||||
add_action('wp_network_dashboard_setup', [$this, 'enhance_right_now_widget']);
|
||||
add_action('admin_footer', [$this, 'add_right_now_enhancements']);
|
||||
}
|
||||
|
||||
public function enhance_recent_posts($query_args) {
|
||||
$query_args['post_status'] = 'publish';
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
public function enhance_right_now_widget() {
|
||||
add_action('network_dashboard_right_now_content_table_end', [$this, 'add_right_now_plugin_link']);
|
||||
}
|
||||
|
||||
public function add_right_now_plugin_link() {
|
||||
if (!current_user_can('manage_network_plugins')) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<tr>';
|
||||
echo '<td class="first b"><a href="' . network_admin_url('plugin-install.php') . '">' . __('Add New Plugin', 'wp-multisite-dashboard') . '</a></td>';
|
||||
echo '<td class="t"><a href="' . network_admin_url('plugins.php') . '">' . __('Manage Plugins', 'wp-multisite-dashboard') . '</a></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
public function add_right_now_enhancements() {
|
||||
$screen = get_current_screen();
|
||||
if (!$screen || $screen->id !== 'dashboard-network') {
|
||||
return;
|
||||
}
|
||||
|
||||
$sites_count = get_sites(['count' => true]);
|
||||
$users_count = count_users()['total_users'];
|
||||
$themes_count = count(wp_get_themes(['allowed' => 'network']));
|
||||
$plugins_count = count(get_plugins());
|
||||
?>
|
||||
<style>
|
||||
.youhave-enhanced {
|
||||
line-height: 1.6;
|
||||
margin: 12px 4px;
|
||||
}
|
||||
.youhave-enhanced .stat-number {
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
var $subsubsub = $('.subsubsub');
|
||||
if ($subsubsub.length) {
|
||||
var pluginLink = '<li class="add-plugin"> | <a href="<?php echo network_admin_url('plugin-install.php'); ?>">Add New Plugin</a></li>';
|
||||
$subsubsub.append(pluginLink);
|
||||
}
|
||||
|
||||
var $youhave = $('.youhave');
|
||||
if ($youhave.length) {
|
||||
var enhancedText = 'You have ' +
|
||||
'<span class="stat-number"><?php echo $sites_count; ?></span> <span class="stat-label">sites</span>' +
|
||||
'<span class="stat-separator">•</span>' +
|
||||
'<span class="stat-number"><?php echo $users_count; ?></span> <span class="stat-label">users</span>' +
|
||||
'<span class="stat-separator">•</span>' +
|
||||
'<span class="stat-number"><?php echo $themes_count; ?></span> <span class="stat-label">themes</span>' +
|
||||
'<span class="stat-separator">•</span>' +
|
||||
'<span class="stat-number"><?php echo $plugins_count; ?></span> <span class="stat-label">plugins</span>';
|
||||
|
||||
$youhave.html(enhancedText).addClass('youhave-enhanced');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function manage_system_widgets() {
|
||||
global $wp_meta_boxes;
|
||||
|
||||
$disabled_widgets = get_site_option('msd_disabled_system_widgets', []);
|
||||
|
||||
if (!empty($disabled_widgets) && isset($wp_meta_boxes['dashboard-network'])) {
|
||||
foreach ($disabled_widgets as $widget_id) {
|
||||
if (isset($wp_meta_boxes['dashboard-network']['normal']['core'][$widget_id])) {
|
||||
unset($wp_meta_boxes['dashboard-network']['normal']['core'][$widget_id]);
|
||||
}
|
||||
if (isset($wp_meta_boxes['dashboard-network']['side']['core'][$widget_id])) {
|
||||
unset($wp_meta_boxes['dashboard-network']['side']['core'][$widget_id]);
|
||||
}
|
||||
if (isset($wp_meta_boxes['dashboard-network']['normal']['high'][$widget_id])) {
|
||||
unset($wp_meta_boxes['dashboard-network']['normal']['high'][$widget_id]);
|
||||
}
|
||||
if (isset($wp_meta_boxes['dashboard-network']['side']['high'][$widget_id])) {
|
||||
unset($wp_meta_boxes['dashboard-network']['side']['high'][$widget_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function cache_detected_widgets() {
|
||||
global $wp_meta_boxes;
|
||||
|
||||
if (!isset($wp_meta_boxes['dashboard-network'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$detected_widgets = [];
|
||||
|
||||
foreach ($wp_meta_boxes['dashboard-network'] as $context => $priorities) {
|
||||
if (!is_array($priorities)) continue;
|
||||
|
||||
foreach ($priorities as $priority => $widgets) {
|
||||
if (!is_array($widgets)) continue;
|
||||
|
||||
foreach ($widgets as $widget_id => $widget_data) {
|
||||
if (strpos($widget_id, 'msd_') !== 0) {
|
||||
$widget_title = $widget_data['title'] ?? $widget_id;
|
||||
if (is_callable($widget_title)) {
|
||||
$widget_title = $widget_id;
|
||||
}
|
||||
|
||||
$detected_widgets[$widget_id] = [
|
||||
'id' => $widget_id,
|
||||
'title' => $widget_title,
|
||||
'context' => $context,
|
||||
'priority' => $priority,
|
||||
'is_custom' => false,
|
||||
'is_system' => in_array($widget_id, [
|
||||
'network_dashboard_right_now',
|
||||
'dashboard_activity',
|
||||
'dashboard_plugins',
|
||||
'dashboard_primary',
|
||||
'dashboard_secondary'
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_site_transient('msd_detected_widgets', $detected_widgets, 12 * HOUR_IN_SECONDS);
|
||||
}
|
||||
|
||||
public function get_enabled_widgets() {
|
||||
return $this->enabled_widgets;
|
||||
}
|
||||
|
||||
public function get_update_checker() {
|
||||
return $this->update_checker;
|
||||
}
|
||||
}
|
148
includes/class-settings-manager.php
Normal file
148
includes/class-settings-manager.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Settings_Manager {
|
||||
|
||||
public function render_settings_page() {
|
||||
if (isset($_POST['submit']) && wp_verify_nonce($_POST['msd_settings_nonce'], 'msd_settings')) {
|
||||
$this->save_settings();
|
||||
return;
|
||||
}
|
||||
|
||||
$widget_options = [
|
||||
'msd_network_overview' => 'Network Overview',
|
||||
'msd_quick_site_management' => 'Quick Site Management',
|
||||
'msd_storage_performance' => 'Storage Usage',
|
||||
'msd_server_info' => 'Server Information',
|
||||
'msd_quick_links' => 'Quick Links',
|
||||
'msd_version_info' => 'Version Information',
|
||||
'msd_custom_news' => 'Network News',
|
||||
'msd_user_management' => 'User Management',
|
||||
'msd_contact_info' => 'Contact Information',
|
||||
'msd_last_edits' => 'Recent Network Activity',
|
||||
'msd_todo_widget' => 'Todo List'
|
||||
];
|
||||
|
||||
include WP_MSD_PLUGIN_DIR . 'templates/settings-page.php';
|
||||
}
|
||||
|
||||
private function save_settings() {
|
||||
$enabled_widgets = [];
|
||||
$widget_options = [
|
||||
'msd_network_overview',
|
||||
'msd_quick_site_management',
|
||||
'msd_storage_performance',
|
||||
'msd_server_info',
|
||||
'msd_quick_links',
|
||||
'msd_version_info',
|
||||
'msd_custom_news',
|
||||
'msd_user_management',
|
||||
'msd_contact_info',
|
||||
'msd_last_edits',
|
||||
'msd_todo_widget'
|
||||
];
|
||||
|
||||
foreach ($widget_options as $widget_id) {
|
||||
if (isset($_POST['widgets'][$widget_id])) {
|
||||
$enabled_widgets[$widget_id] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
update_site_option('msd_enabled_widgets', $enabled_widgets);
|
||||
|
||||
$disabled_system_widgets = [];
|
||||
if (isset($_POST['system_widgets']) && is_array($_POST['system_widgets'])) {
|
||||
$available_widgets = $this->get_available_system_widgets();
|
||||
foreach ($available_widgets as $widget_id => $widget_data) {
|
||||
if (!$widget_data['is_custom'] && !isset($_POST['system_widgets'][$widget_id])) {
|
||||
$disabled_system_widgets[] = $widget_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_site_option('msd_disabled_system_widgets', $disabled_system_widgets);
|
||||
|
||||
wp_safe_redirect(add_query_arg('updated', 'true', network_admin_url('settings.php?page=msd-settings')));
|
||||
exit;
|
||||
}
|
||||
|
||||
public function get_available_system_widgets() {
|
||||
$known_system_widgets = [
|
||||
'network_dashboard_right_now' => [
|
||||
'id' => 'network_dashboard_right_now',
|
||||
'title' => 'Right Now',
|
||||
'context' => 'normal',
|
||||
'priority' => 'core',
|
||||
'is_custom' => false,
|
||||
'is_system' => true
|
||||
],
|
||||
'dashboard_activity' => [
|
||||
'id' => 'dashboard_activity',
|
||||
'title' => 'Activity',
|
||||
'context' => 'normal',
|
||||
'priority' => 'high',
|
||||
'is_custom' => false,
|
||||
'is_system' => true
|
||||
],
|
||||
'dashboard_plugins' => [
|
||||
'id' => 'dashboard_plugins',
|
||||
'title' => 'Plugins',
|
||||
'context' => 'normal',
|
||||
'priority' => 'core',
|
||||
'is_custom' => false,
|
||||
'is_system' => true
|
||||
],
|
||||
'dashboard_primary' => [
|
||||
'id' => 'dashboard_primary',
|
||||
'title' => 'WordPress Events and News',
|
||||
'context' => 'side',
|
||||
'priority' => 'core',
|
||||
'is_custom' => false,
|
||||
'is_system' => true
|
||||
],
|
||||
'dashboard_secondary' => [
|
||||
'id' => 'dashboard_secondary',
|
||||
'title' => 'Other WordPress News',
|
||||
'context' => 'side',
|
||||
'priority' => 'core',
|
||||
'is_custom' => false,
|
||||
'is_system' => true
|
||||
]
|
||||
];
|
||||
|
||||
$available_widgets = $known_system_widgets;
|
||||
|
||||
$cached_widgets = get_site_transient('msd_detected_widgets');
|
||||
if ($cached_widgets && is_array($cached_widgets)) {
|
||||
foreach ($cached_widgets as $widget_id => $widget_data) {
|
||||
if (!isset($available_widgets[$widget_id])) {
|
||||
$available_widgets[$widget_id] = $widget_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $available_widgets;
|
||||
}
|
||||
|
||||
public function get_widget_description($widget_id) {
|
||||
$descriptions = [
|
||||
'msd_network_overview' => __('Network statistics and multisite configuration information', 'wp-multisite-dashboard'),
|
||||
'msd_quick_site_management' => __('Quick access to recently active sites with favicons', 'wp-multisite-dashboard'),
|
||||
'msd_storage_performance' => __('Top 5 sites by storage usage and performance insights', 'wp-multisite-dashboard'),
|
||||
'msd_server_info' => __('Server specifications and WordPress environment details', 'wp-multisite-dashboard'),
|
||||
'msd_quick_links' => __('Customizable quick access links for common tasks with drag-and-drop reordering', 'wp-multisite-dashboard'),
|
||||
'msd_version_info' => __('Plugin version and system information with help links', 'wp-multisite-dashboard'),
|
||||
'msd_custom_news' => __('Custom news sources and updates', 'wp-multisite-dashboard'),
|
||||
'msd_network_settings' => __('Network configuration and settings overview', 'wp-multisite-dashboard'),
|
||||
'msd_user_management' => __('Recent user registrations and user management tools', 'wp-multisite-dashboard'),
|
||||
'msd_contact_info' => __('Network administrator contact information with instant messaging and QR code support', 'wp-multisite-dashboard'),
|
||||
'msd_last_edits' => __('Recent posts, pages, and content activity across the network', 'wp-multisite-dashboard'),
|
||||
'msd_todo_widget' => __('Simple todo list for network administrators with priority levels', 'wp-multisite-dashboard')
|
||||
];
|
||||
|
||||
return $descriptions[$widget_id] ?? '';
|
||||
}
|
||||
}
|
|
@ -62,9 +62,9 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
$where_sql = implode(' AND ', $where_clauses);
|
||||
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE {$where_sql}
|
||||
ORDER BY completed ASC, priority DESC, created_at DESC
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE {$where_sql}
|
||||
ORDER BY completed ASC, priority DESC, created_at DESC
|
||||
LIMIT %d";
|
||||
|
||||
$where_values[] = $limit;
|
||||
|
@ -104,7 +104,7 @@ class WP_MSD_Todo_Manager {
|
|||
}
|
||||
|
||||
$user_id = $user_id ?: get_current_user_id();
|
||||
|
||||
|
||||
if (!$user_id) {
|
||||
return false;
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
|
@ -143,7 +143,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
public function delete_todo_item($item_id, $user_id = null) {
|
||||
$item_id = intval($item_id);
|
||||
|
||||
|
||||
if (!$item_id) {
|
||||
return false;
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
if ($result !== false && $item) {
|
||||
$this->clear_cache();
|
||||
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
|
@ -187,7 +187,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
public function toggle_todo_item($item_id, $completed = null, $user_id = null) {
|
||||
$item_id = intval($item_id);
|
||||
|
||||
|
||||
if (!$item_id) {
|
||||
return false;
|
||||
}
|
||||
|
@ -210,11 +210,11 @@ class WP_MSD_Todo_Manager {
|
|||
...$where_values
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
if (!$current_item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$completed = !$current_item->completed;
|
||||
}
|
||||
|
||||
|
@ -238,7 +238,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
public function update_todo_item($item_id, $data, $user_id = null) {
|
||||
$item_id = intval($item_id);
|
||||
|
||||
|
||||
if (!$item_id) {
|
||||
return false;
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ class WP_MSD_Todo_Manager {
|
|||
];
|
||||
|
||||
$total_result = $this->wpdb->get_row(
|
||||
"SELECT
|
||||
"SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(completed) as completed,
|
||||
SUM(CASE WHEN completed = 0 THEN 1 ELSE 0 END) as pending,
|
||||
|
@ -345,9 +345,9 @@ class WP_MSD_Todo_Manager {
|
|||
}
|
||||
|
||||
$priority_results = $this->wpdb->get_results(
|
||||
"SELECT priority, COUNT(*) as count
|
||||
FROM {$this->table_name}
|
||||
WHERE completed = 0
|
||||
"SELECT priority, COUNT(*) as count
|
||||
FROM {$this->table_name}
|
||||
WHERE completed = 0
|
||||
GROUP BY priority"
|
||||
);
|
||||
|
||||
|
@ -358,10 +358,10 @@ class WP_MSD_Todo_Manager {
|
|||
}
|
||||
|
||||
$user_results = $this->wpdb->get_results(
|
||||
"SELECT user_id, COUNT(*) as total, SUM(completed) as completed
|
||||
FROM {$this->table_name}
|
||||
GROUP BY user_id
|
||||
ORDER BY total DESC
|
||||
"SELECT user_id, COUNT(*) as total, SUM(completed) as completed
|
||||
FROM {$this->table_name}
|
||||
GROUP BY user_id
|
||||
ORDER BY total DESC
|
||||
LIMIT 10"
|
||||
);
|
||||
|
||||
|
@ -392,10 +392,10 @@ class WP_MSD_Todo_Manager {
|
|||
return $cached;
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE due_date < NOW()
|
||||
AND completed = 0
|
||||
ORDER BY due_date ASC
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE due_date < NOW()
|
||||
AND completed = 0
|
||||
ORDER BY due_date ASC
|
||||
LIMIT %d";
|
||||
|
||||
$results = $this->wpdb->get_results(
|
||||
|
@ -445,7 +445,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
|
@ -489,7 +489,7 @@ class WP_MSD_Todo_Manager {
|
|||
}
|
||||
|
||||
$cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
|
||||
|
||||
$result = $this->wpdb->delete(
|
||||
$this->table_name,
|
||||
[
|
||||
|
@ -501,7 +501,7 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
|
@ -515,4 +515,4 @@ class WP_MSD_Todo_Manager {
|
|||
|
||||
return $result !== false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
3
languages/wp-multisite-dashboard-zh_CN.l10n.php
Normal file
3
languages/wp-multisite-dashboard-zh_CN.l10n.php
Normal file
File diff suppressed because one or more lines are too long
BIN
languages/wp-multisite-dashboard-zh_CN.mo
Executable file
BIN
languages/wp-multisite-dashboard-zh_CN.mo
Executable file
Binary file not shown.
1005
languages/wp-multisite-dashboard-zh_CN.po
Executable file
1005
languages/wp-multisite-dashboard-zh_CN.po
Executable file
File diff suppressed because it is too large
Load diff
838
templates/admin-modals.php
Normal file
838
templates/admin-modals.php
Normal file
|
@ -0,0 +1,838 @@
|
|||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$contact_info = get_site_option('msd_contact_info', [
|
||||
'name' => get_network_option(null, 'site_name'),
|
||||
'email' => get_network_option(null, 'admin_email'),
|
||||
'phone' => '',
|
||||
'website' => network_home_url(),
|
||||
'description' => 'Network Administrator Contact Information',
|
||||
'qq' => '',
|
||||
'wechat' => '',
|
||||
'whatsapp' => '',
|
||||
'telegram' => '',
|
||||
'qr_code' => ''
|
||||
]);
|
||||
|
||||
$news_sources = get_site_option('msd_news_sources', [
|
||||
[
|
||||
'name' => 'WordPress News',
|
||||
'url' => 'https://wordpress.org/news/feed/',
|
||||
'enabled' => true
|
||||
]
|
||||
]);
|
||||
|
||||
$quick_links = get_site_option('msd_quick_links', []);
|
||||
?>
|
||||
|
||||
<div id="msd-contact-info-modal" class="msd-modal" style="display: none;">
|
||||
<div class="msd-modal-content">
|
||||
<div class="msd-modal-header">
|
||||
<h3><?php _e('Edit Contact Information', 'wp-multisite-dashboard'); ?></h3>
|
||||
<button type="button" class="msd-modal-close" onclick="MSD.hideContactInfoModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="msd-modal-body">
|
||||
<div class="msd-contact-form">
|
||||
<div class="msd-form-section">
|
||||
<div class="msd-form-field">
|
||||
<label><?php _e('Organization Name:', 'wp-multisite-dashboard'); ?></label>
|
||||
<input type="text" id="msd-contact-name" value="<?php echo esc_attr($contact_info['name']); ?>" placeholder="<?php esc_attr_e('Network Administrator', 'wp-multisite-dashboard'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="msd-form-field">
|
||||
<label><?php _e('Email:', 'wp-multisite-dashboard'); ?></label>
|
||||
<input type="email" id="msd-contact-email" value="<?php echo esc_attr($contact_info['email']); ?>" placeholder="admin@example.com">
|
||||
</div>
|
||||
|
||||
<div class="msd-form-field">
|
||||
<label><?php _e('Phone:', 'wp-multisite-dashboard'); ?></label>
|
||||
<input type="text" id="msd-contact-phone" value="<?php echo esc_attr($contact_info['phone']); ?>" placeholder="+1 234 567 8900">
|
||||
</div>
|
||||
|
||||
<div class="msd-form-field">
|
||||
<label><?php _e('Website:', 'wp-multisite-dashboard'); ?></label>
|
||||
<input type="url" id="msd-contact-website" value="<?php echo esc_attr($contact_info['website']); ?>" placeholder="https://example.com">
|
||||
</div>
|
||||
|
||||
<div class="msd-form-field">
|
||||
<label><?php _e('Description:', 'wp-multisite-dashboard'); ?></label>
|
||||
<textarea id="msd-contact-description" placeholder="<?php esc_attr_e('Brief description or role', 'wp-multisite-dashboard'); ?>"><?php echo esc_textarea($contact_info['description']); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-form-section">
|
||||
<div class="msd-form-grid">
|
||||
<div class="msd-form-field">
|
||||
<label>
|
||||
<span class="dashicons dashicons-admin-users"></span>
|
||||
<?php _e('QQ:', 'wp-multisite-dashboard'); ?>
|
||||
</label>
|
||||
<input type="text" id="msd-contact-qq" value="<?php echo esc_attr($contact_info['qq']); ?>" placeholder="1234567890">
|
||||
</div>
|
||||
|
||||
<div class="msd-form-field">
|
||||
<label>
|
||||
<span class="dashicons dashicons-format-chat"></span>
|
||||
<?php _e('WeChat:', 'wp-multisite-dashboard'); ?>
|
||||
</label>
|
||||
<input type="text" id="msd-contact-wechat" value="<?php echo esc_attr($contact_info['wechat']); ?>" placeholder="WeChat_ID">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-form-grid">
|
||||
<div class="msd-form-field">
|
||||
<label>
|
||||
<span class="dashicons dashicons-smartphone"></span>
|
||||
<?php _e('WhatsApp:', 'wp-multisite-dashboard'); ?>
|
||||
</label>
|
||||
<input type="text" id="msd-contact-whatsapp" value="<?php echo esc_attr($contact_info['whatsapp']); ?>" placeholder="+1234567890">
|
||||
</div>
|
||||
|
||||
<div class="msd-form-field">
|
||||
<label>
|
||||
<span class="dashicons dashicons-email-alt"></span>
|
||||
<?php _e('Telegram:', 'wp-multisite-dashboard'); ?>
|
||||
</label>
|
||||
<input type="text" id="msd-contact-telegram" value="<?php echo esc_attr($contact_info['telegram']); ?>" placeholder="@username">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-form-section">
|
||||
<div class="msd-form-field">
|
||||
<label><?php _e('QR Code Image URL:', 'wp-multisite-dashboard'); ?></label>
|
||||
<div class="msd-qr-input-group">
|
||||
<input type="url" id="msd-contact-qr-code" value="<?php echo esc_attr($contact_info['qr_code']); ?>" placeholder="https://example.com/qr-code.png">
|
||||
<button type="button" class="button" onclick="MSD.selectQRImage()"><?php _e('Select Image', 'wp-multisite-dashboard'); ?></button>
|
||||
</div>
|
||||
<p class="description"><?php _e('Upload or provide URL for a QR code image (WeChat, contact info, etc.)', 'wp-multisite-dashboard'); ?></p>
|
||||
|
||||
<div id="msd-qr-preview" class="msd-qr-preview" style="<?php echo empty($contact_info['qr_code']) ? 'display: none;' : ''; ?>">
|
||||
<img src="<?php echo esc_url($contact_info['qr_code']); ?>" alt="QR Code Preview" class="msd-qr-preview-img">
|
||||
<button type="button" class="msd-qr-remove" onclick="MSD.removeQRCode()">×</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-modal-footer">
|
||||
<button type="button" class="button button-primary" onclick="MSD.saveContactInfo()">
|
||||
<?php _e('Save Contact Info', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
<button type="button" class="button" onclick="MSD.hideContactInfoModal()">
|
||||
<?php _e('Cancel', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="msd-news-sources-modal" class="msd-modal" style="display: none;">
|
||||
<div class="msd-modal-content">
|
||||
<div class="msd-modal-header">
|
||||
<h3><?php _e('Configure News Sources', 'wp-multisite-dashboard'); ?></h3>
|
||||
<button type="button" class="msd-modal-close" onclick="MSD.hideNewsSourcesModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="msd-modal-body">
|
||||
<div id="msd-news-sources-editor">
|
||||
<?php if (!empty($news_sources)): ?>
|
||||
<?php foreach ($news_sources as $index => $source): ?>
|
||||
<div class="msd-news-source-item">
|
||||
<div class="msd-source-row">
|
||||
<input type="text"
|
||||
placeholder="<?php esc_attr_e('Source Name', 'wp-multisite-dashboard'); ?>"
|
||||
value="<?php echo esc_attr($source['name']); ?>"
|
||||
class="msd-news-name"
|
||||
required>
|
||||
<input type="url"
|
||||
placeholder="<?php esc_attr_e('RSS Feed URL', 'wp-multisite-dashboard'); ?>"
|
||||
value="<?php echo esc_url($source['url']); ?>"
|
||||
class="msd-news-url"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="msd-source-options">
|
||||
<label class="msd-checkbox-label">
|
||||
<input type="checkbox"
|
||||
class="msd-news-enabled"
|
||||
<?php checked(!empty($source['enabled'])); ?>>
|
||||
<?php _e('Enabled', 'wp-multisite-dashboard'); ?>
|
||||
</label>
|
||||
|
||||
<button type="button" class="msd-remove-source">
|
||||
<?php _e('Remove', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="msd-add-source-section">
|
||||
<button type="button" id="msd-add-news-source" class="button button-secondary">
|
||||
<span class="dashicons dashicons-plus-alt"></span>
|
||||
<?php _e('Add News Source', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="msd-news-help">
|
||||
<h4><?php _e('Popular RSS Feeds', 'wp-multisite-dashboard'); ?></h4>
|
||||
<div class="msd-rss-suggestions">
|
||||
<div class="msd-rss-suggestion">
|
||||
<strong>WenPai.org News</strong>
|
||||
<code>https://wenpai.org/news/feed/</code>
|
||||
<span class="msd-rss-desc">Official WenPai.org news and updates</span>
|
||||
</div>
|
||||
<div class="msd-rss-suggestion">
|
||||
<strong>WP TEA</strong>
|
||||
<code>https://wptea.com/feed</code>
|
||||
<span class="msd-rss-desc">WordPress China community news and insights</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-modal-footer">
|
||||
<button type="button" class="button button-primary" onclick="MSD.saveNewsSources()">
|
||||
<?php _e('Save News Sources', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
<button type="button" class="button" onclick="MSD.hideNewsSourcesModal()">
|
||||
<?php _e('Cancel', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="msd-quick-links-modal" class="msd-modal" style="display: none;">
|
||||
<div class="msd-modal-content">
|
||||
<div class="msd-modal-header">
|
||||
<h3><?php _e('Configure Quick Links', 'wp-multisite-dashboard'); ?></h3>
|
||||
<button type="button" class="msd-modal-close" onclick="MSD.hideQuickLinksModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="msd-modal-body">
|
||||
<div id="msd-quick-links-editor">
|
||||
<?php if (!empty($quick_links)): ?>
|
||||
<?php foreach ($quick_links as $index => $link): ?>
|
||||
<div class="msd-link-item">
|
||||
<div class="msd-link-row">
|
||||
<input type="text"
|
||||
placeholder="<?php esc_attr_e('Link Title', 'wp-multisite-dashboard'); ?>"
|
||||
value="<?php echo esc_attr($link['title']); ?>"
|
||||
class="msd-link-title"
|
||||
required>
|
||||
<input type="url"
|
||||
placeholder="https://example.com"
|
||||
value="<?php echo esc_url($link['url']); ?>"
|
||||
class="msd-link-url"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="msd-link-options">
|
||||
<input type="text"
|
||||
placeholder="<?php esc_attr_e('dashicons-admin-home or 🏠', 'wp-multisite-dashboard'); ?>"
|
||||
value="<?php echo esc_attr($link['icon']); ?>"
|
||||
class="msd-link-icon"
|
||||
title="<?php esc_attr_e('Icon (Dashicon class or emoji)', 'wp-multisite-dashboard'); ?>">
|
||||
|
||||
<label class="msd-checkbox-label">
|
||||
<input type="checkbox"
|
||||
class="msd-link-newtab"
|
||||
<?php checked(!empty($link['new_tab'])); ?>>
|
||||
<?php _e('Open in new tab', 'wp-multisite-dashboard'); ?>
|
||||
</label>
|
||||
|
||||
<button type="button" class="msd-remove-link">
|
||||
<?php _e('Remove', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="msd-add-link-section">
|
||||
<button type="button" id="msd-add-link" class="button button-secondary">
|
||||
<span class="dashicons dashicons-plus-alt"></span>
|
||||
<?php _e('Add Link', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="msd-quick-links-help">
|
||||
<h4><?php _e('Icon Options', 'wp-multisite-dashboard'); ?></h4>
|
||||
|
||||
<div class="msd-icon-types">
|
||||
<div class="msd-icon-type-section">
|
||||
<h5><?php _e('WordPress Dashicons', 'wp-multisite-dashboard'); ?></h5>
|
||||
<div class="msd-icon-examples">
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-admin-home"></span>
|
||||
<code>dashicons-admin-home</code>
|
||||
<span><?php _e('Home', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-chart-bar"></span>
|
||||
<code>dashicons-chart-bar</code>
|
||||
<span><?php _e('Analytics', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-icon-type-section">
|
||||
<h5><?php _e('Emojis', 'wp-multisite-dashboard'); ?></h5>
|
||||
<div class="msd-icon-examples">
|
||||
<div class="msd-icon-example">
|
||||
<span class="msd-emoji">🏠</span>
|
||||
<code>🏠</code>
|
||||
<span><?php _e('Home', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="msd-emoji">⚙️</span>
|
||||
<code>⚙️</code>
|
||||
<span><?php _e('Settings', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="description">
|
||||
<strong><?php _e('Dashicons:', 'wp-multisite-dashboard'); ?></strong> <?php _e('Built into WordPress, always available. Use format:', 'wp-multisite-dashboard'); ?> <code>dashicons-icon-name</code><br>
|
||||
<strong><?php _e('Emojis:', 'wp-multisite-dashboard'); ?></strong> <?php _e('Copy and paste emoji directly. Works on all devices.', 'wp-multisite-dashboard'); ?>
|
||||
</p>
|
||||
|
||||
<p class="description">
|
||||
<?php printf(__('Find more Dashicons at %s', 'wp-multisite-dashboard'), '<a href="https://developer.wordpress.org/resource/dashicons/" target="_blank">developer.wordpress.org/resource/dashicons/</a>'); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-modal-footer">
|
||||
<button type="button" class="button button-primary" onclick="MSD.saveQuickLinks()">
|
||||
<?php _e('Save Links', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
<button type="button" class="button" onclick="MSD.hideQuickLinksModal()">
|
||||
<?php _e('Cancel', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.msd-modal-intro {
|
||||
margin-bottom: 24px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid var(--msd-primary);
|
||||
}
|
||||
|
||||
.msd-modal-intro p {
|
||||
margin: 0;
|
||||
color: var(--msd-text);
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.msd-contact-form {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.msd-form-section {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
background: var(--msd-bg-light);
|
||||
}
|
||||
|
||||
.msd-form-section h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--msd-text);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid var(--msd-border);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.msd-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.msd-form-field {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.msd-form-field:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.msd-form-field label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
color: var(--msd-text);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.msd-form-field label .dashicons {
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--msd-primary);
|
||||
}
|
||||
|
||||
.msd-form-field input,
|
||||
.msd-form-field textarea {
|
||||
width: 100%;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius-small);
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-form-field input:focus,
|
||||
.msd-form-field textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--msd-primary);
|
||||
box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
|
||||
}
|
||||
|
||||
.msd-form-field textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.msd-qr-input-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.msd-qr-input-group input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.msd-qr-input-group .button {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.msd-qr-preview {
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
padding: 8px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.msd-qr-preview-img {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.msd-qr-remove {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--msd-danger);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-qr-remove:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.msd-form-field .description {
|
||||
margin: 6px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--msd-text-light);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.msd-news-source-item {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: var(--msd-bg);
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-news-source-item:hover {
|
||||
border-color: var(--msd-primary);
|
||||
}
|
||||
|
||||
.msd-source-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.msd-source-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.msd-news-name,
|
||||
.msd-news-url {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius-small);
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-news-name:focus,
|
||||
.msd-news-url:focus {
|
||||
outline: none;
|
||||
border-color: var(--msd-primary);
|
||||
box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
|
||||
}
|
||||
|
||||
.msd-checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--msd-text);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.msd-checkbox-label input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.msd-add-source-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 16px;
|
||||
border: 2px dashed var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-add-source-section:hover {
|
||||
border-color: var(--msd-primary);
|
||||
}
|
||||
|
||||
#msd-add-news-source {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.msd-news-help {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: var(--msd-radius);
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-news-help h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--msd-text);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.msd-rss-suggestions {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.msd-rss-suggestion {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.msd-rss-suggestion strong {
|
||||
color: var(--msd-text);
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.msd-rss-suggestion code {
|
||||
background: #e9ecef;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 10px;
|
||||
word-break: break-all;
|
||||
grid-column: 1 / -1;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.msd-rss-desc {
|
||||
color: var(--msd-text-light);
|
||||
font-size: 11px;
|
||||
grid-column: 1 / -1;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.msd-link-item {
|
||||
margin-bottom: var(--msd-spacing);
|
||||
padding: var(--msd-spacing);
|
||||
background: var(--msd-bg);
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
}
|
||||
|
||||
.msd-link-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.msd-link-options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.msd-link-title,
|
||||
.msd-link-url,
|
||||
.msd-link-icon {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius-small);
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-link-title:focus,
|
||||
.msd-link-url:focus,
|
||||
.msd-link-icon:focus {
|
||||
outline: none;
|
||||
border-color: var(--msd-primary);
|
||||
box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
|
||||
}
|
||||
|
||||
.msd-add-link-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 16px;
|
||||
border: 2px dashed var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-add-link-section:hover {
|
||||
border-color: var(--msd-primary);
|
||||
}
|
||||
|
||||
#msd-add-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.msd-quick-links-help {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: var(--msd-radius);
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-quick-links-help h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--msd-text);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.msd-icon-types {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.msd-icon-type-section h5 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--msd-text);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-icon-examples {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.msd-icon-example {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 1fr auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-icon-example .dashicons {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--msd-primary);
|
||||
}
|
||||
|
||||
.msd-icon-example .msd-emoji {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.msd-icon-example code {
|
||||
background: #e9ecef;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.msd-icon-example span:last-child {
|
||||
color: var(--msd-text-light);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.msd-quick-links-help .description {
|
||||
margin: 12px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--msd-text-light);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.msd-quick-links-help .description:last-child {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-quick-links-help a {
|
||||
color: var(--msd-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.msd-quick-links-help a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.msd-remove-source,
|
||||
.msd-remove-link {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
color: var(--msd-danger);
|
||||
border: 1px solid var(--msd-danger);
|
||||
background: none;
|
||||
border-radius: var(--msd-radius-small);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-remove-source:hover,
|
||||
.msd-remove-link:hover {
|
||||
background: var(--msd-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.msd-source-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.msd-source-options {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.msd-link-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.msd-link-options {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.msd-form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.msd-icon-examples {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.msd-modal-content {
|
||||
margin: 20px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.msd-rss-suggestion {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.msd-news-source-item.error {
|
||||
border-color: var(--msd-danger);
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.msd-news-url.error {
|
||||
border-color: var(--msd-danger);
|
||||
}
|
||||
|
||||
.msd-link-item.error {
|
||||
border-color: var(--msd-danger);
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.msd-link-url.error {
|
||||
border-color: var(--msd-danger);
|
||||
}
|
||||
</style>
|
|
@ -14,10 +14,6 @@ $quick_links = get_site_option('msd_quick_links', []);
|
|||
</div>
|
||||
|
||||
<div class="msd-modal-body">
|
||||
<div class="msd-modal-intro">
|
||||
<p><?php _e('Add custom links to frequently used pages or external tools. These will appear as clickable tiles in your Quick Links widget. You can use WordPress Dashicons or emojis for icons. Links can be reordered by dragging and dropping.', 'wp-multisite-dashboard'); ?></p>
|
||||
</div>
|
||||
|
||||
<div id="msd-quick-links-editor">
|
||||
<?php if (!empty($quick_links)): ?>
|
||||
<?php foreach ($quick_links as $index => $link): ?>
|
||||
|
@ -75,33 +71,13 @@ $quick_links = get_site_option('msd_quick_links', []);
|
|||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-admin-home"></span>
|
||||
<code>dashicons-admin-home</code>
|
||||
<span><?php _e('Home/Dashboard', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-admin-settings"></span>
|
||||
<code>dashicons-admin-settings</code>
|
||||
<span><?php _e('Settings', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-admin-users"></span>
|
||||
<code>dashicons-admin-users</code>
|
||||
<span><?php _e('Users', 'wp-multisite-dashboard'); ?></span>
|
||||
<span><?php _e('Home', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-chart-bar"></span>
|
||||
<code>dashicons-chart-bar</code>
|
||||
<span><?php _e('Analytics', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-email"></span>
|
||||
<code>dashicons-email</code>
|
||||
<span><?php _e('Email', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="dashicons dashicons-external"></span>
|
||||
<code>dashicons-external</code>
|
||||
<span><?php _e('External Link', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -118,35 +94,10 @@ $quick_links = get_site_option('msd_quick_links', []);
|
|||
<code>⚙️</code>
|
||||
<span><?php _e('Settings', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="msd-emoji">👥</span>
|
||||
<code>👥</code>
|
||||
<span><?php _e('Users', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="msd-emoji">📊</span>
|
||||
<code>📊</code>
|
||||
<span><?php _e('Analytics', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="msd-emoji">📧</span>
|
||||
<code>📧</code>
|
||||
<span><?php _e('Email', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
<div class="msd-icon-example">
|
||||
<span class="msd-emoji">🔗</span>
|
||||
<code>🔗</code>
|
||||
<span><?php _e('Link', 'wp-multisite-dashboard'); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-reorder-tip">
|
||||
<h5><?php _e('Drag & Drop Reordering', 'wp-multisite-dashboard'); ?></h5>
|
||||
<p class="description"><?php _e('After saving your links, you can reorder them by dragging and dropping the tiles in the Quick Links widget.', 'wp-multisite-dashboard'); ?></p>
|
||||
</div>
|
||||
|
||||
<p class="description">
|
||||
<strong><?php _e('Dashicons:', 'wp-multisite-dashboard'); ?></strong> <?php _e('Built into WordPress, always available. Use format:', 'wp-multisite-dashboard'); ?> <code>dashicons-icon-name</code><br>
|
||||
<strong><?php _e('Emojis:', 'wp-multisite-dashboard'); ?></strong> <?php _e('Copy and paste emoji directly. Works on all devices.', 'wp-multisite-dashboard'); ?>
|
||||
|
|
|
@ -6,6 +6,10 @@ if (!defined('ABSPATH')) {
|
|||
if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . __('Settings saved successfully!', 'wp-multisite-dashboard') . '</p></div>';
|
||||
}
|
||||
|
||||
$plugin_core = WP_MSD_Plugin_Core::get_instance();
|
||||
$enabled_widgets = $plugin_core->get_enabled_widgets();
|
||||
$settings_manager = new WP_MSD_Settings_Manager();
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
|
@ -22,8 +26,8 @@ if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
|
|||
</h1>
|
||||
|
||||
<div class="msd-card">
|
||||
<h2><?php _e('Widget Configuration', 'wp-multisite-dashboard'); ?></h2>
|
||||
<p><?php _e('Enable or disable dashboard widgets according to your needs.', 'wp-multisite-dashboard'); ?></p>
|
||||
<h2><?php _e('Plugin Widget Configuration', 'wp-multisite-dashboard'); ?></h2>
|
||||
<p><?php _e('Enable or disable custom dashboard widgets provided by this plugin.', 'wp-multisite-dashboard'); ?></p>
|
||||
|
||||
<form method="post" action="">
|
||||
<?php wp_nonce_field('msd_settings', 'msd_settings_nonce'); ?>
|
||||
|
@ -36,108 +40,100 @@ if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
|
|||
type="checkbox"
|
||||
name="widgets[<?php echo esc_attr($widget_id); ?>]"
|
||||
value="1"
|
||||
<?php checked(!empty($this->enabled_widgets[$widget_id])); ?>
|
||||
<?php checked(!empty($enabled_widgets[$widget_id])); ?>
|
||||
/>
|
||||
<?php echo esc_html($widget_name); ?>
|
||||
</label>
|
||||
<p class="description">
|
||||
<?php echo $this->get_widget_description($widget_id); ?>
|
||||
<?php echo $settings_manager->get_widget_description($widget_id); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<h3><?php _e('System & Third-Party Widgets', 'wp-multisite-dashboard'); ?></h3>
|
||||
<p><?php _e('Control the display of WordPress system widgets and widgets from other plugins.', 'wp-multisite-dashboard'); ?></p>
|
||||
|
||||
<?php
|
||||
$available_widgets = $settings_manager->get_available_system_widgets();
|
||||
$disabled_widgets = get_site_option('msd_disabled_system_widgets', []);
|
||||
|
||||
if (!empty($available_widgets)):
|
||||
?>
|
||||
<div class="msd-system-widgets-grid">
|
||||
<?php
|
||||
$system_widgets = array_filter($available_widgets, function($widget) {
|
||||
return $widget['is_system'];
|
||||
});
|
||||
|
||||
$third_party_widgets = array_filter($available_widgets, function($widget) {
|
||||
return !$widget['is_system'] && !$widget['is_custom'];
|
||||
});
|
||||
?>
|
||||
|
||||
<?php if (!empty($system_widgets)): ?>
|
||||
<div class="msd-widget-section">
|
||||
<h4><?php _e('WordPress System Widgets', 'wp-multisite-dashboard'); ?></h4>
|
||||
<?php foreach ($system_widgets as $widget_id => $widget_data): ?>
|
||||
<div class="msd-widget-toggle">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="system_widgets[<?php echo esc_attr($widget_id); ?>]"
|
||||
value="1"
|
||||
<?php checked(!in_array($widget_id, $disabled_widgets)); ?>
|
||||
/>
|
||||
<?php echo esc_html($widget_data['title']); ?>
|
||||
<span class="msd-widget-meta">(<?php echo esc_html($widget_data['context']); ?>)</span>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($third_party_widgets)): ?>
|
||||
<div class="msd-widget-section">
|
||||
<h4><?php _e('Third-Party Plugin Widgets', 'wp-multisite-dashboard'); ?></h4>
|
||||
<?php foreach ($third_party_widgets as $widget_id => $widget_data): ?>
|
||||
<div class="msd-widget-toggle">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="system_widgets[<?php echo esc_attr($widget_id); ?>]"
|
||||
value="1"
|
||||
<?php checked(!in_array($widget_id, $disabled_widgets)); ?>
|
||||
/>
|
||||
<?php echo esc_html($widget_data['title']); ?>
|
||||
<span class="msd-widget-meta">(<?php echo esc_html($widget_data['context']); ?>)</span>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="msd-widget-section">
|
||||
<h4><?php _e('Third-Party Plugin Widgets', 'wp-multisite-dashboard'); ?></h4>
|
||||
<div class="msd-no-third-party">
|
||||
<p><?php _e('No third-party widgets detected yet.', 'wp-multisite-dashboard'); ?></p>
|
||||
<p class="description"><?php _e('Third-party widgets are automatically detected when you visit the network dashboard. If you have plugins that add dashboard widgets, visit the dashboard first, then return here to see them.', 'wp-multisite-dashboard'); ?></p>
|
||||
<a href="<?php echo network_admin_url(); ?>" class="button button-secondary">
|
||||
<?php _e('Visit Network Dashboard', 'wp-multisite-dashboard'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="msd-no-widgets">
|
||||
<p><?php _e('No system widgets found.', 'wp-multisite-dashboard'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<p class="submit">
|
||||
<?php submit_button(__('Save Widget Settings', 'wp-multisite-dashboard'), 'primary', 'submit', false); ?>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="msd-card">
|
||||
<h2><?php _e('News Sources Configuration', 'wp-multisite-dashboard'); ?></h2>
|
||||
<p><?php _e('Configure custom RSS news sources for the Network News widget.', 'wp-multisite-dashboard'); ?></p>
|
||||
|
||||
<div class="msd-news-sources-config">
|
||||
<?php
|
||||
$news_sources = get_site_option('msd_news_sources', [
|
||||
[
|
||||
'name' => 'WordPress News',
|
||||
'url' => 'https://wordpress.org/news/feed/',
|
||||
'enabled' => true
|
||||
]
|
||||
]);
|
||||
?>
|
||||
|
||||
<div class="msd-current-sources">
|
||||
<h3><?php _e('Current News Sources', 'wp-multisite-dashboard'); ?></h3>
|
||||
|
||||
<?php if (!empty($news_sources)): ?>
|
||||
<table class="wp-list-table widefat striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Source Name', 'wp-multisite-dashboard'); ?></th>
|
||||
<th><?php _e('RSS URL', 'wp-multisite-dashboard'); ?></th>
|
||||
<th><?php _e('Status', 'wp-multisite-dashboard'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($news_sources as $source): ?>
|
||||
<tr>
|
||||
<td><strong><?php echo esc_html($source['name']); ?></strong></td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url($source['url']); ?>" target="_blank" class="msd-url-link">
|
||||
<?php echo esc_html($source['url']); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($source['enabled']): ?>
|
||||
<span class="msd-status-badge msd-status-active"><?php _e('Enabled', 'wp-multisite-dashboard'); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="msd-status-badge msd-status-inactive"><?php _e('Disabled', 'wp-multisite-dashboard'); ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="msd-empty-state">
|
||||
<p><?php _e('No news sources configured.', 'wp-multisite-dashboard'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="msd-news-actions">
|
||||
<button type="button" class="button button-primary" onclick="MSD.showNewsSourcesModal()">
|
||||
<span class="dashicons dashicons-rss"></span>
|
||||
<?php _e('Manage News Sources', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
|
||||
<button type="button" class="button button-secondary" onclick="MSD.clearNewsCache()">
|
||||
↻
|
||||
<?php _e('Clear News Cache', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="msd-news-help">
|
||||
<h4><?php _e('Popular RSS Sources', 'wp-multisite-dashboard'); ?></h4>
|
||||
<div class="msd-rss-examples">
|
||||
<div class="msd-rss-example">
|
||||
<strong>WordPress News:</strong>
|
||||
<code>https://wordpress.org/news/feed/</code>
|
||||
</div>
|
||||
<div class="msd-rss-example">
|
||||
<strong>WP Tavern:</strong>
|
||||
<code>https://wptavern.com/feed</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="description">
|
||||
<?php _e('You can add any valid RSS or Atom feed URL. The news widget will fetch and display the latest articles from your configured sources.', 'wp-multisite-dashboard'); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msd-card">
|
||||
<h2><?php _e('Cache Management', 'wp-multisite-dashboard'); ?></h2>
|
||||
<p><?php _e('Clear cached data to refresh dashboard widgets.', 'wp-multisite-dashboard'); ?></p>
|
||||
|
@ -151,217 +147,317 @@ if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
|
|||
<span class="dashicons dashicons-admin-multisite"></span>
|
||||
<?php _e('Clear Network Data', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
<button type="button" class="button" onclick="MSD.clearWidgetCache()">
|
||||
<span class="dashicons dashicons-dashboard"></span>
|
||||
<?php _e('Clear Widget Cache', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="description">
|
||||
<?php _e('Clearing caches will force the dashboard widgets to reload fresh data on the next page visit.', 'wp-multisite-dashboard'); ?>
|
||||
<?php _e('Clearing caches will force the dashboard widgets to reload fresh data on the next page visit. Widget cache contains the list of detected third-party widgets.', 'wp-multisite-dashboard'); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="msd-card">
|
||||
<h2><?php _e('Plugin Information', 'wp-multisite-dashboard'); ?></h2>
|
||||
<p><?php _e('Current plugin status and update information.', 'wp-multisite-dashboard'); ?></p>
|
||||
|
||||
<div class="msd-plugin-info">
|
||||
<div class="msd-info-row">
|
||||
<span class="msd-info-label"><?php _e('Current Version:', 'wp-multisite-dashboard'); ?></span>
|
||||
<span class="msd-info-value"><?php echo esc_html(WP_MSD_VERSION); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="msd-info-row">
|
||||
<span class="msd-info-label"><?php _e('Update Status:', 'wp-multisite-dashboard'); ?></span>
|
||||
<span class="msd-info-value" id="msd-update-status">
|
||||
<button type="button" class="button button-small" onclick="MSD.checkForUpdates()">
|
||||
<?php _e('Check for Updates', 'wp-multisite-dashboard'); ?>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.msd-status-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.msd-status-active {
|
||||
background: #d1eddb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.msd-status-inactive {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.msd-cache-actions,
|
||||
.msd-news-actions {
|
||||
.msd-cache-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.msd-cache-actions .button,
|
||||
.msd-news-actions .button {
|
||||
.msd-cache-actions .button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.msd-news-sources-config {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.msd-current-sources h3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.msd-url-link {
|
||||
word-break: break-all;
|
||||
color: var(--msd-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.msd-url-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.msd-news-help {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
.msd-plugin-info {
|
||||
background: #f8f9fa;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-news-help h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--msd-text);
|
||||
}
|
||||
|
||||
.msd-rss-examples {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.msd-rss-example {
|
||||
.msd-info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-rss-example strong {
|
||||
.msd-info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.msd-info-label {
|
||||
font-weight: 600;
|
||||
color: var(--msd-text);
|
||||
}
|
||||
|
||||
.msd-rss-example code {
|
||||
background: #e9ecef;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
font-family: 'Courier New', monospace;
|
||||
.msd-info-value {
|
||||
color: var(--msd-text-light);
|
||||
}
|
||||
|
||||
.msd-update-available {
|
||||
color: #d63638;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.msd-update-current {
|
||||
color: #00a32a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.msd-settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.msd-widget-toggle {
|
||||
background: var(--msd-bg-light);
|
||||
border: 1px solid var(--msd-border);
|
||||
border-radius: var(--msd-radius);
|
||||
padding: 20px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-widget-toggle:hover {
|
||||
border-color: var(--msd-primary);
|
||||
}
|
||||
|
||||
.msd-widget-toggle label {
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.msd-widget-toggle input[type="checkbox"] {
|
||||
margin: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.msd-widget-toggle .description {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: #8c8c8c;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.msd-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
max-width: unset;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msd-system-widgets-grid {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.msd-widget-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.msd-widget-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: var(--msd-text);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #007cba;
|
||||
}
|
||||
|
||||
.msd-widget-section .msd-widget-toggle {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.msd-widget-section .msd-widget-toggle:hover {
|
||||
border-color: var(--msd-primary);
|
||||
}
|
||||
|
||||
.msd-widget-section .msd-widget-toggle label {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.msd-widget-meta {
|
||||
font-size: 12px;
|
||||
color: var(--msd-text-light);
|
||||
font-style: italic;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.msd-no-widgets,
|
||||
.msd-no-third-party {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 2px dashed #ddd;
|
||||
color: var(--msd-text-light);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.msd-cache-actions,
|
||||
.msd-news-actions {
|
||||
.msd-cache-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.msd-rss-example {
|
||||
.msd-info-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.msd-widget-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.msd-widget-section .msd-widget-toggle label {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.msd-widget-meta {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.msd-settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
window.MSD = window.MSD || {};
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
MSD.clearCache = function(type) {
|
||||
if (!confirm('<?php echo esc_js(__('Are you sure you want to clear the cache?', 'wp-multisite-dashboard')); ?>')) {
|
||||
return;
|
||||
}
|
||||
// 确保全局 MSD 对象存在
|
||||
window.MSD = window.MSD || {};
|
||||
|
||||
var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
|
||||
|
||||
jQuery.post(ajaxurl, {
|
||||
action: 'msd_clear_cache',
|
||||
cache_type: type,
|
||||
nonce: '<?php echo wp_create_nonce('msd_clear_cache'); ?>'
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
alert('<?php echo esc_js(__('Cache cleared successfully!', 'wp-multisite-dashboard')); ?>');
|
||||
} else {
|
||||
alert('<?php echo esc_js(__('Failed to clear cache.', 'wp-multisite-dashboard')); ?>');
|
||||
// 清除缓存功能
|
||||
window.MSD.clearCache = function(type) {
|
||||
if (!confirm('Are you sure you want to clear the cache?')) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MSD.clearNewsCache = function() {
|
||||
if (!confirm('<?php echo esc_js(__('Are you sure you want to clear the news cache?', 'wp-multisite-dashboard')); ?>')) {
|
||||
return;
|
||||
}
|
||||
$.post(msdAjax.ajaxurl, {
|
||||
action: 'msd_clear_cache',
|
||||
cache_type: type,
|
||||
nonce: msdAjax.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
alert('Cache cleared successfully!');
|
||||
} else {
|
||||
alert('Failed to clear cache: ' + (response.data || 'Unknown error'));
|
||||
}
|
||||
}).fail(function() {
|
||||
alert('Failed to clear cache due to network error.');
|
||||
});
|
||||
};
|
||||
|
||||
var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
|
||||
// 检查更新功能
|
||||
window.MSD.checkForUpdates = function() {
|
||||
var $status = $('#msd-update-status');
|
||||
var $button = $status.find('button');
|
||||
|
||||
jQuery.post(ajaxurl, {
|
||||
action: 'msd_refresh_widget_data',
|
||||
widget: 'custom_news',
|
||||
nonce: '<?php echo wp_create_nonce('msd_ajax_nonce'); ?>'
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
alert('<?php echo esc_js(__('News cache cleared successfully!', 'wp-multisite-dashboard')); ?>');
|
||||
} else {
|
||||
alert('<?php echo esc_js(__('Failed to clear news cache.', 'wp-multisite-dashboard')); ?>');
|
||||
$button.prop('disabled', true).text('Checking...');
|
||||
|
||||
$.post(msdAjax.ajaxurl, {
|
||||
action: 'msd_check_plugin_update',
|
||||
nonce: msdAjax.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
if (response.data.version) {
|
||||
$status.html('<span class="msd-update-available">Version ' + response.data.version + ' available!</span>');
|
||||
if (response.data.details_url) {
|
||||
$status.append(' <a href="' + response.data.details_url + '" target="_blank">View Details</a>');
|
||||
}
|
||||
} else {
|
||||
$status.html('<span class="msd-update-current">Up to date</span>');
|
||||
}
|
||||
} else {
|
||||
$button.prop('disabled', false).text('Check for Updates');
|
||||
alert('Failed to check for updates: ' + (response.data || 'Unknown error'));
|
||||
}
|
||||
}).fail(function() {
|
||||
$button.prop('disabled', false).text('Check for Updates');
|
||||
alert('Failed to check for updates due to network error.');
|
||||
});
|
||||
};
|
||||
|
||||
// 清除小部件缓存功能
|
||||
window.MSD.clearWidgetCache = function() {
|
||||
if (!confirm('Are you sure you want to clear the widget cache? This will refresh the list of detected widgets.')) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MSD.showNewsSourcesModal = function() {
|
||||
if (typeof jQuery !== 'undefined' && jQuery('#msd-news-sources-modal').length) {
|
||||
jQuery('#msd-news-sources-modal').fadeIn(200);
|
||||
jQuery('body').addClass('modal-open');
|
||||
} else {
|
||||
alert('<?php echo esc_js(__('Please go to the dashboard to configure news sources.', 'wp-multisite-dashboard')); ?>');
|
||||
}
|
||||
};
|
||||
$.post(msdAjax.ajaxurl, {
|
||||
action: 'msd_clear_widget_cache',
|
||||
nonce: msdAjax.nonce
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
alert('Widget cache cleared successfully! Please reload the page to see updated widgets.');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to clear widget cache: ' + (response.data || 'Unknown error'));
|
||||
}
|
||||
}).fail(function() {
|
||||
alert('Failed to clear widget cache due to network error.');
|
||||
});
|
||||
};
|
||||
|
||||
MSD.hideNewsSourcesModal = function() {
|
||||
if (typeof jQuery !== 'undefined') {
|
||||
jQuery('#msd-news-sources-modal').fadeOut(200);
|
||||
jQuery('body').removeClass('modal-open');
|
||||
}
|
||||
};
|
||||
// 调试信息
|
||||
console.log('MSD Settings loaded with functions:', Object.keys(window.MSD));
|
||||
console.log('msdAjax object:', msdAjax);
|
||||
});
|
||||
|
||||
MSD.saveNewsSources = function() {
|
||||
if (typeof jQuery === 'undefined') {
|
||||
alert('jQuery is required for this functionality');
|
||||
return;
|
||||
}
|
||||
|
||||
var sources = [];
|
||||
jQuery('.msd-news-source-item').each(function() {
|
||||
var $item = jQuery(this);
|
||||
var name = $item.find('.msd-news-name').val().trim();
|
||||
var url = $item.find('.msd-news-url').val().trim();
|
||||
var enabled = $item.find('.msd-news-enabled').is(':checked');
|
||||
|
||||
if (name && url) {
|
||||
sources.push({
|
||||
name: name,
|
||||
url: url,
|
||||
enabled: enabled
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
|
||||
|
||||
jQuery.post(ajaxurl, {
|
||||
action: 'msd_save_news_sources',
|
||||
sources: sources,
|
||||
nonce: '<?php echo wp_create_nonce('msd_ajax_nonce'); ?>'
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
alert('<?php echo esc_js(__('News sources saved successfully!', 'wp-multisite-dashboard')); ?>');
|
||||
MSD.hideNewsSourcesModal();
|
||||
location.reload();
|
||||
} else {
|
||||
alert('<?php echo esc_js(__('Failed to save news sources.', 'wp-multisite-dashboard')); ?>');
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue