v1.2.2 稳定版

首发正式版本
This commit is contained in:
feibisi 2025-06-24 23:05:58 +08:00
parent 836b293850
commit a8c3a0b96d
20 changed files with 5001 additions and 4432 deletions

View file

@ -1,230 +1,242 @@
jQuery(document).ready(function($) {
'use strict';
jQuery(document).ready(function ($) {
"use strict";
const MSD_Core = {
refreshInterval: null,
refreshRate: 300000,
isRefreshing: false,
retryCount: 0,
maxRetries: 3,
const MSD_Core = {
refreshInterval: null,
refreshRate: 300000,
isRefreshing: false,
retryCount: 0,
maxRetries: 3,
init() {
this.bindEvents();
this.startAutoRefresh();
this.setupErrorHandling();
},
init() {
this.bindEvents();
this.startAutoRefresh();
this.setupErrorHandling();
},
bindEvents() {
$(document)
.on('click', '.msd-refresh-btn', this.handleRefreshClick.bind(this));
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).on("beforeunload", () => {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
};
});
},
window.MSD_Core = MSD_Core;
MSD_Core.init();
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");
}
});
},
$('head').append(`
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 || msdAjax.strings.unknown);
}
})
.fail((xhr, status, error) => {
errorCallback(error || msdAjax.strings.network_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) + (msdAjax.strings.million_suffix || "M")
);
} else if (num >= 1000) {
return (
(num / 1000).toFixed(1) + (msdAjax.strings.thousand_suffix || "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() +
(msdAjax.strings.ellipsis || "...")
);
},
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 msdAjax.strings.yesterday;
} else if (diffDays < 7) {
return msdAjax.strings.days_ago.replace("%d", diffDays);
} 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: msdAjax.strings.active,
recent: msdAjax.strings.recent,
inactive: msdAjax.strings.inactive,
very_inactive: msdAjax.strings.very_inactive,
never_logged_in: msdAjax.strings.never_logged_in,
};
return statusLabels[status] || msdAjax.strings.unknown;
},
getRegistrationLabel(registration) {
const labels = {
none: msdAjax.strings.registration_disabled || "Disabled",
user: msdAjax.strings.registration_users_only || "Users Only",
blog: msdAjax.strings.registration_sites_only || "Sites Only",
all: msdAjax.strings.registration_users_sites || "Users & Sites",
};
return labels[registration] || msdAjax.strings.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; }

View file

@ -1,376 +1,448 @@
jQuery(document).ready(function($) {
'use strict';
jQuery(document).ready(function ($) {
"use strict";
const MSD_Modals = {
init() {
this.bindEvents();
// 延迟初始化 sortable等待页面完全加载
$(document).ready(() => {
setTimeout(() => {
this.initSortable();
}, 1000);
const MSD_Modals = {
init() {
this.bindEvents();
$(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() {
$(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);
});
},
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));
saveQuickLinksOrder() {
const order = [];
$("#msd-sortable-links .msd-quick-link-item").each(function () {
const index = $(this).data("index");
if (index !== undefined) {
order.push(index);
}
});
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)
};
},
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(
msdAjax.strings.failed_save_order,
"error",
);
},
);
}
},
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);
});
},
handleModalClose(e) {
if (
e.target === e.currentTarget ||
$(e.target).hasClass("msd-modal-close")
) {
this.hideQuickLinksModal();
this.hideNewsSourcesModal();
this.hideContactInfoModal();
}
},
saveQuickLinksOrder() {
const order = [];
$('#msd-sortable-links .msd-quick-link-item').each(function() {
const index = $(this).data('index');
if (index !== undefined) {
order.push(index);
}
});
showQuickLinksModal() {
$("#msd-quick-links-modal").fadeIn(200);
$("body").addClass("modal-open");
},
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');
}
);
}
},
hideQuickLinksModal() {
$("#msd-quick-links-modal").fadeOut(200);
$("body").removeClass("modal-open");
},
handleModalClose(e) {
if (e.target === e.currentTarget || $(e.target).hasClass('msd-modal-close')) {
this.hideQuickLinksModal();
this.hideNewsSourcesModal();
this.hideContactInfoModal();
}
},
showNewsSourcesModal() {
$("#msd-news-sources-modal").fadeIn(200);
$("body").addClass("modal-open");
},
showQuickLinksModal() {
$('#msd-quick-links-modal').fadeIn(200);
$('body').addClass('modal-open');
},
hideNewsSourcesModal() {
$("#msd-news-sources-modal").fadeOut(200);
$("body").removeClass("modal-open");
},
hideQuickLinksModal() {
$('#msd-quick-links-modal').fadeOut(200);
$('body').removeClass('modal-open');
},
showContactInfoModal() {
$("#msd-contact-info-modal").fadeIn(200);
$("body").addClass("modal-open");
},
showNewsSourcesModal() {
$('#msd-news-sources-modal').fadeIn(200);
$('body').addClass('modal-open');
},
hideContactInfoModal() {
$("#msd-contact-info-modal").fadeOut(200);
$("body").removeClass("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 = `
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>
<input type="text" placeholder="${msdAjax.strings.link_title}" class="msd-link-title" required>
<input type="url" placeholder="${msdAjax.strings.url_placeholder}" class="msd-link-url" required>
</div>
<div class="msd-link-options">
<input type="text" placeholder="dashicons-admin-home or 🏠" class="msd-link-icon">
<input type="text" placeholder="${msdAjax.strings.icon_placeholder}" class="msd-link-icon">
<label class="msd-checkbox-label">
<input type="checkbox" class="msd-link-newtab">
Open in new tab
${msdAjax.strings.open_new_tab}
</label>
<button type="button" class="msd-remove-link">Remove</button>
<button type="button" class="msd-remove-link">${msdAjax.strings.remove}</button>
</div>
</div>
`;
$('#msd-quick-links-editor').append(html);
},
$("#msd-quick-links-editor").append(html);
},
removeQuickLinkRow(e) {
$(e.currentTarget).closest('.msd-link-item').fadeOut(200, function() {
$(this).remove();
});
},
removeQuickLinkRow(e) {
$(e.currentTarget)
.closest(".msd-link-item")
.fadeOut(200, function () {
$(this).remove();
});
},
addNewsSourceRow() {
const html = `
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>
<input type="text" placeholder="${msdAjax.strings.source_name}" class="msd-news-name" required>
<input type="url" placeholder="${msdAjax.strings.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
${msdAjax.strings.enabled}
</label>
<button type="button" class="msd-remove-source">Remove</button>
<button type="button" class="msd-remove-source">${msdAjax.strings.remove}</button>
</div>
</div>
`;
$('#msd-news-sources-editor').append(html);
},
$("#msd-news-sources-editor").append(html);
},
removeNewsSourceRow(e) {
$(e.currentTarget).closest('.msd-news-source-item').fadeOut(200, function() {
$(this).remove();
});
},
removeNewsSourceRow(e) {
$(e.currentTarget)
.closest(".msd-news-source-item")
.fadeOut(200, function () {
$(this).remove();
});
},
saveQuickLinks() {
const links = [];
let hasErrors = false;
saveQuickLinks() {
const links = [];
let hasErrors = false;
// Clear previous errors
$('.msd-link-item').removeClass('error');
$('.msd-link-url').removeClass('error');
$(".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');
$(".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;
}
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;
}
$item.find(".msd-link-url").removeClass("error");
links.push({ title, url, icon, new_tab: newTab });
} else if (title || url) {
$item.addClass("error");
hasErrors = true;
}
};
});
window.MSD_Modals = MSD_Modals;
MSD_Modals.init();
if (hasErrors) {
if (window.MSD_Core) {
window.MSD_Core.showNotice(
msdAjax.strings.fill_required_fields,
"error",
);
}
return;
}
const $saveBtn = $(".msd-modal-footer .button-primary");
$saveBtn.prop("disabled", true).text(msdAjax.strings.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(
msdAjax.strings.failed_save_links,
"error",
);
$saveBtn.prop("disabled", false).text(msdAjax.strings.save_links);
},
);
}
},
saveNewsSources() {
const sources = [];
let hasErrors = false;
$(".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(
msdAjax.strings.fill_required_fields,
"error",
);
}
return;
}
const $saveBtn = $("#msd-news-sources-modal .button-primary");
$saveBtn.prop("disabled", true).text(msdAjax.strings.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(
msdAjax.strings.failed_save_sources,
"error",
);
$saveBtn
.prop("disabled", false)
.text(msdAjax.strings.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(
msdAjax.strings.name_email_required,
"error",
);
}
return;
}
const $saveBtn = $("#msd-contact-info-modal .button-primary");
$saveBtn.prop("disabled", true).text(msdAjax.strings.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(
msdAjax.strings.failed_save_contact,
"error",
);
$saveBtn
.prop("disabled", false)
.text(msdAjax.strings.save_contact_info);
},
);
}
},
selectQRImage() {
if (wp && wp.media) {
const frame = wp.media({
title: msdAjax.strings.select_qr_image,
button: { text: msdAjax.strings.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(msdAjax.strings.enter_qr_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(
msdAjax.strings.news_cache_cleared,
"success",
);
if (window.MSD_Widgets) {
window.MSD_Widgets.loadWidget("custom_news");
}
},
(error) => {
window.MSD_Core.showNotice(
msdAjax.strings.failed_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();
});

View file

@ -0,0 +1,131 @@
jQuery(document).ready(function ($) {
"use strict";
window.MSD = window.MSD || {};
window.MSD.clearCache = function (type) {
if (!confirm(msdAjax.strings.clear_cache_confirm)) {
return;
}
$.post(
msdAjax.ajaxurl,
{
action: "msd_clear_cache",
cache_type: type,
nonce: msdAjax.nonce,
},
function (response) {
if (response.success) {
alert(msdAjax.strings.cache_cleared);
} else {
alert(
msdAjax.strings.cache_clear_failed +
": " +
(response.data || msdAjax.strings.unknown_error),
);
}
},
).fail(function () {
alert(
msdAjax.strings.cache_clear_failed +
" " +
msdAjax.strings.network_error_occurred,
);
});
};
window.MSD.checkForUpdates = function () {
var $status = $("#msd-update-status");
var $button = $status.find("button");
$button.prop("disabled", true).text(msdAjax.strings.checking_updates);
$.post(
msdAjax.ajaxurl,
{
action: "msd_check_plugin_update",
nonce: msdAjax.nonce,
},
function (response) {
if (response.success) {
if (response.data.version) {
var message = msdAjax.strings.update_available.replace(
"{version}",
response.data.version,
);
$status.html(
'<span class="msd-update-available">' + message + "</span>",
);
if (response.data.details_url) {
$status.append(
' <a href="' +
response.data.details_url +
'" target="_blank">' +
msdAjax.strings.view_details +
"</a>",
);
}
} else {
$status.html(
'<span class="msd-update-current">' +
msdAjax.strings.up_to_date +
"</span>",
);
}
} else {
$button.prop("disabled", false).text(msdAjax.strings.check_updates);
alert(
msdAjax.strings.update_check_failed +
": " +
(response.data || msdAjax.strings.unknown_error),
);
}
},
).fail(function () {
$button.prop("disabled", false).text(msdAjax.strings.check_updates);
alert(
msdAjax.strings.update_check_failed +
" " +
msdAjax.strings.network_error_occurred,
);
});
};
window.MSD.clearWidgetCache = function () {
if (!confirm(msdAjax.strings.clear_widget_cache_confirm)) {
return;
}
$.post(
msdAjax.ajaxurl,
{
action: "msd_clear_widget_cache",
nonce: msdAjax.nonce,
},
function (response) {
if (response.success) {
alert(msdAjax.strings.widget_cache_cleared);
location.reload();
} else {
alert(
msdAjax.strings.widget_cache_clear_failed +
": " +
(response.data || msdAjax.strings.unknown_error),
);
}
},
).fail(function () {
alert(
msdAjax.strings.widget_cache_clear_failed +
" " +
msdAjax.strings.network_error_occurred,
);
});
};
console.log(
msdAjax.strings.msd_settings_loaded + ":",
Object.keys(window.MSD),
);
});

File diff suppressed because it is too large Load diff

View file

@ -1,255 +0,0 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WP_MSD_Admin_Template {
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;
}
}

View file

@ -1,333 +1,405 @@
<?php
if (!defined('ABSPATH')) {
exit;
if (!defined("ABSPATH")) {
exit();
}
class WP_MSD_Admin_Interface {
public function __construct() {
add_action('admin_footer', [$this, 'render_modals']);
class WP_MSD_Admin_Interface
{
public function __construct()
{
add_action("admin_footer", [$this, "render_modals"]);
}
public function 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';
if ($screen && $screen->id === "dashboard-network") {
include_once WP_MSD_PLUGIN_DIR . "templates/admin-modals.php";
}
}
public function add_network_widgets() {
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']
"msd_network_overview" => [
__("Network Overview", "wp-multisite-dashboard"),
"render_network_overview_widget",
],
"msd_quick_site_management" => [
__("Quick Site Management", "wp-multisite-dashboard"),
"render_quick_site_widget",
],
"msd_storage_performance" => [
__("Storage Usage", "wp-multisite-dashboard"),
"render_storage_performance_widget",
],
"msd_server_info" => [
__("Server Information", "wp-multisite-dashboard"),
"render_server_info_widget",
],
"msd_quick_links" => [
__("Quick Links", "wp-multisite-dashboard"),
"render_quick_links_widget",
],
"msd_version_info" => [
__("Version Information", "wp-multisite-dashboard"),
"render_version_info_widget",
],
"msd_custom_news" => [
__("Network News", "wp-multisite-dashboard"),
"render_custom_news_widget",
],
"msd_user_management" => [
__("User Management", "wp-multisite-dashboard"),
"render_user_management_widget",
],
"msd_contact_info" => [
__("Contact Information", "wp-multisite-dashboard"),
"render_contact_info_widget",
],
"msd_last_edits" => [
__("Recent Network Activity", "wp-multisite-dashboard"),
"render_last_edits_widget",
],
"msd_todo_widget" => [
__("Todo List", "wp-multisite-dashboard"),
"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]]
);
wp_add_dashboard_widget($widget_id, $widget_data[0], [
$this,
$widget_data[1],
]);
}
}
}
public function render_network_overview_widget() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
echo "</div>";
}
public function render_quick_site_widget() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
echo "</div>";
}
public function render_storage_performance_widget() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
echo "</div>";
}
public function render_server_info_widget() {
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>';
echo '<button class="msd-refresh-btn" title="' .
esc_attr__("Refresh", "wp-multisite-dashboard") .
'" data-widget="server_info">↻</button>';
$this->render_server_info_content();
echo '</div>';
echo "</div>";
}
private function render_server_info_content() {
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()),
__("PHP Version", "wp-multisite-dashboard") => phpversion(),
__(
"MySQL Version",
"wp-multisite-dashboard"
) => $wpdb->db_version(),
__("Server Software", "wp-multisite-dashboard") =>
$_SERVER["SERVER_SOFTWARE"] ??
__("Unknown", "wp-multisite-dashboard"),
__("Server Time", "wp-multisite-dashboard") => current_time(
"Y-m-d H:i:s"
),
__("Memory Limit", "wp-multisite-dashboard") => ini_get(
"memory_limit"
),
__("Max Upload Size", "wp-multisite-dashboard") => 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',
__(
"PHP Version",
"wp-multisite-dashboard"
) => "dashicons-editor-code",
__(
"MySQL Version",
"wp-multisite-dashboard"
) => "dashicons-database",
__(
"Server Software",
"wp-multisite-dashboard"
) => "dashicons-admin-tools",
__("Server Time", "wp-multisite-dashboard") => "dashicons-clock",
__(
"Memory Limit",
"wp-multisite-dashboard"
) => "dashicons-performance",
__(
"Max Upload Size",
"wp-multisite-dashboard"
) => "dashicons-upload",
];
echo '<div class="msd-server-specs">';
foreach ($data as $label => $value) {
$icon = $icons[$label] ?? 'dashicons-info';
$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 '<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>';
echo "</div>";
}
public function render_version_info_widget() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
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() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
echo "</div>";
}
public function render_user_management_widget() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
echo "</div>";
}
public function render_contact_info_widget() {
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>';
echo '<button class="msd-refresh-btn" title="' .
esc_attr__("Refresh", "wp-multisite-dashboard") .
'" data-widget="contact_info">↻</button>';
$this->render_contact_info_content();
echo '</div>';
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' => ''
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",
"wp-multisite-dashboard"
),
"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 '<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>';
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>';
echo '<a href="mailto:' .
esc_attr($contact_info["email"]) .
'">' .
esc_html($contact_info["email"]) .
"</a>";
echo "</div>";
if (!empty($contact_info['phone'])) {
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 '<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>';
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']
"qq" => ["QQ", "dashicons-admin-users"],
"wechat" => [
__("WeChat", "wp-multisite-dashboard"),
"dashicons-format-chat",
],
"whatsapp" => [
__("WhatsApp", "wp-multisite-dashboard"),
"dashicons-smartphone",
],
"telegram" => [
__("Telegram", "wp-multisite-dashboard"),
"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>';
echo "<span>" .
esc_html($data[0]) .
": " .
esc_html($contact_info[$field]) .
"</span>";
echo "</div>";
}
}
if (!empty($contact_info['qr_code'])) {
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 '<img src="' .
esc_url($contact_info["qr_code"]) .
'" alt="' .
esc_attr__("QR Code", "wp-multisite-dashboard") .
'" class="msd-qr-image">';
echo "</div>";
}
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 '<button class="button button-small" onclick="MSD.showContactInfoModal()">' .
__("Edit Contact Info", "wp-multisite-dashboard") .
"</button>";
echo "</div>";
echo '</div>';
echo "</div>";
}
public function render_last_edits_widget() {
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>';
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', []);
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>';
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 . '">';
$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>';
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 "<span>" . esc_html($link["title"]) . "</span>";
echo "</a>";
}
echo '</div>';
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 '<button class="button button-secondary button-small" onclick="MSD.showQuickLinksModal()">' .
__("Edit Links", "wp-multisite-dashboard") .
"</button>";
echo "</div>";
}
echo '</div>';
echo "</div>";
}
public function render_todo_widget() {
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>';
echo '<div class="msd-loading"><span class="msd-spinner"></span>' .
__("Loading...", "wp-multisite-dashboard") .
"</div>";
echo "</div>";
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,148 +1,278 @@
<?php
if (!defined('ABSPATH')) {
exit;
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')) {
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'
"msd_network_overview" => __(
"Network Overview",
"wp-multisite-dashboard"
),
"msd_quick_site_management" => __(
"Quick Site Management",
"wp-multisite-dashboard"
),
"msd_storage_performance" => __(
"Storage Usage",
"wp-multisite-dashboard"
),
"msd_server_info" => __(
"Server Information",
"wp-multisite-dashboard"
),
"msd_quick_links" => __("Quick Links", "wp-multisite-dashboard"),
"msd_version_info" => __(
"Version Information",
"wp-multisite-dashboard"
),
"msd_custom_news" => __("Network News", "wp-multisite-dashboard"),
"msd_user_management" => __(
"User Management",
"wp-multisite-dashboard"
),
"msd_contact_info" => __(
"Contact Information",
"wp-multisite-dashboard"
),
"msd_last_edits" => __(
"Recent Network Activity",
"wp-multisite-dashboard"
),
"msd_todo_widget" => __("Todo List", "wp-multisite-dashboard"),
];
include WP_MSD_PLUGIN_DIR . 'templates/settings-page.php';
include WP_MSD_PLUGIN_DIR . "templates/settings-page.php";
}
private function save_settings() {
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'
"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])) {
if (isset($_POST["widgets"][$widget_id])) {
$enabled_widgets[$widget_id] = 1;
}
}
update_site_option('msd_enabled_widgets', $enabled_widgets);
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])) {
$all_available_widgets = $this->get_all_possible_widgets();
if (
isset($_POST["system_widgets"]) &&
is_array($_POST["system_widgets"])
) {
foreach ($all_available_widgets as $widget_id => $widget_data) {
if (
!$widget_data["is_custom"] &&
!isset($_POST["system_widgets"][$widget_id])
) {
$disabled_system_widgets[] = $widget_id;
}
}
} else {
foreach ($all_available_widgets as $widget_id => $widget_data) {
if (!$widget_data["is_custom"]) {
$disabled_system_widgets[] = $widget_id;
}
}
}
update_site_option('msd_disabled_system_widgets', $disabled_system_widgets);
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;
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
public function get_available_system_widgets()
{
$cached_widgets = get_site_transient("msd_detected_widgets");
$known_system_widgets = $this->get_known_system_widgets();
if ($cached_widgets && is_array($cached_widgets)) {
return array_merge($known_system_widgets, $cached_widgets);
}
return $known_system_widgets;
}
private function get_known_system_widgets()
{
return [
"network_dashboard_right_now" => [
"id" => "network_dashboard_right_now",
"title" => __("Right Now", "wp-multisite-dashboard"),
"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_activity" => [
"id" => "dashboard_activity",
"title" => __("Activity", "wp-multisite-dashboard"),
"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_plugins" => [
"id" => "dashboard_plugins",
"title" => __("Plugins", "wp-multisite-dashboard"),
"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_primary" => [
"id" => "dashboard_primary",
"title" => __(
"WordPress Events and News",
"wp-multisite-dashboard"
),
"context" => "side",
"priority" => "core",
"is_custom" => false,
"is_system" => true,
],
"dashboard_secondary" => [
"id" => "dashboard_secondary",
"title" => __("Other WordPress News", "wp-multisite-dashboard"),
"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;
private function get_all_possible_widgets()
{
$known_widgets = $this->get_known_system_widgets();
$cached_widgets = get_site_transient("msd_detected_widgets");
$stored_disabled = get_site_option("msd_disabled_system_widgets", []);
$all_widgets = $known_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;
if (!isset($all_widgets[$widget_id])) {
$all_widgets[$widget_id] = $widget_data;
}
}
}
return $available_widgets;
foreach ($stored_disabled as $widget_id) {
if (!isset($all_widgets[$widget_id])) {
$all_widgets[$widget_id] = [
"id" => $widget_id,
"title" => $this->generate_widget_title_from_id($widget_id),
"context" => "unknown",
"priority" => "default",
"is_custom" => false,
"is_system" => false,
];
}
}
return $all_widgets;
}
public function get_widget_description($widget_id) {
private function generate_widget_title_from_id($widget_id)
{
$title = str_replace(["_", "-"], " ", $widget_id);
$title = ucwords($title);
return $title;
}
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')
"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] ?? '';
return $descriptions[$widget_id] ?? "";
}
}

View file

@ -1,518 +0,0 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WP_MSD_Todo_Manager {
private $wpdb;
private $table_name;
private $cache_group = 'msd_todos';
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
$this->table_name = $wpdb->base_prefix . 'msd_todo_list';
}
public function create_todo_table() {
$charset_collate = $this->wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
item varchar(500) NOT NULL,
completed tinyint(1) DEFAULT 0,
priority enum('low','medium','high') DEFAULT 'medium',
user_id bigint(20) unsigned NOT NULL,
created_at datetime NOT NULL,
updated_at datetime DEFAULT NULL,
due_date datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY completed (completed),
KEY priority (priority),
KEY created_at (created_at)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
public function get_todo_items($limit = 50, $user_id = null, $completed = null) {
$cache_key = "todo_items_{$limit}_" . ($user_id ?? 'all') . "_" . ($completed ?? 'all');
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
$where_clauses = ['1=1'];
$where_values = [];
if ($user_id !== null) {
$where_clauses[] = 'user_id = %d';
$where_values[] = $user_id;
}
if ($completed !== null) {
$where_clauses[] = 'completed = %d';
$where_values[] = $completed ? 1 : 0;
}
$where_sql = implode(' AND ', $where_clauses);
$sql = "SELECT * FROM {$this->table_name}
WHERE {$where_sql}
ORDER BY completed ASC, priority DESC, created_at DESC
LIMIT %d";
$where_values[] = $limit;
$prepared_sql = $this->wpdb->prepare($sql, ...$where_values);
$results = $this->wpdb->get_results($prepared_sql, ARRAY_A);
$todos = [];
foreach ($results as $row) {
$user_data = get_userdata($row['user_id']);
$todos[] = [
'id' => intval($row['id']),
'item' => $row['item'],
'completed' => (bool)$row['completed'],
'priority' => $row['priority'],
'user_id' => intval($row['user_id']),
'user_name' => $user_data ? $user_data->display_name : 'Unknown User',
'user_avatar' => $user_data ? get_avatar_url($user_data->ID, 32) : '',
'created_at' => $row['created_at'],
'updated_at' => $row['updated_at'],
'due_date' => $row['due_date'],
'created_human' => human_time_diff(strtotime($row['created_at'])) . ' ago',
'due_human' => $row['due_date'] ? human_time_diff(strtotime($row['due_date'])) : null,
'is_overdue' => $row['due_date'] && strtotime($row['due_date']) < current_time('timestamp') && !$row['completed'],
'priority_label' => $this->get_priority_label($row['priority']),
'priority_class' => $this->get_priority_class($row['priority'])
];
}
wp_cache_set($cache_key, $todos, $this->cache_group, 1800);
return $todos;
}
public function add_todo_item($item, $priority = 'medium', $user_id = null, $due_date = null) {
if (empty($item)) {
return false;
}
$user_id = $user_id ?: get_current_user_id();
if (!$user_id) {
return false;
}
$data = [
'item' => sanitize_text_field($item),
'priority' => in_array($priority, ['low', 'medium', 'high']) ? $priority : 'medium',
'user_id' => $user_id,
'created_at' => current_time('mysql'),
'due_date' => $due_date ? date('Y-m-d H:i:s', strtotime($due_date)) : null
];
$result = $this->wpdb->insert(
$this->table_name,
$data,
['%s', '%s', '%d', '%s', '%s']
);
if ($result !== false) {
$this->clear_cache();
if (class_exists('WP_MSD_Network_Data')) {
$network_data = new WP_MSD_Network_Data();
$network_data->log_activity(
0,
'todo_added',
sprintf('Todo item added: %s', substr($item, 0, 50)),
'low',
$user_id
);
}
}
return $result !== false;
}
public function delete_todo_item($item_id, $user_id = null) {
$item_id = intval($item_id);
if (!$item_id) {
return false;
}
$where = ['id' => $item_id];
$where_format = ['%d'];
if ($user_id) {
$where['user_id'] = $user_id;
$where_format[] = '%d';
} elseif (!current_user_can('manage_network')) {
$where['user_id'] = get_current_user_id();
$where_format[] = '%d';
}
$item = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT item FROM {$this->table_name} WHERE id = %d",
$item_id
)
);
$result = $this->wpdb->delete($this->table_name, $where, $where_format);
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(
0,
'todo_deleted',
sprintf('Todo item deleted: %s', substr($item->item, 0, 50)),
'low'
);
}
}
return $result !== false;
}
public function toggle_todo_item($item_id, $completed = null, $user_id = null) {
$item_id = intval($item_id);
if (!$item_id) {
return false;
}
$where_clause = 'id = %d';
$where_values = [$item_id];
if ($user_id) {
$where_clause .= ' AND user_id = %d';
$where_values[] = $user_id;
} elseif (!current_user_can('manage_network')) {
$where_clause .= ' AND user_id = %d';
$where_values[] = get_current_user_id();
}
if ($completed === null) {
$current_item = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT completed FROM {$this->table_name} WHERE {$where_clause}",
...$where_values
)
);
if (!$current_item) {
return false;
}
$completed = !$current_item->completed;
}
$result = $this->wpdb->update(
$this->table_name,
[
'completed' => $completed ? 1 : 0,
'updated_at' => current_time('mysql')
],
['id' => $item_id],
['%d', '%s'],
['%d']
);
if ($result !== false) {
$this->clear_cache();
}
return $result !== false;
}
public function update_todo_item($item_id, $data, $user_id = null) {
$item_id = intval($item_id);
if (!$item_id) {
return false;
}
$allowed_fields = ['item', 'priority', 'due_date', 'completed'];
$update_data = [];
$update_format = [];
foreach ($data as $field => $value) {
if (!in_array($field, $allowed_fields)) {
continue;
}
switch ($field) {
case 'item':
$update_data['item'] = sanitize_text_field($value);
$update_format[] = '%s';
break;
case 'priority':
if (in_array($value, ['low', 'medium', 'high'])) {
$update_data['priority'] = $value;
$update_format[] = '%s';
}
break;
case 'due_date':
$update_data['due_date'] = $value ? date('Y-m-d H:i:s', strtotime($value)) : null;
$update_format[] = '%s';
break;
case 'completed':
$update_data['completed'] = $value ? 1 : 0;
$update_format[] = '%d';
break;
}
}
if (empty($update_data)) {
return false;
}
$update_data['updated_at'] = current_time('mysql');
$update_format[] = '%s';
$where = ['id' => $item_id];
$where_format = ['%d'];
if ($user_id) {
$where['user_id'] = $user_id;
$where_format[] = '%d';
} elseif (!current_user_can('manage_network')) {
$where['user_id'] = get_current_user_id();
$where_format[] = '%d';
}
$result = $this->wpdb->update(
$this->table_name,
$update_data,
$where,
$update_format,
$where_format
);
if ($result !== false) {
$this->clear_cache();
}
return $result !== false;
}
public function get_todo_statistics() {
$cache_key = 'todo_statistics';
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
$stats = [
'total' => 0,
'completed' => 0,
'pending' => 0,
'overdue' => 0,
'by_priority' => [
'high' => 0,
'medium' => 0,
'low' => 0
],
'by_user' => []
];
$total_result = $this->wpdb->get_row(
"SELECT
COUNT(*) as total,
SUM(completed) as completed,
SUM(CASE WHEN completed = 0 THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN due_date < NOW() AND completed = 0 THEN 1 ELSE 0 END) as overdue
FROM {$this->table_name}"
);
if ($total_result) {
$stats['total'] = intval($total_result->total);
$stats['completed'] = intval($total_result->completed);
$stats['pending'] = intval($total_result->pending);
$stats['overdue'] = intval($total_result->overdue);
}
$priority_results = $this->wpdb->get_results(
"SELECT priority, COUNT(*) as count
FROM {$this->table_name}
WHERE completed = 0
GROUP BY priority"
);
foreach ($priority_results as $priority_result) {
if (isset($stats['by_priority'][$priority_result->priority])) {
$stats['by_priority'][$priority_result->priority] = intval($priority_result->count);
}
}
$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
LIMIT 10"
);
foreach ($user_results as $user_result) {
$user_data = get_userdata($user_result->user_id);
$stats['by_user'][] = [
'user_id' => intval($user_result->user_id),
'user_name' => $user_data ? $user_data->display_name : 'Unknown User',
'total' => intval($user_result->total),
'completed' => intval($user_result->completed),
'pending' => intval($user_result->total) - intval($user_result->completed)
];
}
wp_cache_set($cache_key, $stats, $this->cache_group, 3600);
return $stats;
}
public function get_user_todos($user_id, $limit = 20) {
return $this->get_todo_items($limit, $user_id);
}
public function get_overdue_todos($limit = 20) {
$cache_key = "overdue_todos_{$limit}";
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
$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(
$this->wpdb->prepare($sql, $limit),
ARRAY_A
);
$todos = [];
foreach ($results as $row) {
$user_data = get_userdata($row['user_id']);
$todos[] = [
'id' => intval($row['id']),
'item' => $row['item'],
'priority' => $row['priority'],
'user_id' => intval($row['user_id']),
'user_name' => $user_data ? $user_data->display_name : 'Unknown User',
'due_date' => $row['due_date'],
'due_human' => human_time_diff(strtotime($row['due_date'])) . ' overdue',
'created_at' => $row['created_at'],
'priority_label' => $this->get_priority_label($row['priority']),
'priority_class' => $this->get_priority_class($row['priority'])
];
}
wp_cache_set($cache_key, $todos, $this->cache_group, 1800);
return $todos;
}
public function bulk_delete_completed($user_id = null) {
$where_clause = 'completed = 1';
$where_values = [];
if ($user_id) {
$where_clause .= ' AND user_id = %d';
$where_values[] = $user_id;
} elseif (!current_user_can('manage_network')) {
$where_clause .= ' AND user_id = %d';
$where_values[] = get_current_user_id();
}
if (!empty($where_values)) {
$sql = "DELETE FROM {$this->table_name} WHERE {$where_clause}";
$result = $this->wpdb->query($this->wpdb->prepare($sql, ...$where_values));
} else {
$result = $this->wpdb->delete($this->table_name, ['completed' => 1], ['%d']);
}
if ($result !== false) {
$this->clear_cache();
if (class_exists('WP_MSD_Network_Data')) {
$network_data = new WP_MSD_Network_Data();
$network_data->log_activity(
0,
'todo_bulk_delete',
sprintf('Bulk deleted %d completed todo items', $result),
'medium'
);
}
}
return $result !== false;
}
private function get_priority_label($priority) {
$labels = [
'low' => __('Low', 'wp-multisite-dashboard'),
'medium' => __('Medium', 'wp-multisite-dashboard'),
'high' => __('High', 'wp-multisite-dashboard')
];
return $labels[$priority] ?? $labels['medium'];
}
private function get_priority_class($priority) {
$classes = [
'low' => 'msd-priority-low',
'medium' => 'msd-priority-medium',
'high' => 'msd-priority-high'
];
return $classes[$priority] ?? $classes['medium'];
}
public function clear_cache() {
wp_cache_flush_group($this->cache_group);
return true;
}
public function cleanup_old_todos($days = 90) {
if (!current_user_can('manage_network')) {
return false;
}
$cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$result = $this->wpdb->delete(
$this->table_name,
[
'completed' => 1,
'updated_at' => $cutoff_date
],
['%d', '%s']
);
if ($result !== false) {
$this->clear_cache();
if (class_exists('WP_MSD_Network_Data')) {
$network_data = new WP_MSD_Network_Data();
$network_data->log_activity(
0,
'todo_cleanup',
sprintf('Cleaned up %d old completed todo items', $result),
'medium'
);
}
}
return $result !== false;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,36 +1,42 @@
<?php
if (!defined('ABSPATH')) {
exit;
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' => ''
$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",
"wp-multisite-dashboard"
),
"qq" => "",
"wechat" => "",
"whatsapp" => "",
"telegram" => "",
"qr_code" => "",
]);
$news_sources = get_site_option('msd_news_sources', [
$news_sources = get_site_option("msd_news_sources", [
[
'name' => 'WordPress News',
'url' => 'https://wordpress.org/news/feed/',
'enabled' => true
]
"name" => __("WordPress News", "wp-multisite-dashboard"),
"url" => "https://wordpress.org/news/feed/",
"enabled" => true,
],
]);
$quick_links = get_site_option('msd_quick_links', []);
$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>
<h3><?php _e(
"Edit Contact Information",
"wp-multisite-dashboard"
); ?></h3>
<button type="button" class="msd-modal-close" onclick="MSD.hideContactInfoModal()">&times;</button>
</div>
@ -38,28 +44,59 @@ $quick_links = get_site_option('msd_quick_links', []);
<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'); ?>">
<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">
<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">
<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">
<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>
<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>
@ -68,17 +105,27 @@ $quick_links = get_site_option('msd_quick_links', []);
<div class="msd-form-field">
<label>
<span class="dashicons dashicons-admin-users"></span>
<?php _e('QQ:', 'wp-multisite-dashboard'); ?>
<?php _e("QQ:", "wp-multisite-dashboard"); ?>
</label>
<input type="text" id="msd-contact-qq" value="<?php echo esc_attr($contact_info['qq']); ?>" placeholder="1234567890">
<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'); ?>
<?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">
<input type="text" id="msd-contact-wechat" value="<?php echo esc_attr(
$contact_info["wechat"]
); ?>" placeholder="<?php esc_attr_e(
"WeChat ID",
"wp-multisite-dashboard"
); ?>">
</div>
</div>
@ -86,32 +133,62 @@ $quick_links = get_site_option('msd_quick_links', []);
<div class="msd-form-field">
<label>
<span class="dashicons dashicons-smartphone"></span>
<?php _e('WhatsApp:', 'wp-multisite-dashboard'); ?>
<?php _e(
"WhatsApp:",
"wp-multisite-dashboard"
); ?>
</label>
<input type="text" id="msd-contact-whatsapp" value="<?php echo esc_attr($contact_info['whatsapp']); ?>" placeholder="+1234567890">
<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'); ?>
<?php _e(
"Telegram:",
"wp-multisite-dashboard"
); ?>
</label>
<input type="text" id="msd-contact-telegram" value="<?php echo esc_attr($contact_info['telegram']); ?>" placeholder="@username">
<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>
<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>
<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>
<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">
<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="<?php esc_attr_e(
"QR Code Preview",
"wp-multisite-dashboard"
); ?>" class="msd-qr-preview-img">
<button type="button" class="msd-qr-remove" onclick="MSD.removeQRCode()">×</button>
</div>
</div>
@ -121,10 +198,10 @@ $quick_links = get_site_option('msd_quick_links', []);
<div class="msd-modal-footer">
<button type="button" class="button button-primary" onclick="MSD.saveContactInfo()">
<?php _e('Save Contact Info', 'wp-multisite-dashboard'); ?>
<?php _e("Save Contact Info", "wp-multisite-dashboard"); ?>
</button>
<button type="button" class="button" onclick="MSD.hideContactInfoModal()">
<?php _e('Cancel', 'wp-multisite-dashboard'); ?>
<?php _e("Cancel", "wp-multisite-dashboard"); ?>
</button>
</div>
</div>
@ -133,7 +210,10 @@ $quick_links = get_site_option('msd_quick_links', []);
<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>
<h3><?php _e(
"Configure News Sources",
"wp-multisite-dashboard"
); ?></h3>
<button type="button" class="msd-modal-close" onclick="MSD.hideNewsSourcesModal()">&times;</button>
</div>
@ -144,13 +224,23 @@ $quick_links = get_site_option('msd_quick_links', []);
<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']); ?>"
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']); ?>"
placeholder="<?php esc_attr_e(
"RSS Feed URL",
"wp-multisite-dashboard"
); ?>"
value="<?php echo esc_url(
$source["url"]
); ?>"
class="msd-news-url"
required>
</div>
@ -159,12 +249,20 @@ $quick_links = get_site_option('msd_quick_links', []);
<label class="msd-checkbox-label">
<input type="checkbox"
class="msd-news-enabled"
<?php checked(!empty($source['enabled'])); ?>>
<?php _e('Enabled', 'wp-multisite-dashboard'); ?>
<?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'); ?>
<?php _e(
"Remove",
"wp-multisite-dashboard"
); ?>
</button>
</div>
</div>
@ -175,22 +273,37 @@ $quick_links = get_site_option('msd_quick_links', []);
<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'); ?>
<?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>
<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>
<strong><?php _e(
"WenPai.org News",
"wp-multisite-dashboard"
); ?></strong>
<code>https://wenpai.org/news/feed/</code>
<span class="msd-rss-desc">Official WenPai.org news and updates</span>
<span class="msd-rss-desc"><?php _e(
"Official WenPai.org news and updates",
"wp-multisite-dashboard"
); ?></span>
</div>
<div class="msd-rss-suggestion">
<strong>WP TEA</strong>
<strong><?php _e(
"WP TEA",
"wp-multisite-dashboard"
); ?></strong>
<code>https://wptea.com/feed</code>
<span class="msd-rss-desc">WordPress China community news and insights</span>
<span class="msd-rss-desc"><?php _e(
"WordPress China community news and insights",
"wp-multisite-dashboard"
); ?></span>
</div>
</div>
</div>
@ -198,10 +311,10 @@ $quick_links = get_site_option('msd_quick_links', []);
<div class="msd-modal-footer">
<button type="button" class="button button-primary" onclick="MSD.saveNewsSources()">
<?php _e('Save News Sources', 'wp-multisite-dashboard'); ?>
<?php _e("Save News Sources", "wp-multisite-dashboard"); ?>
</button>
<button type="button" class="button" onclick="MSD.hideNewsSourcesModal()">
<?php _e('Cancel', 'wp-multisite-dashboard'); ?>
<?php _e("Cancel", "wp-multisite-dashboard"); ?>
</button>
</div>
</div>
@ -210,7 +323,10 @@ $quick_links = get_site_option('msd_quick_links', []);
<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>
<h3><?php _e(
"Configure Quick Links",
"wp-multisite-dashboard"
); ?></h3>
<button type="button" class="msd-modal-close" onclick="MSD.hideQuickLinksModal()">&times;</button>
</div>
@ -221,33 +337,56 @@ $quick_links = get_site_option('msd_quick_links', []);
<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']); ?>"
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']); ?>"
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']); ?>"
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'); ?>">
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'); ?>
<?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'); ?>
<?php _e(
"Remove",
"wp-multisite-dashboard"
); ?>
</button>
</div>
</div>
@ -258,64 +397,100 @@ $quick_links = get_site_option('msd_quick_links', []);
<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'); ?>
<?php _e("Add Link", "wp-multisite-dashboard"); ?>
</button>
</div>
<div class="msd-quick-links-help">
<h4><?php _e('Icon Options', 'wp-multisite-dashboard'); ?></h4>
<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>
<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>
<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>
<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>
<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>
<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>
<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'); ?>
<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>'); ?>
<?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'); ?>
<?php _e("Save Links", "wp-multisite-dashboard"); ?>
</button>
<button type="button" class="button" onclick="MSD.hideQuickLinksModal()">
<?php _e('Cancel', 'wp-multisite-dashboard'); ?>
<?php _e("Cancel", "wp-multisite-dashboard"); ?>
</button>
</div>
</div>

View file

@ -1,308 +0,0 @@
<?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' => ''
]);
?>
<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()">&times;</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>
<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-contact-qr {
text-align: center;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--msd-bg);
}
.msd-qr-image {
max-width: 100%;
max-height: 200px;
border: 1px solid var(--msd-border);
padding: 12px;
background: var(--msd-bg);
border-radius: var(--msd-radius-small);
transition: all 0.2s ease;
}
@media screen and (max-width: 600px) {
.msd-form-grid {
grid-template-columns: 1fr;
}
.msd-qr-input-group {
flex-direction: column;
align-items: stretch;
}
.msd-modal-content {
margin: 20px;
width: auto;
}
}
</style>

View file

@ -1,279 +0,0 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$news_sources = get_site_option('msd_news_sources', [
[
'name' => 'WordPress News',
'url' => 'https://wordpress.org/news/feed/',
'enabled' => true
]
]);
?>
<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()">&times;</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>
<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>
<style>
.msd-modal-intro {
margin-bottom: 20px;
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-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-news-help h5 {
margin: 16px 0 8px 0;
color: var(--msd-text);
font-size: 13px;
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-feed-tips {
background: white;
padding: 12px;
border-radius: 3px;
border: 1px solid #e9ecef;
}
.msd-feed-tips ul {
margin: 8px 0 0 0;
padding-left: 16px;
}
.msd-feed-tips li {
font-size: 12px;
color: var(--msd-text-light);
line-height: 1.4;
margin-bottom: 4px;
}
@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-rss-suggestion {
grid-template-columns: 1fr;
}
.msd-modal-content {
margin: 20px;
width: auto;
}
}
.msd-news-source-item.error {
border-color: var(--msd-danger);
background: #fff5f5;
}
.msd-news-url.error {
border-color: var(--msd-danger);
}
</style>

View file

@ -1,356 +0,0 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$quick_links = get_site_option('msd_quick_links', []);
?>
<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()">&times;</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: 20px;
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;
}
.msd-link-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-link-item:hover {
border-color: var(--msd-primary);
}
.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-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-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-reorder-tip {
margin-top: 20px;
padding: 12px;
background: #e7f3ff;
border-radius: 4px;
border-left: 4px solid #2196f3;
}
.msd-reorder-tip h5 {
margin: 0 0 8px 0;
color: var(--msd-text);
font-size: 14px;
font-weight: 600;
}
.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;
}
@media screen and (max-width: 600px) {
.msd-link-row {
grid-template-columns: 1fr;
}
.msd-link-options {
grid-template-columns: 1fr;
gap: 8px;
}
.msd-icon-examples {
grid-template-columns: 1fr;
}
.msd-modal-content {
margin: 20px;
width: auto;
}
}
.msd-link-item.error {
border-color: var(--msd-danger);
background: #fff5f5;
}
.msd-link-url.error {
border-color: var(--msd-danger);
}
</style>

View file

@ -1,90 +1,159 @@
<?php
if (!defined('ABSPATH')) {
exit;
if (!defined("ABSPATH")) {
exit();
}
if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
echo '<div class="notice notice-success is-dismissible"><p>' . __('Settings saved successfully!', 'wp-multisite-dashboard') . '</p></div>';
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();
$widget_options = [
"msd_network_overview" => __("Network Overview", "wp-multisite-dashboard"),
"msd_quick_site_management" => __(
"Quick Site Management",
"wp-multisite-dashboard"
),
"msd_storage_performance" => __("Storage Usage", "wp-multisite-dashboard"),
"msd_server_info" => __("Server Information", "wp-multisite-dashboard"),
"msd_quick_links" => __("Quick Links", "wp-multisite-dashboard"),
"msd_version_info" => __("Version Information", "wp-multisite-dashboard"),
"msd_custom_news" => __("Network News", "wp-multisite-dashboard"),
"msd_user_management" => __("User Management", "wp-multisite-dashboard"),
"msd_contact_info" => __("Contact Information", "wp-multisite-dashboard"),
"msd_last_edits" => __("Recent Network Activity", "wp-multisite-dashboard"),
"msd_todo_widget" => __("Todo List", "wp-multisite-dashboard"),
];
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?>
<h1><?php echo esc_html(get_admin_page_title()); ?>
<span style="font-size: 13px; padding-left: 10px;">
<?php printf( esc_html__( 'Version: %s', 'wp-multisite-dashboard' ), esc_html( WP_MSD_VERSION ) ); ?>
<?php printf(
esc_html__("Version: %s", "wp-multisite-dashboard"),
esc_html(WP_MSD_VERSION)
); ?>
</span>
<a href="https://wpmultisite.com/document/wp-multisite-dashboard" target="_blank" class="button button-secondary" style="margin-left: 10px;">
<?php esc_html_e( 'Documentation', 'wp-multisite-dashboard' ); ?>
<?php esc_html_e("Documentation", "wp-multisite-dashboard"); ?>
</a>
<a href="https://wpmultisite.com/support/" target="_blank" class="button button-secondary">
<?php esc_html_e( 'Support', 'wp-multisite-dashboard' ); ?>
<?php esc_html_e("Support", "wp-multisite-dashboard"); ?>
</a>
</h1>
<div class="msd-card">
<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>
<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'); ?>
<?php wp_nonce_field("msd_settings", "msd_settings_nonce"); ?>
<div class="msd-settings-grid">
<?php foreach ($widget_options as $widget_id => $widget_name): ?>
<?php foreach (
$widget_options
as $widget_id => $widget_name
): ?>
<div class="msd-widget-toggle">
<label>
<input
type="checkbox"
name="widgets[<?php echo esc_attr($widget_id); ?>]"
name="widgets[<?php echo esc_attr(
$widget_id
); ?>]"
value="1"
<?php checked(!empty($enabled_widgets[$widget_id])); ?>
<?php checked(
!empty($enabled_widgets[$widget_id])
); ?>
/>
<?php echo esc_html($widget_name); ?>
</label>
<p class="description">
<?php echo $settings_manager->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>
<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', []);
$disabled_widgets = get_site_option(
"msd_disabled_system_widgets",
[]
);
if (!empty($available_widgets)):
?>
if (!empty($available_widgets)): ?>
<div class="msd-system-widgets-grid">
<?php
$system_widgets = array_filter($available_widgets, function($widget) {
return $widget['is_system'];
});
$system_widgets = array_filter(
$available_widgets,
function ($widget) {
return $widget["is_system"] ?? false;
}
);
$third_party_widgets = array_filter($available_widgets, function($widget) {
return !$widget['is_system'] && !$widget['is_custom'];
});
$third_party_widgets = array_filter(
$available_widgets,
function ($widget) {
return !($widget["is_system"] ?? false) &&
!($widget["is_custom"] ?? false);
}
);
?>
<?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): ?>
<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); ?>]"
name="system_widgets[<?php echo esc_attr(
$widget_id
); ?>]"
value="1"
<?php checked(!in_array($widget_id, $disabled_widgets)); ?>
<?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>
<?php echo esc_html(
$widget_data["title"]
); ?>
<span class="msd-widget-meta">(<?php echo esc_html(
$widget_data["context"]
); ?>)</span>
</label>
</div>
<?php endforeach; ?>
@ -93,86 +162,171 @@ $settings_manager = new WP_MSD_Settings_Manager();
<?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): ?>
<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); ?>]"
name="system_widgets[<?php echo esc_attr(
$widget_id
); ?>]"
value="1"
<?php checked(!in_array($widget_id, $disabled_widgets)); ?>
<?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>
<?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: ?>
<?php endif; ?>
<?php if (empty($third_party_widgets)): ?>
<div class="msd-widget-section">
<h4><?php _e('Third-Party Plugin Widgets', 'wp-multisite-dashboard'); ?></h4>
<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>
<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>
<div style="margin: 15px 0;">
<a href="<?php echo network_admin_url(); ?>" class="button button-secondary">
<?php _e(
"Visit Network Dashboard",
"wp-multisite-dashboard"
); ?>
</a>
<button type="button" class="button" onclick="MSD.clearWidgetCache()">
<?php _e(
"Refresh Widget Detection",
"wp-multisite-dashboard"
); ?>
</button>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php else: ?>
<div class="msd-no-widgets">
<p><?php _e('No system widgets found.', 'wp-multisite-dashboard'); ?></p>
<p><?php _e(
"No system widgets found.",
"wp-multisite-dashboard"
); ?></p>
<p><?php _e(
"Visit the network dashboard to detect available widgets.",
"wp-multisite-dashboard"
); ?></p>
<div style="margin: 15px 0;">
<a href="<?php echo network_admin_url(); ?>" class="button button-secondary">
<?php _e(
"Visit Network Dashboard",
"wp-multisite-dashboard"
); ?>
</a>
<button type="button" class="button" onclick="MSD.clearWidgetCache()">
<?php _e(
"Refresh Widget Detection",
"wp-multisite-dashboard"
); ?>
</button>
</div>
</div>
<?php endif; ?>
<?php endif;
?>
<p class="submit">
<?php submit_button(__('Save Widget Settings', 'wp-multisite-dashboard'), 'primary', 'submit', false); ?>
<?php submit_button(
__("Save Widget Settings", "wp-multisite-dashboard"),
"primary",
"submit",
false
); ?>
</p>
</form>
</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>
<h2><?php _e("Cache Management", "wp-multisite-dashboard"); ?></h2>
<p><?php _e(
"Clear cached data to refresh dashboard widgets.",
"wp-multisite-dashboard"
); ?></p>
<div class="msd-cache-actions">
<button type="button" class="button" onclick="MSD.clearCache('all')">
<?php _e('Clear All Caches', 'wp-multisite-dashboard'); ?>
<?php _e("Clear All Caches", "wp-multisite-dashboard"); ?>
</button>
<button type="button" class="button" onclick="MSD.clearCache('network')">
<span class="dashicons dashicons-admin-multisite"></span>
<?php _e('Clear Network Data', 'wp-multisite-dashboard'); ?>
<?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'); ?>
<?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. Widget cache contains the list of detected third-party widgets.', '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>
<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>
<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-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'); ?>
<?php _e(
"Check for Updates",
"wp-multisite-dashboard"
); ?>
</button>
</span>
</div>
@ -375,89 +529,3 @@ $settings_manager = new WP_MSD_Settings_Manager();
}
}
</style>
<script>
jQuery(document).ready(function($) {
'use strict';
// 确保全局 MSD 对象存在
window.MSD = window.MSD || {};
// 清除缓存功能
window.MSD.clearCache = function(type) {
if (!confirm('Are you sure you want to clear the cache?')) {
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.');
});
};
// 检查更新功能
window.MSD.checkForUpdates = function() {
var $status = $('#msd-update-status');
var $button = $status.find('button');
$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;
}
$.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.');
});
};
// 调试信息
console.log('MSD Settings loaded with functions:', Object.keys(window.MSD));
console.log('msdAjax object:', msdAjax);
});
</script>

View file

@ -3,72 +3,91 @@
* Plugin Name: WP Multisite Dashboard
* Plugin URI: https://wpmultisite.com/plugins/wp-multisite-dashboard
* Description: Essential dashboard widgets for WordPress multisite administrators
* Version: 1.2.0
* Version: 1.2.2
* Author: WPMultisite.com
* Author URI: https://WPMultisite.com
* License: GPLv2+
* Text Domain: wp-multisite-dashboard
* Domain Path: /languages
* Requires PHP: 7.4
*/
if (!defined('ABSPATH')) {
exit;
if (!defined("ABSPATH")) {
exit();
}
define('WP_MSD_VERSION', '1.2.0');
define('WP_MSD_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WP_MSD_PLUGIN_URL', plugin_dir_url(__FILE__));
define("WP_MSD_VERSION", "1.2.2");
define("WP_MSD_PLUGIN_DIR", plugin_dir_path(__FILE__));
define("WP_MSD_PLUGIN_URL", plugin_dir_url(__FILE__));
require_once WP_MSD_PLUGIN_DIR . 'includes/class-helpers.php';
require_once WP_MSD_PLUGIN_DIR . 'includes/class-network-data.php';
require_once WP_MSD_PLUGIN_DIR . 'includes/class-user-manager.php';
require_once WP_MSD_PLUGIN_DIR . 'includes/class-ajax-handler.php';
require_once WP_MSD_PLUGIN_DIR . 'includes/class-admin-interface.php';
require_once WP_MSD_PLUGIN_DIR . 'includes/class-settings-manager.php';
require_once WP_MSD_PLUGIN_DIR . 'includes/class-plugin-core.php';
require_once WP_MSD_PLUGIN_DIR . "includes/class-helpers.php";
require_once WP_MSD_PLUGIN_DIR . "includes/class-network-data.php";
require_once WP_MSD_PLUGIN_DIR . "includes/class-user-manager.php";
require_once WP_MSD_PLUGIN_DIR . "includes/class-ajax-handler.php";
require_once WP_MSD_PLUGIN_DIR . "includes/class-admin-interface.php";
require_once WP_MSD_PLUGIN_DIR . "includes/class-settings-manager.php";
require_once WP_MSD_PLUGIN_DIR . "includes/class-plugin-core.php";
function wp_msd_init() {
function wp_msd_init()
{
if (!is_multisite()) {
add_action('admin_notices', 'wp_msd_multisite_required_notice');
add_action("admin_notices", "wp_msd_multisite_required_notice");
return;
}
WP_MSD_Plugin_Core::get_instance();
}
function wp_msd_multisite_load_textdomain() {
load_plugin_textdomain('wp-multisite-dashboard', false, dirname(plugin_basename(__FILE__)) . '/languages');
function wp_msd_multisite_load_textdomain()
{
$plugin_rel_path = dirname(plugin_basename(__FILE__)) . "/languages/";
load_plugin_textdomain("wp-multisite-dashboard", false, $plugin_rel_path);
}
add_action('plugins_loaded', 'wp_msd_multisite_load_textdomain');
add_action("plugins_loaded", "wp_msd_multisite_load_textdomain");
function wp_msd_multisite_required_notice() {
function wp_msd_multisite_required_notice()
{
echo '<div class="notice notice-error"><p>';
echo __('WP Multisite Dashboard requires WordPress Multisite to be enabled.', 'wp-multisite-dashboard');
echo '</p></div>';
echo __(
"WP Multisite Dashboard requires WordPress Multisite to be enabled.",
"wp-multisite-dashboard"
);
echo "</p></div>";
}
register_activation_hook(__FILE__, 'wp_msd_activation');
function wp_msd_activation() {
register_activation_hook(__FILE__, "wp_msd_activation");
function wp_msd_activation()
{
if (!is_multisite()) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(__('This plugin requires WordPress Multisite to be enabled.', 'wp-multisite-dashboard'));
wp_die(
__(
"This plugin requires WordPress Multisite to be enabled.",
"wp-multisite-dashboard"
)
);
}
$network_data = new WP_MSD_Network_Data();
$network_data->create_activity_log_table();
set_site_transient('msd_activation_notice', true, 30);
set_site_transient("msd_activation_notice", true, 30);
}
add_action('network_admin_notices', 'wp_msd_activation_notice');
function wp_msd_activation_notice() {
if (get_site_transient('msd_activation_notice')) {
add_action("network_admin_notices", "wp_msd_activation_notice");
function wp_msd_activation_notice()
{
if (get_site_transient("msd_activation_notice")) {
echo '<div class="notice notice-success is-dismissible">';
echo '<p>' . __('WP Multisite Dashboard has been activated successfully!', 'wp-multisite-dashboard') . '</p>';
echo '</div>';
delete_site_transient('msd_activation_notice');
echo "<p>" .
__(
"WP Multisite Dashboard has been activated successfully!",
"wp-multisite-dashboard"
) .
"</p>";
echo "</div>";
delete_site_transient("msd_activation_notice");
}
}
add_action('plugins_loaded', 'wp_msd_init');
add_action("plugins_loaded", "wp_msd_init");