mirror of
https://github.com/WenPai-org/wp-domain-mapping.git
synced 2025-08-03 05:33:23 +08:00
v2.0 正式版发布
This commit is contained in:
parent
a1869ff494
commit
dd5e4f04a0
12 changed files with 3705 additions and 2138 deletions
1127
admin/pages.php
1127
admin/pages.php
File diff suppressed because it is too large
Load diff
|
@ -358,24 +358,44 @@ settings_errors( 'dm_settings' );
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ( ! defined( 'COOKIE_DOMAIN' ) ) : ?>
|
||||
<?php
|
||||
// Use the improved COOKIE_DOMAIN check function from pages.php
|
||||
$cookie_domain_status = dm_check_cookie_domain_status();
|
||||
|
||||
if ($cookie_domain_status['is_optimal']): ?>
|
||||
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
|
||||
<?php else : ?>
|
||||
<?php elseif ($cookie_domain_status['has_warning']): ?>
|
||||
<span class="dashicons dashicons-warning" style="color: #f56e28;"></span>
|
||||
<?php else: ?>
|
||||
<span class="dashicons dashicons-no-alt" style="color: #dc3232;"></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php esc_html_e( 'COOKIE_DOMAIN', 'wp-domain-mapping' ); ?></td>
|
||||
<td><?php esc_html_e('COOKIE_DOMAIN', 'wp-domain-mapping'); ?></td>
|
||||
<td>
|
||||
<?php if ( ! defined( 'COOKIE_DOMAIN' ) ) : ?>
|
||||
<?php esc_html_e( 'Not defined (correct)', 'wp-domain-mapping' ); ?>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: COOKIE_DOMAIN constant value */
|
||||
esc_html__( 'Defined as: %s - remove this from wp-config.php', 'wp-domain-mapping' ),
|
||||
'<code>' . esc_html( COOKIE_DOMAIN ) . '</code>'
|
||||
);
|
||||
?>
|
||||
<?php if ($cookie_domain_status['is_optimal']): ?>
|
||||
<span style="color: #46b450;">
|
||||
<?php echo esc_html($cookie_domain_status['message']); ?>
|
||||
</span>
|
||||
<?php elseif ($cookie_domain_status['has_warning']): ?>
|
||||
<span style="color: #f56e28;">
|
||||
<?php echo esc_html($cookie_domain_status['message']); ?>
|
||||
</span>
|
||||
<br><small style="color: #666;">
|
||||
<?php esc_html_e('This may cause login issues on mapped domains. Consider removing the COOKIE_DOMAIN definition from wp-config.php.', 'wp-domain-mapping'); ?>
|
||||
</small>
|
||||
<?php else: ?>
|
||||
<span style="color: #dc3232;">
|
||||
<?php echo esc_html($cookie_domain_status['message']); ?>
|
||||
</span>
|
||||
<br><small style="color: #666;">
|
||||
<?php esc_html_e('Remove the define(\'COOKIE_DOMAIN\', ...) line from wp-config.php to enable dynamic cookie domain handling.', 'wp-domain-mapping'); ?>
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (defined('WP_DEBUG') && WP_DEBUG && !empty($cookie_domain_status['debug_info'])): ?>
|
||||
<br><small style="color: #999; font-style: italic;">
|
||||
<?php echo esc_html($cookie_domain_status['debug_info']); ?>
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* WP Domain Mapping Admin Styles */
|
||||
/* WP Domain Mapping Admin Styles - Optimized Layout */
|
||||
|
||||
/* Main cards */
|
||||
.domain-mapping-card {
|
||||
|
@ -9,9 +9,14 @@
|
|||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.04);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tabs - Now inside the main card */
|
||||
.domain-mapping-wrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.domain-mapping-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -40,8 +45,21 @@
|
|||
border-bottom-color: #dcdcde;
|
||||
}
|
||||
|
||||
/* Tab Content */
|
||||
.domain-mapping-content {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.domain-mapping-content h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.domain-mapping-content h3 {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
color: #1d2327;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Search form */
|
||||
|
@ -50,7 +68,11 @@
|
|||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-form-field {
|
||||
|
@ -62,6 +84,7 @@
|
|||
.search-form-field label {
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.search-form-submit {
|
||||
|
@ -72,9 +95,10 @@
|
|||
|
||||
/* Tables */
|
||||
.tablenav {
|
||||
margin: 10px 0;
|
||||
margin: 15px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tablenav-pages {
|
||||
|
@ -92,23 +116,32 @@
|
|||
font-weight: 400;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.tablenav-pages span.current {
|
||||
background: #007cba;
|
||||
background: #2271b1;
|
||||
color: #fff;
|
||||
border-color: #2271b1;
|
||||
}
|
||||
|
||||
.tablenav-pages a:hover {
|
||||
background: #2271b1;
|
||||
color: #fff;
|
||||
border-color: #007cba;
|
||||
}
|
||||
|
||||
.displaying-num {
|
||||
margin-left: 10px;
|
||||
color: #555;
|
||||
color: #646970;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.domains-table th,
|
||||
.logs-table th,
|
||||
.domains-health-table th {
|
||||
font-weight: 600;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.column-site-id {
|
||||
|
@ -152,8 +185,8 @@
|
|||
background: #2271b1;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
@ -161,6 +194,8 @@
|
|||
.form-table th {
|
||||
width: 200px;
|
||||
padding: 15px 10px 15px 0;
|
||||
font-weight: 600;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
|
@ -168,16 +203,23 @@
|
|||
}
|
||||
|
||||
.description {
|
||||
color: #666;
|
||||
color: #646970;
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.regular-text {
|
||||
width: 25em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Notices */
|
||||
.notice {
|
||||
padding: 8px 12px;
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
margin: 5px 0 15px;
|
||||
border-left: 4px solid #ddd;
|
||||
}
|
||||
|
||||
.notice p {
|
||||
|
@ -187,63 +229,85 @@
|
|||
|
||||
.notice-success {
|
||||
background-color: #f0f9eb;
|
||||
border-left: 4px solid #46b450;
|
||||
border-left-color: #46b450;
|
||||
}
|
||||
|
||||
.notice-error {
|
||||
background-color: #fef0f0;
|
||||
border-left: 4px solid #dc3232;
|
||||
border-left-color: #dc3232;
|
||||
}
|
||||
|
||||
.notice-warning {
|
||||
background-color: #fff8e5;
|
||||
border-left: 4px solid #ffb900;
|
||||
border-left-color: #ffb900;
|
||||
}
|
||||
|
||||
.notice-info {
|
||||
background-color: #f0f6fa;
|
||||
border-left: 4px solid #00a0d2;
|
||||
border-left-color: #00a0d2;
|
||||
}
|
||||
|
||||
.notice.inline {
|
||||
display: block;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
/* DNS instructions */
|
||||
.dns-instructions {
|
||||
margin-top: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.dns-instructions h3 {
|
||||
color: #1d2327;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dns-example {
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 3px;
|
||||
background: #f6f7f7;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.dns-example h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.dns-example table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.dns-tips {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.dns-tips li {
|
||||
margin-bottom: 8px;
|
||||
color: #50575e;
|
||||
}
|
||||
|
||||
/* Status indicators */
|
||||
.dashicons-yes-alt,
|
||||
.dashicons-no-alt {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.dashicons-no-alt,
|
||||
.dashicons-warning,
|
||||
.dashicons-minus {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Logs table styles */
|
||||
.logs-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logs-table .column-user,
|
||||
|
@ -261,41 +325,49 @@
|
|||
|
||||
.log-action {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.log-action-add {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
background-color: #d1e7dd;
|
||||
color: #0f5132;
|
||||
}
|
||||
|
||||
.log-action-edit {
|
||||
background-color: #d9edf7;
|
||||
color: #31708f;
|
||||
background-color: #cce7ff;
|
||||
color: #004085;
|
||||
}
|
||||
|
||||
.log-action-delete {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.log-action-import {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
/* Health details */
|
||||
.health-details-content {
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.health-details-content h4 {
|
||||
margin-top: 0;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
/* Progress bar */
|
||||
.progress-bar-outer {
|
||||
background-color: #f0f0f1;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 4px;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
|
@ -304,18 +376,30 @@
|
|||
}
|
||||
|
||||
.progress-bar-inner {
|
||||
background-color: #2271b1;
|
||||
background: linear-gradient(90deg, #2271b1 0%, #72aee6 100%);
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: #1d2327;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Import/Export specific */
|
||||
.export-section,
|
||||
.import-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.import-summary,
|
||||
.import-details {
|
||||
margin-top: 15px;
|
||||
|
@ -337,11 +421,414 @@
|
|||
background-color: #fff8e5;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
/* Buttons */
|
||||
.button {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 2.15384615;
|
||||
margin: 0;
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
background: #2271b1;
|
||||
border-color: #2271b1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button-primary:hover,
|
||||
.button-primary:focus {
|
||||
background: #135e96;
|
||||
border-color: #135e96;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
background: #f6f7f7;
|
||||
border-color: #dcdcde;
|
||||
color: #50575e;
|
||||
}
|
||||
|
||||
.button-secondary:hover,
|
||||
.button-secondary:focus {
|
||||
background: #f0f0f1;
|
||||
border-color: #8c8f94;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.button-small {
|
||||
font-size: 11px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.button:disabled,
|
||||
.button[disabled] {
|
||||
background: #f6f7f7 !important;
|
||||
border-color: #dcdcde !important;
|
||||
color: #a7aaad !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Health check specific buttons */
|
||||
.check-domain-health {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Table row hover effects */
|
||||
.wp-list-table tbody tr:hover {
|
||||
background-color: #f6f7f7;
|
||||
}
|
||||
|
||||
.wp-list-table tbody tr.hover {
|
||||
background-color: #f0f6fa;
|
||||
}
|
||||
|
||||
/* Bulk actions */
|
||||
.bulkactions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.bulkactions select {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Row actions */
|
||||
.row-actions {
|
||||
visibility: hidden;
|
||||
padding: 2px 0 0;
|
||||
color: #646970;
|
||||
}
|
||||
|
||||
.wp-list-table tbody tr:hover .row-actions {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.row-actions span {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.row-actions a {
|
||||
color: #2271b1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.row-actions a:hover {
|
||||
color: #135e96;
|
||||
}
|
||||
|
||||
/* Dashboard Widget Styles */
|
||||
.dm-dashboard-widget {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dm-dashboard-widget > div {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dm-dashboard-widget > div:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dm-dashboard-widget h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1d2327;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dm-domain-primary {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dm-domain-primary a {
|
||||
text-decoration: none;
|
||||
color: #2271b1;
|
||||
}
|
||||
|
||||
.dm-domain-primary a:hover {
|
||||
color: #135e96;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dm-no-primary {
|
||||
color: #646970;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dm-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dm-stat {
|
||||
background: #f0f0f1;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dm-stat-value {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2271b1;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dm-stat-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #646970;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.dm-domains-list ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.dm-domains-list li {
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.dm-domains-list a {
|
||||
text-decoration: none;
|
||||
color: #50575e;
|
||||
}
|
||||
|
||||
.dm-domains-list a:hover {
|
||||
color: #2271b1;
|
||||
}
|
||||
|
||||
.dm-badge-primary {
|
||||
background: #2271b1;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.dm-health-status {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dm-refresh-health {
|
||||
padding: 2px 4px !important;
|
||||
min-height: 24px !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.dm-refresh-health .dashicons {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.dm-health-good {
|
||||
color: #46b450;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dm-health-issues {
|
||||
background: #fcf0f1;
|
||||
border: 1px solid #f0c4c6;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.dm-issue-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dm-issue-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dm-issue-list {
|
||||
color: #d63638;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dm-last-check {
|
||||
margin: 8px 0 0;
|
||||
font-size: 12px;
|
||||
color: #646970;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.dm-dns-info code {
|
||||
background: #f0f0f1;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dm-activity-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.dm-activity-list li {
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #f0f0f1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dm-activity-list li:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.dm-activity-action {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.dm-action-add {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
}
|
||||
|
||||
.dm-action-edit {
|
||||
background-color: #d9edf7;
|
||||
color: #31708f;
|
||||
}
|
||||
|
||||
.dm-action-delete {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
}
|
||||
|
||||
.dm-activity-domain {
|
||||
font-weight: 500;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.dm-activity-meta {
|
||||
display: block;
|
||||
color: #646970;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.dm-quick-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dm-quick-actions .button {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.dm-quick-actions .dashicons {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dm-loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dm-loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: -10px 0 0 -10px;
|
||||
border: 2px solid #f0f0f1;
|
||||
border-top-color: #2271b1;
|
||||
border-radius: 50%;
|
||||
animation: dm-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dm-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media screen and (max-width: 782px) {
|
||||
.domain-mapping-tabs {
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.domain-mapping-tab {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.domain-mapping-tab.active {
|
||||
border-color: #2271b1;
|
||||
border-left: 4px solid #2271b1;
|
||||
}
|
||||
|
||||
.domain-mapping-tab.active::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.domain-mapping-content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.search-form-field {
|
||||
|
@ -353,9 +840,11 @@
|
|||
max-width: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
|
@ -363,20 +852,25 @@
|
|||
padding: 0 0 15px;
|
||||
}
|
||||
|
||||
.domain-mapping-tabs {
|
||||
.dm-stats-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.dm-stat {
|
||||
padding: 8px 5px;
|
||||
}
|
||||
|
||||
.dm-stat-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.dm-quick-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.domain-mapping-tab {
|
||||
.dm-quick-actions .button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.domain-mapping-tab.active {
|
||||
border-bottom: 1px solid #007cba;
|
||||
border-left: 4px solid #007cba;
|
||||
}
|
||||
|
||||
/* Hide less important columns on mobile */
|
||||
|
@ -391,9 +885,19 @@
|
|||
.wp-list-table td.column-actions {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.tablenav {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tablenav-pages {
|
||||
margin-left: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 在文件末尾添加移动端优化 */
|
||||
@media screen and (max-width: 600px) {
|
||||
.wp-list-table {
|
||||
font-size: 12px;
|
||||
|
@ -405,11 +909,24 @@
|
|||
}
|
||||
|
||||
.domain-mapping-card {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.domain-mapping-content {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加验证消息样式 */
|
||||
/* Widget specific styles */
|
||||
#dm_domain_status_widget .postbox-header {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#dm_domain_status_widget.closed .inside {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Domain validation feedback */
|
||||
#domain-validation-message {
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
|
@ -421,3 +938,99 @@
|
|||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
#domain-validation-message.success {
|
||||
background-color: #d1e7dd;
|
||||
color: #0f5132;
|
||||
border: 1px solid #badbcc;
|
||||
}
|
||||
|
||||
/* Animation utilities */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-in {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(-20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accessibility improvements */
|
||||
.screen-reader-text {
|
||||
border: 0;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute !important;
|
||||
width: 1px;
|
||||
word-wrap: normal !important;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
.domain-mapping-tab:focus,
|
||||
.button:focus,
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
box-shadow: 0 0 0 2px #2271b1;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.button.loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.button.loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
border: 2px solid transparent;
|
||||
border-top-color: currentColor;
|
||||
border-radius: 50%;
|
||||
animation: dm-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* High contrast mode support */
|
||||
@media (prefers-contrast: high) {
|
||||
.domain-mapping-tab.active {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.notice {
|
||||
border-left-width: 6px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,11 +33,11 @@ jQuery(document).ready(function($) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Basic domain format validation
|
||||
var domainPattern = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$/;
|
||||
// Enhanced domain format validation - more flexible validation
|
||||
var domainPattern = /^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
|
||||
if (!domainPattern.test(domain)) {
|
||||
e.preventDefault();
|
||||
showNotice('#edit-domain-status', 'Please enter a valid domain format (e.g., example.com)', 'error');
|
||||
showNotice('#edit-domain-status', 'Please enter a valid domain format (e.g., example.com, www.example.com)', 'error');
|
||||
$('#domain').focus();
|
||||
return false;
|
||||
}
|
||||
|
@ -527,7 +527,7 @@ jQuery(document).ready(function($) {
|
|||
$feedback.html('<span style="color: #dc3232;">Please remove http:// or https://</span>').show();
|
||||
} else if (domain.indexOf('/') !== -1) {
|
||||
$feedback.html('<span style="color: #dc3232;">Please remove any paths or slashes</span>').show();
|
||||
} else if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$/.test(domain)) {
|
||||
} else if (!/^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]\.[a-zA-Z]{2,}$/.test(domain)) {
|
||||
$feedback.html('<span style="color: #dc3232;">Please enter a valid domain format</span>').show();
|
||||
} else {
|
||||
$feedback.html('<span style="color: #46b450;">Domain format looks good</span>').show();
|
||||
|
@ -586,10 +586,11 @@ jQuery(document).ready(function($) {
|
|||
// Initialize when document is ready
|
||||
initializePage();
|
||||
});
|
||||
|
||||
// Domain Mapping Dashboard Widget JavaScript
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
// 健康状态刷新按钮
|
||||
// Health status refresh button
|
||||
$(document).on('click', '.dm-refresh-health', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -600,7 +601,7 @@ jQuery(document).ready(function($) {
|
|||
$button.prop('disabled', true);
|
||||
$healthContent.addClass('dm-loading');
|
||||
|
||||
// 旋转图标
|
||||
// Rotate icon
|
||||
$button.find('.dashicons').addClass('dashicons-update-spin');
|
||||
|
||||
$.ajax({
|
||||
|
@ -615,7 +616,7 @@ jQuery(document).ready(function($) {
|
|||
if (response.success) {
|
||||
$healthContent.html(response.data.html);
|
||||
|
||||
// 显示成功消息
|
||||
// Show success message
|
||||
var $notice = $('<div class="notice notice-success is-dismissible"><p>' +
|
||||
response.data.message + '</p></div>');
|
||||
$('#dm_domain_status_widget .inside').prepend($notice);
|
||||
|
@ -635,7 +636,7 @@ jQuery(document).ready(function($) {
|
|||
});
|
||||
});
|
||||
|
||||
// 检查所有域名健康状态
|
||||
// Check all domain health status
|
||||
$(document).on('click', '.dm-check-all-health', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -658,12 +659,12 @@ jQuery(document).ready(function($) {
|
|||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// 刷新健康状态区域
|
||||
// Refresh health status area
|
||||
if ($('#dm-widget-health-status').length) {
|
||||
$('#dm-widget-health-status .dm-health-content').html(response.data.html);
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
// Show success message
|
||||
var $notice = $('<div class="notice notice-success is-dismissible"><p>' +
|
||||
response.data.message + '</p></div>');
|
||||
$('#dm_domain_status_widget .inside').prepend($notice);
|
||||
|
@ -686,27 +687,27 @@ jQuery(document).ready(function($) {
|
|||
});
|
||||
});
|
||||
|
||||
// 自动刷新(可选)
|
||||
// Auto-refresh (optional)
|
||||
if ($('#dm_domain_status_widget').length && $('#dm_domain_status_widget').is(':visible')) {
|
||||
// 每10分钟自动刷新一次
|
||||
// Auto-refresh every 10 minutes
|
||||
var autoRefreshInterval = setInterval(function() {
|
||||
if ($('#dm_domain_status_widget').is(':visible')) {
|
||||
$('.dm-refresh-health').first().trigger('click');
|
||||
}
|
||||
}, 600000); // 10分钟
|
||||
}, 600000); // 10 minutes
|
||||
|
||||
// 页面卸载时清除定时器
|
||||
// Clear timer on page unload
|
||||
$(window).on('beforeunload', function() {
|
||||
clearInterval(autoRefreshInterval);
|
||||
});
|
||||
}
|
||||
|
||||
// 小工具配置保存
|
||||
// Widget configuration save
|
||||
$(document).on('submit', '#dm_domain_status_widget form', function(e) {
|
||||
// 配置会自动保存,这里可以添加额外的处理
|
||||
// Configuration will auto-save, here we can add additional processing
|
||||
var showHealth = $('#dm_widget_show_health').is(':checked');
|
||||
|
||||
// 如果取消勾选健康状态,隐藏相关区域
|
||||
// If unchecked health status, hide related areas
|
||||
if (!showHealth) {
|
||||
$('#dm-widget-health-status').fadeOut();
|
||||
} else {
|
||||
|
@ -714,14 +715,14 @@ jQuery(document).ready(function($) {
|
|||
}
|
||||
});
|
||||
|
||||
// 为域名链接添加外部图标
|
||||
// Add external icons for domain links
|
||||
$('.dm-domains-list a, .dm-domain-primary a').each(function() {
|
||||
if (!$(this).find('.dashicons-external').length) {
|
||||
$(this).append(' <span class="dashicons dashicons-external" style="font-size: 14px; vertical-align: middle;"></span>');
|
||||
}
|
||||
});
|
||||
|
||||
// 工具提示
|
||||
// Tooltips
|
||||
if ($.fn.tooltip) {
|
||||
$('#dm_domain_status_widget [title]').tooltip({
|
||||
position: {
|
||||
|
|
|
@ -822,173 +822,205 @@ class WP_Domain_Mapping_Admin {
|
|||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for domain actions
|
||||
* UPDATED: Support editing domain names and better conflict checking
|
||||
* AJAX handler for domain actions - IMPROVED SECURITY
|
||||
*/
|
||||
public function ajax_handle_actions() {
|
||||
check_ajax_referer( 'domain_mapping', 'nonce' );
|
||||
// Verify nonce first
|
||||
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'domain_mapping' ) ) {
|
||||
wp_send_json_error( __( 'Security check failed.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
if ( ! current_user_can( 'manage_network' ) ) {
|
||||
wp_send_json_error( __( 'Permission denied.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$action = sanitize_text_field( $_POST['action_type'] );
|
||||
$domain = dm_clean_domain( sanitize_text_field( isset( $_POST['domain'] ) ? strtolower( $_POST['domain'] ) : '' ) );
|
||||
// Sanitize and validate inputs
|
||||
$action = isset( $_POST['action_type'] ) ? sanitize_key( $_POST['action_type'] ) : '';
|
||||
$domain = isset( $_POST['domain'] ) ? dm_clean_domain( sanitize_text_field( $_POST['domain'] ) ) : '';
|
||||
$blog_id = isset( $_POST['blog_id'] ) ? absint( $_POST['blog_id'] ) : 0;
|
||||
$active = isset( $_POST['active'] ) ? absint( $_POST['active'] ) : 0;
|
||||
$orig_domain = isset( $_POST['orig_domain'] ) ? dm_clean_domain( sanitize_text_field( $_POST['orig_domain'] ) ) : '';
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Validate action type
|
||||
$allowed_actions = array( 'save', 'delete' );
|
||||
if ( ! in_array( $action, $allowed_actions ) ) {
|
||||
wp_send_json_error( __( 'Invalid action.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
switch ( $action ) {
|
||||
case 'save':
|
||||
if ( $blog_id != 0 && $blog_id != 1 ) {
|
||||
// Validate domain format
|
||||
if ( ! dm_validate_domain( $domain ) ) {
|
||||
wp_send_json_error( __( 'Invalid domain format.', 'wp-domain-mapping' ) );
|
||||
// Enhanced validation for save action
|
||||
if ( $blog_id <= 0 || $blog_id === 1 ) {
|
||||
wp_send_json_error( __( 'Invalid site ID.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
// Validate domain format
|
||||
if ( empty( $domain ) || ! dm_validate_domain( $domain ) ) {
|
||||
wp_send_json_error( __( 'Invalid domain format.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
// Check if blog exists
|
||||
if ( ! get_blog_details( $blog_id ) ) {
|
||||
wp_send_json_error( __( 'Site does not exist.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
// For editing, check if domain changed
|
||||
$domain_changed = ! empty( $orig_domain ) && $orig_domain !== $domain;
|
||||
|
||||
if ( $domain_changed || empty( $orig_domain ) ) {
|
||||
// Check if domain exists for another blog
|
||||
$exists = dm_domain_exists_for_another_blog( $domain, $blog_id );
|
||||
|
||||
if ( $exists ) {
|
||||
wp_send_json_error( sprintf(
|
||||
__( 'Domain %s is already mapped to site ID %d.', 'wp-domain-mapping' ),
|
||||
esc_html( $domain ),
|
||||
intval( $exists->blog_id )
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// For editing, check if domain changed
|
||||
$domain_changed = ! empty( $orig_domain ) && $orig_domain !== $domain;
|
||||
$wpdb->query( 'START TRANSACTION' );
|
||||
|
||||
if ( $domain_changed || empty( $orig_domain ) ) {
|
||||
// Check if domain exists for another blog
|
||||
$exists = dm_domain_exists_for_another_blog( $domain, $blog_id );
|
||||
|
||||
if ( $exists ) {
|
||||
wp_send_json_error( sprintf(
|
||||
__( 'Domain %s is already mapped to site ID %d.', 'wp-domain-mapping' ),
|
||||
$domain,
|
||||
$exists->blog_id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$wpdb->query( 'START TRANSACTION' );
|
||||
|
||||
try {
|
||||
if ( empty( $orig_domain ) ) {
|
||||
// Insert new domain
|
||||
$success = $wpdb->insert(
|
||||
$this->tables['domains'],
|
||||
array(
|
||||
'blog_id' => $blog_id,
|
||||
'domain' => $domain,
|
||||
'active' => $active
|
||||
),
|
||||
array( '%d', '%s', '%d' )
|
||||
);
|
||||
|
||||
if ( $success ) {
|
||||
// If setting as primary, reset other domains
|
||||
if ( $active ) {
|
||||
$wpdb->update(
|
||||
$this->tables['domains'],
|
||||
array( 'active' => 0 ),
|
||||
array(
|
||||
'blog_id' => $blog_id,
|
||||
'domain !=' => $domain
|
||||
),
|
||||
array( '%d' ),
|
||||
array( '%d', '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
dm_log_action( 'add', $domain, $blog_id, $current_user_id );
|
||||
|
||||
$wpdb->query( 'COMMIT' );
|
||||
wp_send_json_success( __( 'Domain added successfully.', 'wp-domain-mapping' ) );
|
||||
} else {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
wp_send_json_error( __( 'Failed to add domain.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
// Update existing domain
|
||||
|
||||
// Prepare update data
|
||||
$update_data = array(
|
||||
try {
|
||||
if ( empty( $orig_domain ) ) {
|
||||
// Insert new domain
|
||||
$success = $wpdb->insert(
|
||||
$this->tables['domains'],
|
||||
array(
|
||||
'blog_id' => $blog_id,
|
||||
'domain' => $domain,
|
||||
'active' => $active
|
||||
);
|
||||
$update_format = array( '%d', '%d' );
|
||||
),
|
||||
array( '%d', '%s', '%d' )
|
||||
);
|
||||
|
||||
// If domain changed, update it
|
||||
if ( $domain_changed ) {
|
||||
$update_data['domain'] = $domain;
|
||||
$update_format[] = '%s';
|
||||
}
|
||||
|
||||
// If setting as primary, reset other domains first
|
||||
if ( $success ) {
|
||||
// If setting as primary, reset other domains
|
||||
if ( $active ) {
|
||||
$wpdb->update(
|
||||
$this->tables['domains'],
|
||||
array( 'active' => 0 ),
|
||||
array( 'blog_id' => $blog_id ),
|
||||
array(
|
||||
'blog_id' => $blog_id,
|
||||
'domain' => array( 'NOT LIKE', $domain )
|
||||
),
|
||||
array( '%d' ),
|
||||
array( '%d' )
|
||||
);
|
||||
}
|
||||
|
||||
$success = $wpdb->update(
|
||||
$this->tables['domains'],
|
||||
$update_data,
|
||||
array( 'domain' => $orig_domain ),
|
||||
$update_format,
|
||||
array( '%s' )
|
||||
);
|
||||
// Log the action
|
||||
dm_log_action( 'add', $domain, $blog_id, $current_user_id );
|
||||
|
||||
if ( $success !== false ) {
|
||||
// Log the action
|
||||
dm_log_action( 'edit', $domain, $blog_id, $current_user_id );
|
||||
|
||||
$wpdb->query( 'COMMIT' );
|
||||
wp_send_json_success( __( 'Domain updated successfully.', 'wp-domain-mapping' ) );
|
||||
} else {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
wp_send_json_error( __( 'No changes were made or update failed.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
$wpdb->query( 'COMMIT' );
|
||||
wp_send_json_success( __( 'Domain added successfully.', 'wp-domain-mapping' ) );
|
||||
} else {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
wp_send_json_error( __( 'Failed to add domain.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
// Validate original domain exists
|
||||
$orig_exists = dm_get_domain_by_name( $orig_domain );
|
||||
if ( ! $orig_exists || $orig_exists->blog_id != $blog_id ) {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
wp_send_json_error( __( 'Original domain not found or access denied.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
// Update existing domain
|
||||
$update_data = array(
|
||||
'blog_id' => $blog_id,
|
||||
'active' => $active
|
||||
);
|
||||
$update_format = array( '%d', '%d' );
|
||||
|
||||
// If domain changed, update it
|
||||
if ( $domain_changed ) {
|
||||
$update_data['domain'] = $domain;
|
||||
$update_format[] = '%s';
|
||||
}
|
||||
|
||||
// If setting as primary, reset other domains first
|
||||
if ( $active ) {
|
||||
$wpdb->update(
|
||||
$this->tables['domains'],
|
||||
array( 'active' => 0 ),
|
||||
array( 'blog_id' => $blog_id ),
|
||||
array( '%d' ),
|
||||
array( '%d' )
|
||||
);
|
||||
}
|
||||
|
||||
$success = $wpdb->update(
|
||||
$this->tables['domains'],
|
||||
$update_data,
|
||||
array( 'domain' => $orig_domain ),
|
||||
$update_format,
|
||||
array( '%s' )
|
||||
);
|
||||
|
||||
if ( $success !== false ) {
|
||||
// Log the action
|
||||
dm_log_action( 'edit', $domain, $blog_id, $current_user_id );
|
||||
|
||||
$wpdb->query( 'COMMIT' );
|
||||
wp_send_json_success( __( 'Domain updated successfully.', 'wp-domain-mapping' ) );
|
||||
} else {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
wp_send_json_error( __( 'No changes were made or update failed.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
wp_send_json_error( __( 'An error occurred while saving domain.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
wp_send_json_error( __( 'Invalid site ID.', 'wp-domain-mapping' ) );
|
||||
} catch ( Exception $e ) {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
error_log( 'Domain mapping error: ' . $e->getMessage() );
|
||||
wp_send_json_error( __( 'An error occurred while saving domain.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$domains = isset( $_POST['domains'] ) ? array_map( 'sanitize_text_field', (array) $_POST['domains'] ) : array( $domain );
|
||||
|
||||
// Validate domains array
|
||||
$domains = array_filter( $domains, function( $d ) {
|
||||
return ! empty( $d ) && dm_validate_domain( $d );
|
||||
});
|
||||
|
||||
if ( empty( $domains ) ) {
|
||||
wp_send_json_error( __( 'No valid domains provided for deletion.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
$wpdb->query( 'START TRANSACTION' );
|
||||
$deleted = 0;
|
||||
|
||||
try {
|
||||
foreach ( $domains as $del_domain ) {
|
||||
if ( empty( $del_domain ) ) continue;
|
||||
// Get domain info before deletion for logging and validation
|
||||
$domain_info = dm_get_domain_by_name( $del_domain );
|
||||
|
||||
// Get blog_id before deletion for logging
|
||||
$affected_blog_id = $wpdb->get_var( $wpdb->prepare(
|
||||
"SELECT blog_id FROM {$this->tables['domains']} WHERE domain = %s",
|
||||
$del_domain
|
||||
));
|
||||
if ( ! $domain_info ) {
|
||||
continue; // Skip non-existent domains
|
||||
}
|
||||
|
||||
if ( $affected_blog_id ) {
|
||||
// Delete the domain
|
||||
$result = $wpdb->delete(
|
||||
$this->tables['domains'],
|
||||
array( 'domain' => $del_domain ),
|
||||
array( '%s' )
|
||||
);
|
||||
// Check if user has permission to delete this domain
|
||||
if ( ! current_user_can( 'manage_network' ) ) {
|
||||
continue; // Skip if no permission
|
||||
}
|
||||
|
||||
if ( $result ) {
|
||||
$deleted++;
|
||||
// Delete the domain
|
||||
$result = $wpdb->delete(
|
||||
$this->tables['domains'],
|
||||
array( 'domain' => $del_domain ),
|
||||
array( '%s' )
|
||||
);
|
||||
|
||||
// Log the action
|
||||
dm_log_action( 'delete', $del_domain, $affected_blog_id, $current_user_id );
|
||||
}
|
||||
if ( $result ) {
|
||||
$deleted++;
|
||||
// Log the action
|
||||
dm_log_action( 'delete', $del_domain, $domain_info->blog_id, $current_user_id );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1010,6 +1042,7 @@ class WP_Domain_Mapping_Admin {
|
|||
}
|
||||
} catch ( Exception $e ) {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
error_log( 'Domain mapping deletion error: ' . $e->getMessage() );
|
||||
wp_send_json_error( __( 'An error occurred while deleting domains.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
break;
|
||||
|
@ -1313,7 +1346,7 @@ class WP_Domain_Mapping_Admin {
|
|||
if ( ! get_site_option( 'dm_ipaddress' ) && ! get_site_option( 'dm_cname' ) ) {
|
||||
if ( dm_is_site_admin() ) {
|
||||
echo wp_kses(
|
||||
__( "Please set the IP address or CNAME of your server in the <a href='wpmu-admin.php?page=domain-mapping'>site admin page</a>.", 'wp-domain-mapping' ),
|
||||
__( "Please set the IP address or CNAME of your server in the <a href='wp-admin/network/settings.php?page=domain-mapping'>site admin page</a>.", 'wp-domain-mapping' ),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
);
|
||||
} else {
|
||||
|
@ -1355,7 +1388,7 @@ class WP_Domain_Mapping_Admin {
|
|||
* @return array Modified columns
|
||||
*/
|
||||
public function add_domain_mapping_columns( $columns ) {
|
||||
$columns['map'] = __( 'Mapping' );
|
||||
$columns['map'] = __( 'Mapping', 'wp-domain-mapping' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ class WP_Domain_Mapping_Core {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create required database tables
|
||||
* Create required database tables - OPTIMIZED
|
||||
*/
|
||||
public function create_tables() {
|
||||
global $wpdb;
|
||||
|
@ -127,7 +127,7 @@ class WP_Domain_Mapping_Core {
|
|||
$created = 0;
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
// Create domain_mapping table
|
||||
// Create domain_mapping table - IMPROVED INDEXES
|
||||
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->tables['domains']}'" ) != $this->tables['domains'] ) {
|
||||
$wpdb->query( "CREATE TABLE IF NOT EXISTS `{$this->tables['domains']}` (
|
||||
`id` bigint(20) NOT NULL auto_increment,
|
||||
|
@ -135,24 +135,29 @@ class WP_Domain_Mapping_Core {
|
|||
`domain` varchar(255) NOT NULL,
|
||||
`active` tinyint(4) default '1',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `blog_id` (`blog_id`,`domain`,`active`)
|
||||
UNIQUE KEY `domain` (`domain`),
|
||||
KEY `blog_id` (`blog_id`),
|
||||
KEY `blog_active` (`blog_id`, `active`),
|
||||
KEY `active_domain` (`active`, `domain`)
|
||||
) $charset_collate;" );
|
||||
$created = 1;
|
||||
}
|
||||
|
||||
// Create domain_mapping_logins table
|
||||
// Create domain_mapping_logins table - IMPROVED INDEXES
|
||||
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->tables['logins']}'" ) != $this->tables['logins'] ) {
|
||||
$wpdb->query( "CREATE TABLE IF NOT EXISTS `{$this->tables['logins']}` (
|
||||
`id` varchar(32) NOT NULL,
|
||||
`user_id` bigint(20) NOT NULL,
|
||||
`blog_id` bigint(20) NOT NULL,
|
||||
`t` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_blog` (`user_id`, `blog_id`),
|
||||
KEY `timestamp` (`t`)
|
||||
) $charset_collate;" );
|
||||
$created = 1;
|
||||
}
|
||||
|
||||
// Create domain_mapping_logs table
|
||||
// Create domain_mapping_logs table - IMPROVED INDEXES
|
||||
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->tables['logs']}'" ) != $this->tables['logs'] ) {
|
||||
$wpdb->query( "CREATE TABLE IF NOT EXISTS `{$this->tables['logs']}` (
|
||||
`id` bigint(20) NOT NULL auto_increment,
|
||||
|
@ -161,7 +166,11 @@ class WP_Domain_Mapping_Core {
|
|||
`domain` varchar(255) NOT NULL,
|
||||
`blog_id` bigint(20) NOT NULL,
|
||||
`timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `blog_timestamp` (`blog_id`, `timestamp`),
|
||||
KEY `user_action` (`user_id`, `action`),
|
||||
KEY `domain_action` (`domain`, `action`),
|
||||
KEY `timestamp` (`timestamp`)
|
||||
) $charset_collate;" );
|
||||
$created = 1;
|
||||
}
|
||||
|
@ -472,109 +481,115 @@ class WP_Domain_Mapping_Core {
|
|||
/**
|
||||
* Handle remote login JS
|
||||
*/
|
||||
public function remote_login_js() {
|
||||
global $current_blog, $current_user, $wpdb;
|
||||
public function remote_login_js() {
|
||||
global $current_blog, $current_user, $wpdb;
|
||||
|
||||
if (strtotime($details->t) < (time() - 300)) {
|
||||
wp_die(__('Login key expired', 'wp-domain-mapping'));
|
||||
}
|
||||
if ( 0 == get_site_option( 'dm_remote_login' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 0 == get_site_option( 'dm_remote_login' ) ) {
|
||||
return;
|
||||
}
|
||||
$hash = $this->get_hash();
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
|
||||
$hash = $this->get_hash();
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
if ( $_GET['dm'] == $hash ) {
|
||||
if ( $_GET['action'] == 'load' ) {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $_GET['dm'] == $hash ) {
|
||||
if ( $_GET['action'] == 'load' ) {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
exit;
|
||||
}
|
||||
$key = md5( time() . mt_rand() );
|
||||
|
||||
$key = md5( time() . mt_rand() );
|
||||
$wpdb->insert(
|
||||
$this->tables['logins'],
|
||||
array(
|
||||
'id' => $key,
|
||||
'user_id' => $current_user->ID,
|
||||
'blog_id' => $_GET['blogid'],
|
||||
't' => current_time( 'mysql' )
|
||||
),
|
||||
array( '%s', '%d', '%d', '%s' )
|
||||
);
|
||||
|
||||
$wpdb->insert(
|
||||
$this->tables['logins'],
|
||||
array(
|
||||
'id' => $key,
|
||||
'user_id' => $current_user->ID,
|
||||
'blog_id' => $_GET['blogid'],
|
||||
't' => current_time( 'mysql' )
|
||||
),
|
||||
array( '%s', '%d', '%d', '%s' )
|
||||
);
|
||||
$url = add_query_arg(
|
||||
array(
|
||||
'action' => 'login',
|
||||
'dm' => $hash,
|
||||
'k' => $key,
|
||||
't' => mt_rand()
|
||||
),
|
||||
$_GET['back']
|
||||
);
|
||||
|
||||
$url = add_query_arg(
|
||||
array(
|
||||
'action' => 'login',
|
||||
'dm' => $hash,
|
||||
'k' => $key,
|
||||
't' => mt_rand()
|
||||
),
|
||||
$_GET['back']
|
||||
);
|
||||
echo "window.location = '" . esc_url( $url ) . "'";
|
||||
exit;
|
||||
|
||||
echo "window.location = '" . esc_url( $url ) . "'";
|
||||
exit;
|
||||
} elseif ( $_GET['action'] == 'login' ) {
|
||||
$details = $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$this->tables['logins']} WHERE id = %s AND blog_id = %d",
|
||||
$_GET['k'], $wpdb->blogid
|
||||
));
|
||||
|
||||
} elseif ( $_GET['action'] == 'login' ) {
|
||||
$details = $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$this->tables['logins']} WHERE id = %s AND blog_id = %d",
|
||||
$_GET['k'], $wpdb->blogid
|
||||
));
|
||||
if ( $details ) {
|
||||
// FIX: Add time validation that was missing
|
||||
if ( strtotime( $details->t ) < ( time() - 300 ) ) {
|
||||
wp_die( __( 'Login key expired', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
if ( $details ) {
|
||||
if ( $details->blog_id == $wpdb->blogid ) {
|
||||
$wpdb->delete(
|
||||
$this->tables['logins'],
|
||||
array( 'id' => $_GET['k'] ),
|
||||
array( '%s' )
|
||||
);
|
||||
if ( $details->blog_id == $wpdb->blogid ) {
|
||||
$wpdb->delete(
|
||||
$this->tables['logins'],
|
||||
array( 'id' => $_GET['k'] ),
|
||||
array( '%s' )
|
||||
);
|
||||
|
||||
$wpdb->query( $wpdb->prepare(
|
||||
"DELETE FROM {$this->tables['logins']} WHERE t < %s",
|
||||
date( 'Y-m-d H:i:s', time() - 120 )
|
||||
));
|
||||
$wpdb->query( $wpdb->prepare(
|
||||
"DELETE FROM {$this->tables['logins']} WHERE t < %s",
|
||||
date( 'Y-m-d H:i:s', time() - 120 )
|
||||
));
|
||||
|
||||
wp_set_auth_cookie( $details->user_id );
|
||||
wp_set_auth_cookie( $details->user_id );
|
||||
|
||||
wp_redirect( remove_query_arg(
|
||||
array( 'dm', 'action', 'k', 't' ),
|
||||
$protocol . $current_blog->domain . $_SERVER['REQUEST_URI']
|
||||
));
|
||||
exit;
|
||||
wp_redirect( remove_query_arg(
|
||||
array( 'dm', 'action', 'k', 't' ),
|
||||
$protocol . $current_blog->domain . $_SERVER['REQUEST_URI']
|
||||
));
|
||||
exit;
|
||||
|
||||
} else {
|
||||
wp_die( esc_html__( "Incorrect or out of date login key", 'wp-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
wp_die( esc_html__( "Unknown login key", 'wp-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
wp_die( esc_html__( "Incorrect or out of date login key", 'wp-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
wp_die( esc_html__( "Unknown login key", 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
} elseif ( $_GET['action'] == 'logout' ) {
|
||||
$details = $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$this->tables['logins']} WHERE id = %s AND blog_id = %d",
|
||||
$_GET['k'], $_GET['blogid']
|
||||
));
|
||||
} elseif ( $_GET['action'] == 'logout' ) {
|
||||
$details = $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$this->tables['logins']} WHERE id = %s AND blog_id = %d",
|
||||
$_GET['k'], $_GET['blogid']
|
||||
));
|
||||
|
||||
if ( $details ) {
|
||||
$wpdb->delete(
|
||||
$this->tables['logins'],
|
||||
array( 'id' => $_GET['k'] ),
|
||||
array( '%s' )
|
||||
);
|
||||
if ( $details ) {
|
||||
// FIX: Add time validation for logout too
|
||||
if ( strtotime( $details->t ) < ( time() - 300 ) ) {
|
||||
wp_die( __( 'Logout key expired', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
$blog = get_blog_details( $_GET['blogid'] );
|
||||
wp_clear_auth_cookie();
|
||||
wp_redirect( trailingslashit( $blog->siteurl ) . "wp-login.php?loggedout=true" );
|
||||
exit;
|
||||
} else {
|
||||
wp_die( esc_html__( "Unknown logout key", 'wp-domain-mapping' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$wpdb->delete(
|
||||
$this->tables['logins'],
|
||||
array( 'id' => $_GET['k'] ),
|
||||
array( '%s' )
|
||||
);
|
||||
|
||||
$blog = get_blog_details( $_GET['blogid'] );
|
||||
wp_clear_auth_cookie();
|
||||
wp_redirect( trailingslashit( $blog->siteurl ) . "wp-login.php?loggedout=true" );
|
||||
exit;
|
||||
} else {
|
||||
wp_die( esc_html__( "Unknown logout key", 'wp-domain-mapping' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add JS loader for remote login
|
||||
|
|
|
@ -94,6 +94,14 @@ class WP_Domain_Mapping_Tools {
|
|||
add_action( 'admin_init', array( $this, 'handle_export' ) );
|
||||
add_action( 'admin_init', array( $this, 'handle_health_manual_check' ) );
|
||||
add_action( 'admin_init', array( $this, 'handle_health_settings_save' ) );
|
||||
|
||||
// Add debug hooks
|
||||
add_action( 'admin_notices', array( $this, 'show_cron_debug_info' ) );
|
||||
|
||||
// Add manual trigger cron hook (only for debugging)
|
||||
if ( isset( $_GET['dm_test_cron'] ) && current_user_can( 'manage_network' ) ) {
|
||||
add_action( 'admin_init', array( $this, 'test_cron_execution' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -278,32 +286,56 @@ class WP_Domain_Mapping_Tools {
|
|||
}
|
||||
|
||||
// ========================================
|
||||
// Health Check Functions
|
||||
// Health Check Functions - IMPROVED
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Schedule health check
|
||||
* Schedule health check - improved version
|
||||
*/
|
||||
public function schedule_health_check() {
|
||||
if ( ! wp_next_scheduled( 'dm_domain_health_check' ) ) {
|
||||
wp_schedule_event( time(), 'daily', 'dm_domain_health_check' );
|
||||
// First clear existing schedules
|
||||
$this->unschedule_health_check();
|
||||
|
||||
// Check if setting is enabled
|
||||
if ( ! get_site_option( 'dm_health_check_enabled', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set next execution time (tomorrow at 2:00 AM)
|
||||
$next_run = strtotime( 'tomorrow 2:00 AM' );
|
||||
|
||||
// Schedule event
|
||||
$scheduled = wp_schedule_event( $next_run, 'daily', 'dm_domain_health_check' );
|
||||
|
||||
// Log scheduling status
|
||||
if ( $scheduled !== false ) {
|
||||
error_log( 'WP Domain Mapping: Health check scheduled for ' . date( 'Y-m-d H:i:s', $next_run ) );
|
||||
} else {
|
||||
error_log( 'WP Domain Mapping: Failed to schedule health check' );
|
||||
}
|
||||
|
||||
// Also set an option to track scheduling status
|
||||
update_site_option( 'dm_health_check_scheduled', array(
|
||||
'scheduled_at' => current_time( 'timestamp' ),
|
||||
'next_run' => $next_run,
|
||||
'status' => $scheduled !== false ? 'scheduled' : 'failed'
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule health check
|
||||
* Unschedule health check - improved version
|
||||
*/
|
||||
public function unschedule_health_check() {
|
||||
$timestamp = wp_next_scheduled( 'dm_domain_health_check' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'dm_domain_health_check' );
|
||||
}
|
||||
// Clear main health check
|
||||
wp_clear_scheduled_hook( 'dm_domain_health_check' );
|
||||
|
||||
// Also clear any batch processing
|
||||
$timestamp = wp_next_scheduled( 'dm_domain_health_check_batch' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'dm_domain_health_check_batch' );
|
||||
}
|
||||
// Clear batch processing health check
|
||||
wp_clear_scheduled_hook( 'dm_domain_health_check_batch' );
|
||||
|
||||
// Clear scheduling status option
|
||||
delete_site_option( 'dm_health_check_scheduled' );
|
||||
|
||||
error_log( 'WP Domain Mapping: Health check unscheduled' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,15 +475,25 @@ class WP_Domain_Mapping_Tools {
|
|||
}
|
||||
|
||||
/**
|
||||
* Scheduled health check
|
||||
* Scheduled health check - improved version
|
||||
*/
|
||||
public function scheduled_health_check() {
|
||||
// Log execution start
|
||||
error_log( 'WP Domain Mapping: Starting scheduled health check at ' . current_time( 'mysql' ) );
|
||||
|
||||
// Check if health checks are enabled
|
||||
if ( ! get_site_option( 'dm_health_check_enabled', true ) ) {
|
||||
error_log( 'WP Domain Mapping: Health check is disabled, skipping' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Update last execution time
|
||||
update_site_option( 'dm_last_health_check', current_time( 'timestamp' ) );
|
||||
|
||||
// Start batch processing
|
||||
$this->start_health_check_batch();
|
||||
|
||||
error_log( 'WP Domain Mapping: Scheduled health check batch started' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -821,6 +863,73 @@ class WP_Domain_Mapping_Tools {
|
|||
wp_mail( $notification_email, $subject, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new method: check cron status
|
||||
*/
|
||||
public function get_cron_status() {
|
||||
$scheduled_info = get_site_option( 'dm_health_check_scheduled', false );
|
||||
$next_scheduled = wp_next_scheduled( 'dm_domain_health_check' );
|
||||
$last_check = get_site_option( 'dm_last_health_check', false );
|
||||
|
||||
return array(
|
||||
'scheduled_info' => $scheduled_info,
|
||||
'next_scheduled' => $next_scheduled,
|
||||
'next_scheduled_formatted' => $next_scheduled ? date( 'Y-m-d H:i:s', $next_scheduled ) : 'Not scheduled',
|
||||
'last_check' => $last_check,
|
||||
'last_check_formatted' => $last_check ? date( 'Y-m-d H:i:s', $last_check ) : 'Never',
|
||||
'wp_cron_disabled' => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON,
|
||||
'cron_running' => ! defined( 'DISABLE_WP_CRON' ) || ! DISABLE_WP_CRON
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show cron debug info (only show on network admin pages)
|
||||
*/
|
||||
public function show_cron_debug_info() {
|
||||
if ( ! is_network_admin() || ! current_user_can( 'manage_network' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_screen = get_current_screen();
|
||||
if ( ! $current_screen || $current_screen->id !== 'settings_page_domain-mapping-network' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = $this->get_cron_status();
|
||||
|
||||
if ( $status['wp_cron_disabled'] ) {
|
||||
echo '<div class="notice notice-warning"><p>';
|
||||
echo '<strong>WP Domain Mapping:</strong> WordPress Cron is disabled (DISABLE_WP_CRON = true). ';
|
||||
echo 'Automatic health checks will not work. Please set up a system cron job.';
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
// Show next execution time (only in debug mode)
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
echo '<div class="notice notice-info"><p>';
|
||||
echo '<strong>Health Check Status:</strong><br>';
|
||||
echo 'Next scheduled: ' . esc_html( $status['next_scheduled_formatted'] ) . '<br>';
|
||||
echo 'Last check: ' . esc_html( $status['last_check_formatted'] ) . '<br>';
|
||||
echo '<a href="' . add_query_arg( 'dm_test_cron', '1' ) . '">Test Cron Execution</a>';
|
||||
echo '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cron execution (only for debugging)
|
||||
*/
|
||||
public function test_cron_execution() {
|
||||
if ( ! isset( $_GET['dm_test_cron'] ) || ! current_user_can( 'manage_network' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Manually trigger health check
|
||||
$this->scheduled_health_check();
|
||||
|
||||
wp_redirect( remove_query_arg( 'dm_test_cron' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Import/Export Functions
|
||||
// ========================================
|
||||
|
@ -899,7 +1008,7 @@ class WP_Domain_Mapping_Tools {
|
|||
wp_send_json_error( __( 'Invalid security token. Please try again.', 'wp-domain-mapping' ) );
|
||||
}
|
||||
|
||||
// 在处理文件之前添加:
|
||||
// Check file size before processing
|
||||
if ($_FILES['csv_file']['size'] > 5 * 1024 * 1024) {
|
||||
wp_send_json_error(__('File size exceeds 5MB limit.', 'wp-domain-mapping'));
|
||||
return;
|
||||
|
@ -915,7 +1024,7 @@ class WP_Domain_Mapping_Tools {
|
|||
$update_existing = isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false;
|
||||
$validate_sites = isset( $_POST['validate_sites'] ) ? (bool) $_POST['validate_sites'] : true;
|
||||
|
||||
// 验证文件类型
|
||||
// Validate file type
|
||||
$file_type = wp_check_filetype($_FILES['csv_file']['name']);
|
||||
if (!in_array($file_type['ext'], array('csv', 'txt'))) {
|
||||
wp_send_json_error(__('Only CSV files are allowed.', 'wp-domain-mapping'));
|
||||
|
|
|
@ -25,7 +25,7 @@ function dm_ensure_protocol( $domain ) {
|
|||
|
||||
/**
|
||||
* Clean domain name (remove protocol and trailing slash)
|
||||
* UPDATED: Keep www prefix to distinguish www.domain.com from domain.com
|
||||
* UPDATED: Improved domain cleaning function, preserves www prefix
|
||||
*
|
||||
* @param string $domain Domain name
|
||||
* @return string Cleaned domain
|
||||
|
@ -49,10 +49,15 @@ function dm_clean_domain( $domain ) {
|
|||
if ( function_exists( 'idn_to_ascii' ) && preg_match( '/[^a-z0-9\-\.]/i', $domain ) ) {
|
||||
if (defined('INTL_IDNA_VARIANT_UTS46')) {
|
||||
// PHP 7.2+
|
||||
$domain = idn_to_ascii( $domain, 0, INTL_IDNA_VARIANT_UTS46 );
|
||||
$ascii_domain = idn_to_ascii( $domain, 0, INTL_IDNA_VARIANT_UTS46 );
|
||||
} else {
|
||||
// PHP < 7.2
|
||||
$domain = idn_to_ascii( $domain );
|
||||
$ascii_domain = idn_to_ascii( $domain );
|
||||
}
|
||||
|
||||
// Only use converted result if conversion was successful
|
||||
if ($ascii_domain !== false) {
|
||||
$domain = $ascii_domain;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,14 +66,82 @@ function dm_clean_domain( $domain ) {
|
|||
|
||||
/**
|
||||
* Validate a domain name
|
||||
* UPDATED: Accept www prefix as valid
|
||||
* UPDATED: More flexible domain validation, supports www prefix and more valid formats
|
||||
*
|
||||
* @param string $domain The domain
|
||||
* @return bool True if valid
|
||||
*/
|
||||
function dm_validate_domain( $domain ) {
|
||||
// Basic validation - now accepts www prefix
|
||||
return (bool) preg_match( '/^(www\.)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i', $domain );
|
||||
// Remove possible protocol and path
|
||||
$domain = preg_replace('#^https?://#', '', $domain);
|
||||
$domain = preg_replace('#/.*$#', '', $domain);
|
||||
$domain = trim($domain);
|
||||
|
||||
// Check if empty
|
||||
if (empty($domain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check length limit (RFC 1035)
|
||||
if (strlen($domain) > 253) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if contains only valid characters (letters, numbers, dots, hyphens)
|
||||
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $domain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if starts or ends with dot
|
||||
if (substr($domain, 0, 1) === '.' || substr($domain, -1) === '.') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for consecutive dots
|
||||
if (strpos($domain, '..') !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split domain into parts
|
||||
$parts = explode('.', $domain);
|
||||
|
||||
// Need at least two parts (domain.tld)
|
||||
if (count($parts) < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each part
|
||||
foreach ($parts as $part) {
|
||||
// Each part cannot be empty
|
||||
if (empty($part)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each part cannot exceed 63 characters (RFC 1035)
|
||||
if (strlen($part) > 63) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each part cannot start or end with hyphen
|
||||
if (substr($part, 0, 1) === '-' || substr($part, -1) === '-') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each part can only contain letters, numbers and hyphens
|
||||
if (!preg_match('/^[a-zA-Z0-9-]+$/', $part)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check TLD (last part) validity
|
||||
$tld = end($parts);
|
||||
|
||||
// TLD needs at least 2 characters and can only contain letters
|
||||
if (strlen($tld) < 2 || !preg_match('/^[a-zA-Z]+$/', $tld)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,20 +516,302 @@ function dm_format_action_name( $action ) {
|
|||
}
|
||||
}
|
||||
|
||||
// Cache functions
|
||||
/**
|
||||
* Enhanced caching functions for better performance
|
||||
*/
|
||||
|
||||
// Cache functions - IMPROVED
|
||||
function dm_get_domains_by_blog_id_cached($blog_id) {
|
||||
$cache_key = 'dm_domains_' . $blog_id;
|
||||
$domains = wp_cache_get($cache_key, 'domain_mapping');
|
||||
$cache_group = 'domain_mapping';
|
||||
|
||||
$domains = wp_cache_get($cache_key, $cache_group);
|
||||
|
||||
if (false === $domains) {
|
||||
$domains = dm_get_domains_by_blog_id($blog_id);
|
||||
wp_cache_set($cache_key, $domains, 'domain_mapping', HOUR_IN_SECONDS);
|
||||
// Cache for 1 hour, or until manually cleared
|
||||
wp_cache_set($cache_key, $domains, $cache_group, HOUR_IN_SECONDS);
|
||||
}
|
||||
|
||||
return $domains;
|
||||
}
|
||||
|
||||
// Clear cache function
|
||||
function dm_clear_domain_cache($blog_id) {
|
||||
wp_cache_delete('dm_domains_' . $blog_id, 'domain_mapping');
|
||||
// Clear cache function - IMPROVED
|
||||
function dm_clear_domain_cache($blog_id = null) {
|
||||
$cache_group = 'domain_mapping';
|
||||
|
||||
if ($blog_id) {
|
||||
// Clear specific blog cache
|
||||
wp_cache_delete('dm_domains_' . $blog_id, $cache_group);
|
||||
wp_cache_delete('dm_domain_exists_' . $blog_id, $cache_group);
|
||||
} else {
|
||||
// Clear all domain mapping caches
|
||||
wp_cache_flush_group($cache_group);
|
||||
}
|
||||
|
||||
// Also clear object cache if available
|
||||
if (function_exists('wp_cache_flush_group')) {
|
||||
wp_cache_flush_group($cache_group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached domain existence check
|
||||
*/
|
||||
function dm_domain_exists_cached($domain) {
|
||||
$cache_key = 'dm_domain_exists_' . md5($domain);
|
||||
$cache_group = 'domain_mapping';
|
||||
|
||||
$exists = wp_cache_get($cache_key, $cache_group);
|
||||
|
||||
if (false === $exists) {
|
||||
$exists = dm_get_domain_by_name($domain);
|
||||
// Cache for 30 minutes
|
||||
wp_cache_set($cache_key, $exists, $cache_group, 30 * MINUTE_IN_SECONDS);
|
||||
}
|
||||
|
||||
return $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch clear cache for multiple blogs
|
||||
*/
|
||||
function dm_clear_multiple_domain_cache($blog_ids) {
|
||||
if (!is_array($blog_ids)) {
|
||||
$blog_ids = array($blog_ids);
|
||||
}
|
||||
|
||||
foreach ($blog_ids as $blog_id) {
|
||||
dm_clear_domain_cache($blog_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check results caching
|
||||
*/
|
||||
function dm_get_health_result_cached($domain) {
|
||||
$cache_key = 'dm_health_' . md5($domain);
|
||||
$cache_group = 'domain_mapping_health';
|
||||
|
||||
$result = wp_cache_get($cache_key, $cache_group);
|
||||
|
||||
if (false === $result) {
|
||||
$result = dm_get_health_result($domain);
|
||||
if ($result) {
|
||||
// Cache for 1 hour
|
||||
wp_cache_set($cache_key, $result, $cache_group, HOUR_IN_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear health check cache
|
||||
*/
|
||||
function dm_clear_health_cache($domain = null) {
|
||||
$cache_group = 'domain_mapping_health';
|
||||
|
||||
if ($domain) {
|
||||
wp_cache_delete('dm_health_' . md5($domain), $cache_group);
|
||||
} else {
|
||||
if (function_exists('wp_cache_flush_group')) {
|
||||
wp_cache_flush_group($cache_group);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Enhanced error handling and logging functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Log domain mapping errors
|
||||
*/
|
||||
function dm_log_error($message, $context = array()) {
|
||||
if (!defined('WP_DEBUG') || !WP_DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
$log_message = '[WP Domain Mapping] ' . $message;
|
||||
|
||||
if (!empty($context)) {
|
||||
$log_message .= ' Context: ' . json_encode($context);
|
||||
}
|
||||
|
||||
error_log($log_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log domain mapping info
|
||||
*/
|
||||
function dm_log_info($message, $context = array()) {
|
||||
if (!defined('WP_DEBUG') || !WP_DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
$log_message = '[WP Domain Mapping INFO] ' . $message;
|
||||
|
||||
if (!empty($context)) {
|
||||
$log_message .= ' Context: ' . json_encode($context);
|
||||
}
|
||||
|
||||
error_log($log_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe database query execution
|
||||
*/
|
||||
function dm_safe_query($query, $params = array()) {
|
||||
global $wpdb;
|
||||
|
||||
try {
|
||||
if (!empty($params)) {
|
||||
$prepared = $wpdb->prepare($query, $params);
|
||||
} else {
|
||||
$prepared = $query;
|
||||
}
|
||||
|
||||
$result = $wpdb->query($prepared);
|
||||
|
||||
if ($result === false) {
|
||||
dm_log_error('Database query failed', array(
|
||||
'query' => $query,
|
||||
'params' => $params,
|
||||
'error' => $wpdb->last_error
|
||||
));
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
dm_log_error('Database exception', array(
|
||||
'query' => $query,
|
||||
'params' => $params,
|
||||
'exception' => $e->getMessage()
|
||||
));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced domain validation with error details
|
||||
*/
|
||||
function dm_validate_domain_with_errors($domain) {
|
||||
$errors = array();
|
||||
|
||||
// Remove possible protocol and path
|
||||
$original_domain = $domain;
|
||||
$domain = preg_replace('#^https?://#', '', $domain);
|
||||
$domain = preg_replace('#/.*$#', '', $domain);
|
||||
$domain = trim($domain);
|
||||
|
||||
// Check if empty
|
||||
if (empty($domain)) {
|
||||
$errors[] = __('Domain cannot be empty', 'wp-domain-mapping');
|
||||
return array('valid' => false, 'errors' => $errors, 'domain' => $domain);
|
||||
}
|
||||
|
||||
// Check length limit (RFC 1035)
|
||||
if (strlen($domain) > 253) {
|
||||
$errors[] = __('Domain name too long (max 253 characters)', 'wp-domain-mapping');
|
||||
}
|
||||
|
||||
// Check if contains only valid characters
|
||||
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $domain)) {
|
||||
$errors[] = __('Domain contains invalid characters', 'wp-domain-mapping');
|
||||
}
|
||||
|
||||
// Check if starts or ends with dot
|
||||
if (substr($domain, 0, 1) === '.' || substr($domain, -1) === '.') {
|
||||
$errors[] = __('Domain cannot start or end with a dot', 'wp-domain-mapping');
|
||||
}
|
||||
|
||||
// Check for consecutive dots
|
||||
if (strpos($domain, '..') !== false) {
|
||||
$errors[] = __('Domain cannot contain consecutive dots', 'wp-domain-mapping');
|
||||
}
|
||||
|
||||
// Split domain into parts
|
||||
$parts = explode('.', $domain);
|
||||
|
||||
// Need at least two parts
|
||||
if (count($parts) < 2) {
|
||||
$errors[] = __('Domain must have at least two parts (e.g., domain.com)', 'wp-domain-mapping');
|
||||
}
|
||||
|
||||
// Check each part
|
||||
foreach ($parts as $i => $part) {
|
||||
if (empty($part)) {
|
||||
$errors[] = sprintf(__('Domain part %d is empty', 'wp-domain-mapping'), $i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strlen($part) > 63) {
|
||||
$errors[] = sprintf(__('Domain part "%s" too long (max 63 characters)', 'wp-domain-mapping'), $part);
|
||||
}
|
||||
|
||||
if (substr($part, 0, 1) === '-' || substr($part, -1) === '-') {
|
||||
$errors[] = sprintf(__('Domain part "%s" cannot start or end with hyphen', 'wp-domain-mapping'), $part);
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9-]+$/', $part)) {
|
||||
$errors[] = sprintf(__('Domain part "%s" contains invalid characters', 'wp-domain-mapping'), $part);
|
||||
}
|
||||
}
|
||||
|
||||
// Check TLD
|
||||
if (count($parts) >= 2) {
|
||||
$tld = end($parts);
|
||||
if (strlen($tld) < 2 || !preg_match('/^[a-zA-Z]+$/', $tld)) {
|
||||
$errors[] = __('Invalid top-level domain (TLD)', 'wp-domain-mapping');
|
||||
}
|
||||
}
|
||||
|
||||
$is_valid = empty($errors);
|
||||
|
||||
// Log validation attempts if debugging
|
||||
if (!$is_valid) {
|
||||
dm_log_info('Domain validation failed', array(
|
||||
'original' => $original_domain,
|
||||
'cleaned' => $domain,
|
||||
'errors' => $errors
|
||||
));
|
||||
}
|
||||
|
||||
return array(
|
||||
'valid' => $is_valid,
|
||||
'errors' => $errors,
|
||||
'domain' => $domain,
|
||||
'original' => $original_domain
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced health check with error handling
|
||||
*/
|
||||
function dm_check_domain_health_safe($domain) {
|
||||
try {
|
||||
$tools = WP_Domain_Mapping_Tools::get_instance();
|
||||
$result = $tools->check_domain_health($domain);
|
||||
|
||||
dm_log_info('Health check completed', array(
|
||||
'domain' => $domain,
|
||||
'result' => $result
|
||||
));
|
||||
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
dm_log_error('Health check failed', array(
|
||||
'domain' => $domain,
|
||||
'exception' => $e->getMessage()
|
||||
));
|
||||
|
||||
return array(
|
||||
'domain' => $domain,
|
||||
'last_check' => current_time('mysql'),
|
||||
'error' => $e->getMessage(),
|
||||
'dns_status' => 'error',
|
||||
'ssl_valid' => false,
|
||||
'accessible' => false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
55
sunrise.php
55
sunrise.php
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Sunrise.php for WordPress Domain Mapping
|
||||
* Sunrise.php for WP Domain Mapping
|
||||
*
|
||||
* This file must be copied to wp-content/sunrise.php
|
||||
* Also, you must add "define('SUNRISE', 'on');" to wp-config.php
|
||||
|
@ -18,16 +18,27 @@ if (!defined('MULTISITE') || !MULTISITE) {
|
|||
// Enable domain mapping
|
||||
define('DOMAIN_MAPPING', 1);
|
||||
|
||||
// Check if we're on the main site already
|
||||
if (defined('COOKIE_DOMAIN') && COOKIE_DOMAIN == $_SERVER['HTTP_HOST']) {
|
||||
return;
|
||||
// Don't process if we're in admin and on the original domain
|
||||
if (is_admin() && isset($_SERVER['HTTP_HOST'])) {
|
||||
// Allow admin access from any domain - we'll handle this in the plugin itself
|
||||
}
|
||||
|
||||
global $wpdb, $current_blog, $current_site;
|
||||
|
||||
// Check if tables exist before proceeding
|
||||
if (!$wpdb) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get domains table name
|
||||
$domain_mapping_table = $wpdb->base_prefix . 'domain_mapping';
|
||||
|
||||
// Check if the domain mapping table exists
|
||||
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$domain_mapping_table}'") == $domain_mapping_table;
|
||||
if (!$table_exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for the current domain in the domain mapping table
|
||||
$domain = sanitize_text_field($_SERVER['HTTP_HOST']);
|
||||
$blog_id = $wpdb->get_var($wpdb->prepare(
|
||||
|
@ -38,14 +49,19 @@ $blog_id = $wpdb->get_var($wpdb->prepare(
|
|||
// If we found a mapped domain, override current_blog
|
||||
if (!empty($blog_id)) {
|
||||
// Get the mapped blog details
|
||||
$mapped_blog = $wpdb->get_row("SELECT * FROM {$wpdb->blogs} WHERE blog_id = '{$blog_id}' LIMIT 1");
|
||||
$mapped_blog = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d LIMIT 1",
|
||||
$blog_id
|
||||
));
|
||||
|
||||
if ($mapped_blog) {
|
||||
// Override current_blog
|
||||
$current_blog = $mapped_blog;
|
||||
|
||||
// Also set the cookie domain to the current domain
|
||||
define('COOKIE_DOMAIN', $_SERVER['HTTP_HOST']);
|
||||
// Set cookie domain for the mapped domain (only if not defined in wp-config.php)
|
||||
if (!defined('COOKIE_DOMAIN')) {
|
||||
define('COOKIE_DOMAIN', $domain);
|
||||
}
|
||||
|
||||
// Define the mapped domain constant
|
||||
define('MAPPED_DOMAIN', true);
|
||||
|
@ -53,12 +69,35 @@ if (!empty($blog_id)) {
|
|||
// Allow other plugins to know this is a mapped domain
|
||||
$GLOBALS['dm_domain'] = array(
|
||||
'original' => $current_blog->domain,
|
||||
'mapped' => $_SERVER['HTTP_HOST']
|
||||
'mapped' => $domain,
|
||||
'blog_id' => $blog_id
|
||||
);
|
||||
|
||||
// Fix request URI for path sites
|
||||
if ($current_blog->path != '/' && ($current_blog->path != '/wp/' || strpos($_SERVER['REQUEST_URI'], '/wp/') === false)) {
|
||||
$current_blog->path = '/';
|
||||
}
|
||||
|
||||
// Store original values for reference
|
||||
if (!defined('DM_ORIGINAL_DOMAIN')) {
|
||||
define('DM_ORIGINAL_DOMAIN', $mapped_blog->domain);
|
||||
}
|
||||
if (!defined('DM_ORIGINAL_PATH')) {
|
||||
define('DM_ORIGINAL_PATH', $mapped_blog->path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check if current request is for admin area
|
||||
function dm_is_admin_request() {
|
||||
if (!isset($_SERVER['REQUEST_URI'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request_uri = $_SERVER['REQUEST_URI'];
|
||||
return (
|
||||
strpos($request_uri, '/wp-admin/') !== false ||
|
||||
strpos($request_uri, '/wp-login.php') !== false ||
|
||||
(defined('WP_ADMIN') && WP_ADMIN)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: WP Domain Mapping
|
||||
* Plugin URI: https://wenpai.org/plugins/wp-domain-mapping/
|
||||
* Description: Map any site on a WordPress website to another domain with enhanced management features.
|
||||
* Version: 2.2.0
|
||||
* Version: 2.0.0
|
||||
* Author: WPDomain.com
|
||||
* Author URI: https://wpdomain.com/
|
||||
* Network: true
|
||||
|
@ -20,7 +20,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
|
||||
// Define plugin constants
|
||||
define( 'WP_DOMAIN_MAPPING_VERSION', '2.2.0' );
|
||||
define( 'WP_DOMAIN_MAPPING_VERSION', '2.0.0' );
|
||||
define( 'WP_DOMAIN_MAPPING_DIR_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'WP_DOMAIN_MAPPING_DIR_PATH', plugin_dir_path( __FILE__ ) );
|
||||
define( 'WP_DOMAIN_MAPPING_BASENAME', plugin_basename( __FILE__ ) );
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue