v2.0 版本重写

新增多项改进
This commit is contained in:
文派备案 2025-05-23 16:10:39 +08:00
parent 31bf7145ea
commit fbebd6ea2f
12 changed files with 4241 additions and 2180 deletions

View file

@ -1,6 +1,6 @@
<?php
/**
* Domains administration page
* Domains administration page template
*
* @package WP Domain Mapping
*/
@ -9,6 +9,8 @@
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// This file is included from the render_domains_admin method
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?>
@ -16,7 +18,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php printf( esc_html__( 'Version: %s', 'wp-domain-mapping' ), esc_html( WP_DOMAIN_MAPPING_VERSION ) ); ?>
</span>
<a href="https://wpmultisite.com/document/wp-domain-mapping" target="_blank" class="button button-secondary" style="margin-left: 10px;">
<?php esc_html_e( 'Documentation', 'wp-domain-mapping' ); ?>
<?php esc_html_e( 'Document', 'wp-domain-mapping' ); ?>
</a>
<a href="https://wpmultisite.com/forums/" target="_blank" class="button button-secondary">
<?php esc_html_e( 'Support', 'wp-domain-mapping' ); ?>
@ -26,7 +28,70 @@ if ( ! defined( 'ABSPATH' ) ) {
<div class="card domain-mapping-card">
<h2><?php echo $edit_row ? esc_html__( 'Edit Domain', 'wp-domain-mapping' ) : esc_html__( 'Add New Domain', 'wp-domain-mapping' ); ?></h2>
<div id="edit-domain-status" class="notice" style="display:none;"></div>
<?php dm_edit_domain( $edit_row ); ?>
<form id="edit-domain-form" method="POST">
<input type="hidden" name="orig_domain" value="<?php echo esc_attr( $edit_row ? $edit_row->domain : '' ); ?>" />
<table class="form-table">
<tr>
<th><label for="blog_id"><?php esc_html_e( 'Site ID', 'wp-domain-mapping' ); ?></label></th>
<td>
<input type="number" id="blog_id" name="blog_id" value="<?php echo esc_attr( $edit_row ? $edit_row->blog_id : '' ); ?>" class="regular-text" required />
<?php if ( ! $edit_row ) : ?>
<p class="description">
<?php
$site_list_url = network_admin_url( 'sites.php' );
printf(
/* translators: %s: URL to sites list */
wp_kses(
__( 'Not sure about Site ID? <a href="%s" target="_blank">View all sites</a> to find the ID.', 'wp-domain-mapping' ),
array( 'a' => array( 'href' => array(), 'target' => array() ) )
),
esc_url( $site_list_url )
);
?>
</p>
<?php endif; ?>
</td>
</tr>
<tr>
<th><label for="domain"><?php esc_html_e( 'Domain', 'wp-domain-mapping' ); ?></label></th>
<td>
<input type="text" id="domain" name="domain" value="<?php echo esc_attr( $edit_row ? $edit_row->domain : '' ); ?>" class="regular-text" required
placeholder="example.com" pattern="^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$" />
<p class="description">
<?php esc_html_e( 'Enter the domain without http:// or https:// (e.g., example.com)', 'wp-domain-mapping' ); ?>
</p>
</td>
</tr>
<tr>
<th><label for="active"><?php esc_html_e( 'Primary', 'wp-domain-mapping' ); ?></label></th>
<td>
<input type="checkbox" id="active" name="active" value="1" <?php checked( $edit_row ? $edit_row->active : 0, 1 ); ?> />
<span class="description">
<?php esc_html_e( 'Set as the primary domain for this site', 'wp-domain-mapping' ); ?>
</span>
</td>
</tr>
<?php if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) : ?>
<tr>
<td colspan="2" class="notice notice-warning">
<p><?php esc_html_e( 'Warning! Primary domains are currently disabled in network settings.', 'wp-domain-mapping' ); ?></p>
</td>
</tr>
<?php endif; ?>
</table>
<p>
<input type="submit" class="button button-primary" value="<?php echo $edit_row
? esc_attr__( 'Update Domain', 'wp-domain-mapping' )
: esc_attr__( 'Add Domain', 'wp-domain-mapping' ); ?>" />
<?php if ( $edit_row ) : ?>
<a href="<?php echo esc_url( admin_url( 'network/sites.php?page=domains' ) ); ?>" class="button button-secondary">
<?php esc_html_e( 'Cancel', 'wp-domain-mapping' ); ?>
</a>
<?php endif; ?>
</p>
</form>
</div>
<div class="card domain-mapping-card">
@ -112,14 +177,15 @@ if ( ! defined( 'ABSPATH' ) ) {
}
$where_sql = $where ? ' WHERE ' . implode( ' AND ', $where ) : '';
$tables = dm_get_table_names();
// Count total items for pagination
$total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS . $where_sql );
$total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$tables['domains']}" . $where_sql );
$total_pages = ceil( $total_items / $per_page );
// Get the domains with pagination
$rows = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS . $where_sql . " ORDER BY id DESC LIMIT %d, %d",
"SELECT * FROM {$tables['domains']}" . $where_sql . " ORDER BY id DESC LIMIT %d, %d",
$offset,
$per_page
));
@ -191,7 +257,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<th><?php esc_html_e( 'Sites with Mapped Domains', 'wp-domain-mapping' ); ?></th>
<td>
<?php
$sites_with_domains = $wpdb->get_var( "SELECT COUNT(DISTINCT blog_id) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS );
$sites_with_domains = $wpdb->get_var( "SELECT COUNT(DISTINCT blog_id) FROM {$tables['domains']}" );
echo esc_html( $sites_with_domains );
?>
</td>
@ -201,314 +267,162 @@ if ( ! defined( 'ABSPATH' ) ) {
</div>
</div>
<script>
jQuery(document).ready(function($) {
// Tab switching functionality
$('.domain-mapping-tab').on('click', function() {
$('.domain-mapping-tab').removeClass('active');
$(this).addClass('active');
<?php
/**
* Render domain listing table
*
* @param array $rows Domain data rows
*/
function dm_domain_listing( $rows ) {
global $wpdb;
var tab = $(this).data('tab');
$('.domain-mapping-section').hide();
$('.domain-mapping-section[data-section="' + tab + '"]').show();
});
// Helper function for showing notices
function showNotice(selector, message, type) {
$(selector)
.removeClass('notice-success notice-error notice-warning notice-info')
.addClass('notice-' + type)
.html('<p>' + message + '</p>')
.show()
.delay(3000)
.fadeOut();
if ( ! $rows ) {
echo '<div class="notice notice-info"><p>' . esc_html__( 'No domains found.', 'wp-domain-mapping' ) . '</p></div>';
return;
}
// Handle domain edit/add form submission
$('#edit-domain-form').on('submit', function(e) {
e.preventDefault();
$edit_url = network_admin_url(
file_exists( ABSPATH . 'wp-admin/network/site-info.php' )
? 'site-info.php'
: ( file_exists( ABSPATH . 'wp-admin/ms-sites.php' ) ? 'ms-sites.php' : 'wpmu-blogs.php' )
);
?>
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<label for="bulk-action-selector-top" class="screen-reader-text"><?php esc_html_e( 'Select bulk action', 'wp-domain-mapping' ); ?></label>
<select id="bulk-action-selector-top" name="action">
<option value="-1"><?php esc_html_e( 'Bulk Actions', 'wp-domain-mapping' ); ?></option>
<option value="delete"><?php esc_html_e( 'Delete', 'wp-domain-mapping' ); ?></option>
</select>
<input type="submit" class="button action" value="<?php esc_attr_e( 'Apply', 'wp-domain-mapping' ); ?>" />
</div>
</div>
// Validate form
var blogId = $('#blog_id').val();
var domain = $('#domain').val();
<table class="wp-list-table widefat striped domains-table">
<thead>
<tr>
<td class="manage-column column-cb check-column">
<input id="select-all" type="checkbox" />
</td>
<th scope="col" class="column-site-id"><?php esc_html_e( 'Site ID', 'wp-domain-mapping' ); ?></th>
<th scope="col" class="column-site-name"><?php esc_html_e( 'Site Name', 'wp-domain-mapping' ); ?></th>
<th scope="col" class="column-domain"><?php esc_html_e( 'Domain', 'wp-domain-mapping' ); ?></th>
<th scope="col" class="column-primary"><?php esc_html_e( 'Primary', 'wp-domain-mapping' ); ?></th>
<th scope="col" class="column-actions"><?php esc_html_e( 'Actions', 'wp-domain-mapping' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $rows as $row ) :
$site_name = get_blog_option( $row->blog_id, 'blogname', esc_html__( 'Unknown', 'wp-domain-mapping' ) );
?>
<tr>
<th scope="row" class="check-column">
<input type="checkbox" class="domain-checkbox" value="<?php echo esc_attr( $row->domain ); ?>" />
</th>
<td class="column-site-id">
<a href="<?php echo esc_url( add_query_arg( array( 'action' => 'editblog', 'id' => $row->blog_id ), $edit_url ) ); ?>">
<?php echo esc_html( $row->blog_id ); ?>
</a>
</td>
<td class="column-site-name">
<?php echo esc_html( $site_name ); ?>
</td>
<td class="column-domain">
<a href="<?php echo esc_url( dm_ensure_protocol( $row->domain ) ); ?>" target="_blank">
<?php echo esc_html( $row->domain ); ?>
<span class="dashicons dashicons-external" style="font-size: 14px; line-height: 1.3; opacity: 0.7;"></span>
</a>
</td>
<td class="column-primary">
<?php if ( $row->active == 1 ) : ?>
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
<span class="screen-reader-text"><?php esc_html_e( 'Yes', 'wp-domain-mapping' ); ?></span>
<?php else : ?>
<span class="dashicons dashicons-no-alt" style="color: #dc3232;"></span>
<span class="screen-reader-text"><?php esc_html_e( 'No', 'wp-domain-mapping' ); ?></span>
<?php endif; ?>
</td>
<td class="column-actions">
<div class="row-actions">
<span class="edit">
<a href="<?php echo esc_url( add_query_arg( array( 'edit_domain' => $row->domain ), admin_url( 'network/sites.php?page=domains' ) ) ); ?>" class="button button-small">
<?php esc_html_e( 'Edit', 'wp-domain-mapping' ); ?>
</a>
</span>
<?php if ( $row->active != 1 ) : ?>
<span class="delete">
<a href="#" class="button button-small domain-delete-button" data-domain="<?php echo esc_attr( $row->domain ); ?>">
<?php esc_html_e( 'Delete', 'wp-domain-mapping' ); ?>
</a>
</span>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
if (!blogId || !domain) {
showNotice('#edit-domain-status', '<?php esc_html_e( 'Please fill in all required fields.', 'wp-domain-mapping' ); ?>', 'error');
return;
}
var formData = $(this).serializeArray();
formData.push({name: 'action', value: 'dm_handle_actions'});
formData.push({name: 'action_type', value: 'save'});
formData.push({name: 'nonce', value: '<?php echo wp_create_nonce( 'domain_mapping' ); ?>'});
$('#edit-domain-status').html('<p><?php esc_html_e( 'Saving...', 'wp-domain-mapping' ); ?></p>').show();
$.ajax({
url: ajaxurl,
type: 'POST',
data: formData,
success: function(response) {
if (response.success) {
showNotice('#edit-domain-status', response.data, 'success');
setTimeout(function() {
location.href = '<?php echo esc_js( admin_url( 'network/sites.php?page=domains' ) ); ?>';
}, 1000);
} else {
showNotice('#edit-domain-status', response.data || '<?php esc_html_e( 'Failed to save domain.', 'wp-domain-mapping' ); ?>', 'error');
}
},
error: function() {
showNotice('#edit-domain-status', '<?php esc_html_e( 'Server error occurred.', 'wp-domain-mapping' ); ?>', 'error');
}
});
});
// Handle domain list bulk actions
$('#domain-list-form').on('submit', function(e) {
e.preventDefault();
var selectedDomains = [];
$('.domain-checkbox:checked').each(function() {
selectedDomains.push($(this).val());
});
if (selectedDomains.length === 0) {
showNotice('#domain-status', '<?php esc_html_e( 'Please select at least one domain.', 'wp-domain-mapping' ); ?>', 'error');
return;
}
var action = $('#bulk-action-selector-top').val();
if (action === '-1') return;
if (!confirm('<?php esc_html_e( 'Are you sure you want to delete the selected domains?', 'wp-domain-mapping' ); ?>')) {
return;
}
$('#domain-status').html('<p><?php esc_html_e( 'Processing...', 'wp-domain-mapping' ); ?></p>').show();
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'dm_handle_actions',
action_type: 'delete',
domains: selectedDomains,
nonce: '<?php echo wp_create_nonce( 'domain_mapping' ); ?>'
},
success: function(response) {
if (response.success) {
showNotice('#domain-status', response.data, 'success');
setTimeout(function() {
location.reload();
}, 1000);
} else {
showNotice('#domain-status', response.data || '<?php esc_html_e( 'Failed to delete domains.', 'wp-domain-mapping' ); ?>', 'error');
}
},
error: function() {
showNotice('#domain-status', '<?php esc_html_e( 'Server error occurred.', 'wp-domain-mapping' ); ?>', 'error');
}
});
});
});
</script>
<style>
/* Main cards */
.domain-mapping-card {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
max-width: unset;
margin-top: 20px;
padding: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
<?php if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) : ?>
<div class="notice notice-warning inline">
<p><?php esc_html_e( 'Warning! Primary domains are currently disabled in network settings.', 'wp-domain-mapping' ); ?></p>
</div>
<?php endif; ?>
<?php
}
/* Tabs */
.domain-mapping-tabs {
display: flex;
flex-wrap: wrap;
gap: 5px;
border-bottom: 1px solid #c3c4c7;
margin-bottom: 20px;
}
/**
* Render domain logs table
*/
function dm_domain_logs() {
global $wpdb;
.domain-mapping-tab {
padding: 8px 16px;
border: none;
background: none;
cursor: pointer;
font-size: 14px;
border-bottom: 2px solid transparent;
}
$tables = dm_get_table_names();
.domain-mapping-tab.active {
border-bottom: 2px solid #007cba;
font-weight: 600;
background: #f0f0f1;
}
.domain-mapping-tab:hover:not(.active) {
background: #f0f0f1;
border-bottom-color: #dcdcde;
}
.domain-mapping-content {
flex: 1;
}
/* Search form */
.search-form {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 15px;
margin-bottom: 15px;
}
.search-form-field {
display: flex;
flex-direction: column;
min-width: 180px;
}
.search-form-field label {
margin-bottom: 5px;
font-weight: 500;
}
.search-form-submit {
display: flex;
align-items: center;
gap: 10px;
}
/* Tables */
.tablenav {
margin: 10px 0;
display: flex;
align-items: center;
}
.tablenav-pages {
margin-left: auto;
}
.tablenav-pages a,
.tablenav-pages span.current {
display: inline-block;
min-width: 17px;
padding: 3px 5px 7px;
background: #f0f0f1;
font-size: 16px;
line-height: 1;
font-weight: 400;
text-align: center;
text-decoration: none;
}
.tablenav-pages span.current {
background: #007cba;
color: #fff;
border-color: #007cba;
}
.displaying-num {
margin-left: 10px;
color: #555;
}
.domains-table th,
.logs-table th {
font-weight: 600;
}
.column-site-id {
width: 80px;
}
.column-site-name {
width: 20%;
}
.column-primary {
width: 80px;
text-align: center;
}
.column-actions {
width: 120px;
}
/* Form elements */
.form-table th {
width: 200px;
padding: 15px 10px 15px 0;
}
.form-table td {
padding: 15px 0;
}
.description {
color: #666;
font-size: 13px;
margin-top: 4px;
}
/* Notices */
.notice {
padding: 8px 12px;
border-radius: 3px;
margin: 5px 0 15px;
}
.notice p {
margin: 0.5em 0;
padding: 2px;
}
.notice-success {
background-color: #f0f9eb;
border-left: 4px solid #46b450;
}
.notice-error {
background-color: #fef0f0;
border-left: 4px solid #dc3232;
}
.notice-warning {
background-color: #fff8e5;
border-left: 4px solid #ffb900;
}
.notice-info {
background-color: #f0f6fa;
border-left: 4px solid #00a0d2;
}
/* Responsive */
@media screen and (max-width: 782px) {
.search-form {
flex-direction: column;
align-items: stretch;
// Make sure the table exists
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$tables['logs']}'" ) != $tables['logs'] ) {
echo '<div class="notice notice-error"><p>' .
esc_html__('Domain mapping logs table is missing. Please deactivate and reactivate the plugin.', 'wp-domain-mapping') .
'</p></div>';
return;
}
.search-form-field {
min-width: 100%;
// Get pagination parameters
$per_page = isset( $_GET['logs_per_page'] ) ? absint( $_GET['logs_per_page'] ) : 50;
$paged = isset( $_GET['logs_paged'] ) ? absint( $_GET['logs_paged'] ) : 1;
$offset = ( $paged - 1 ) * $per_page;
// Get action filter
$action_filter = isset( $_GET['log_action'] ) ? sanitize_text_field( $_GET['log_action'] ) : '';
// Build WHERE clause for filtering
$where = array();
if ( ! empty( $action_filter ) ) {
$where[] = $wpdb->prepare( "action = %s", $action_filter );
}
.form-table th {
width: 100%;
display: block;
$where_sql = $where ? ' WHERE ' . implode( ' AND ', $where ) : '';
// Count total logs for pagination
$total_logs = $wpdb->get_var( "SELECT COUNT(*) FROM {$tables['logs']}" . $where_sql );
$total_pages = ceil( $total_logs / $per_page );
// Get logs with pagination
$logs = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$tables['logs']}" . $where_sql . " ORDER BY timestamp DESC LIMIT %d, %d",
$offset,
$per_page
));
// Get available actions for filter
$actions = $wpdb->get_col( "SELECT DISTINCT action FROM {$tables['logs']}" );
if ( ! $logs ) {
echo '<div class="notice notice-info"><p>' . esc_html__( 'No domain mapping logs available.', 'wp-domain-mapping' ) . '</p></div>';
return;
}
.form-table td {
display: block;
padding: 0 0 15px;
}
// Include the logs table template
include WP_DOMAIN_MAPPING_DIR_PATH . 'admin/logs-table.php';
}
</style>
?>

View file

@ -10,46 +10,12 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Get pagination parameters
$per_page = isset( $_GET['logs_per_page'] ) ? absint( $_GET['logs_per_page'] ) : 50;
$paged = isset( $_GET['logs_paged'] ) ? absint( $_GET['logs_paged'] ) : 1;
$offset = ( $paged - 1 ) * $per_page;
// Get action filter
$action_filter = isset( $_GET['log_action'] ) ? sanitize_text_field( $_GET['log_action'] ) : '';
// Build WHERE clause for filtering
$where = array();
if ( ! empty( $action_filter ) ) {
$where[] = $wpdb->prepare( "action = %s", $action_filter );
}
$where_sql = $where ? ' WHERE ' . implode( ' AND ', $where ) : '';
// Count total logs for pagination
$total_logs = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_LOGS . $where_sql );
$total_pages = ceil( $total_logs / $per_page );
// Get logs with pagination
$logs = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_LOGS . $where_sql . " ORDER BY timestamp DESC LIMIT %d, %d",
$offset,
$per_page
));
// Get available actions for filter
$actions = $wpdb->get_col( "SELECT DISTINCT action FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_LOGS );
if ( ! $logs ) {
echo '<div class="notice notice-info"><p>' . esc_html__( 'No domain mapping logs available.', 'wp-domain-mapping' ) . '</p></div>';
return;
}
// This file is included from dm_domain_logs() function
?>
<div class="tablenav top">
<form method="GET" id="logs-filter-form" class="logs-filter">
<input type="hidden" name="page" value="domains" />
<input type="hidden" name="tab" value="domain-logs" />
<div class="alignleft actions">
<label for="log_action" class="screen-reader-text"><?php esc_html_e( 'Filter by action', 'wp-domain-mapping' ); ?></label>
@ -57,21 +23,7 @@ if ( ! $logs ) {
<option value=""><?php esc_html_e( 'All actions', 'wp-domain-mapping' ); ?></option>
<?php foreach ( $actions as $action ) : ?>
<option value="<?php echo esc_attr( $action ); ?>" <?php selected( $action_filter, $action ); ?>>
<?php
switch ( $action ) {
case 'add':
esc_html_e( 'Added', 'wp-domain-mapping' );
break;
case 'edit':
esc_html_e( 'Updated', 'wp-domain-mapping' );
break;
case 'delete':
esc_html_e( 'Deleted', 'wp-domain-mapping' );
break;
default:
echo esc_html( ucfirst( $action ) );
}
?>
<?php echo esc_html( dm_format_action_name( $action ) ); ?>
</option>
<?php endforeach; ?>
</select>
@ -79,7 +31,7 @@ if ( ! $logs ) {
<input type="submit" class="button" value="<?php esc_attr_e( 'Filter', 'wp-domain-mapping' ); ?>" />
<?php if ( ! empty( $action_filter ) ) : ?>
<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'domains', 'tab' => 'domain-logs' ), admin_url( 'network/sites.php' ) ) ); ?>" class="button">
<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'domains' ), admin_url( 'network/sites.php' ) ) ); ?>" class="button">
<?php esc_html_e( 'Clear', 'wp-domain-mapping' ); ?>
</a>
<?php endif; ?>
@ -136,19 +88,8 @@ if ( ! $logs ) {
: sprintf( esc_html__( 'User #%d', 'wp-domain-mapping' ), $log->user_id );
// Format action for display
switch ( $log->action ) {
case 'add':
$action_display = '<span class="log-action log-action-add">' . esc_html__( 'Added', 'wp-domain-mapping' ) . '</span>';
break;
case 'edit':
$action_display = '<span class="log-action log-action-edit">' . esc_html__( 'Updated', 'wp-domain-mapping' ) . '</span>';
break;
case 'delete':
$action_display = '<span class="log-action log-action-delete">' . esc_html__( 'Deleted', 'wp-domain-mapping' ) . '</span>';
break;
default:
$action_display = '<span class="log-action">' . esc_html( ucfirst( $log->action ) ) . '</span>';
}
$action_display = dm_format_action_name( $log->action );
$action_class = 'log-action-' . $log->action;
// Get site name
$site_name = get_blog_option( $log->blog_id, 'blogname', '' );
@ -166,7 +107,11 @@ if ( ! $logs ) {
?>
<tr>
<td class="column-user"><?php echo $username; ?></td>
<td class="column-action"><?php echo $action_display; ?></td>
<td class="column-action">
<span class="log-action <?php echo esc_attr( $action_class ); ?>">
<?php echo esc_html( $action_display ); ?>
</span>
</td>
<td class="column-domain">
<a href="<?php echo esc_url( dm_ensure_protocol( $log->domain ) ); ?>" target="_blank">
<?php echo esc_html( $log->domain ); ?>
@ -216,73 +161,3 @@ if ( ! $logs ) {
</div>
</div>
<?php endif; ?>
<style>
/* Logs table styles */
.logs-filter {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.logs-table .column-user,
.logs-table .column-site {
width: 15%;
}
.logs-table .column-action {
width: 10%;
}
.logs-table .column-date {
width: 15%;
}
.log-action {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.log-action-add {
background-color: #dff0d8;
color: #3c763d;
}
.log-action-edit {
background-color: #d9edf7;
color: #31708f;
}
.log-action-delete {
background-color: #f2dede;
color: #a94442;
}
/* Pagination */
.tablenav-pages a,
.tablenav-pages span.current {
display: inline-block;
min-width: 17px;
padding: 3px 5px 7px;
background: #f0f0f1;
font-size: 16px;
line-height: 1;
font-weight: 400;
text-align: center;
text-decoration: none;
}
.tablenav-pages span.current {
background: #007cba;
color: #fff;
border-color: #007cba;
}
.displaying-num {
margin-left: 10px;
color: #555;
}
</style>

1033
admin/pages.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -39,7 +39,7 @@ settings_errors( 'dm_settings' );
<?php printf( esc_html__( 'Version: %s', 'wp-domain-mapping' ), esc_html( WP_DOMAIN_MAPPING_VERSION ) ); ?>
</span>
<a href="https://wpmultisite.com/document/wp-domain-mapping" target="_blank" class="button button-secondary" style="margin-left: 10px;">
<?php esc_html_e( 'Documentation', 'wp-domain-mapping' ); ?>
<?php esc_html_e( 'Document', 'wp-domain-mapping' ); ?>
</a>
<a href="https://wpmultisite.com/forums/" target="_blank" class="button button-secondary">
<?php esc_html_e( 'Support', 'wp-domain-mapping' ); ?>

View file

@ -9,11 +9,6 @@ defined('ABSPATH') || exit;
$protocol = isset($protocol) ? $protocol : (is_ssl() ? 'https://' : 'http://');
$domains = isset($domains) ? $domains : array();
// Initialize logs array if not set to prevent PHP warnings
if (!isset($logs) || !is_array($logs)) {
$logs = array();
}
?>
<div class="wrap">
@ -97,18 +92,18 @@ if (!isset($logs) || !is_array($logs)) {
<?php foreach ($domains as $domain): ?>
<tr>
<td>
<a href="<?php echo esc_url($protocol . $domain['domain']); ?>" target="_blank">
<?php echo esc_html($domain['domain']); ?>
<a href="<?php echo esc_url($protocol . $domain->domain); ?>" target="_blank">
<?php echo esc_html($domain->domain); ?>
<span class="dashicons dashicons-external" style="font-size: 14px; line-height: 1.3; opacity: 0.7;"></span>
</a>
</td>
<td>
<?php if ($domain['active'] == 1): ?>
<?php if ($domain->active == 1): ?>
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
<span class="screen-reader-text"><?php _e('Yes', 'wp-domain-mapping'); ?></span>
<?php else: ?>
<a href="<?php echo wp_nonce_url(add_query_arg(
array('page' => 'domainmapping', 'action' => 'primary', 'domain' => $domain['domain']),
array('page' => 'domainmapping', 'action' => 'primary', 'domain' => $domain->domain),
admin_url('tools.php')
), 'domain_mapping'); ?>" class="button button-small">
<?php _e('Make Primary', 'wp-domain-mapping'); ?>
@ -116,11 +111,11 @@ if (!isset($logs) || !is_array($logs)) {
<?php endif; ?>
</td>
<td>
<?php if ($domain['active'] != 1): ?>
<?php if ($domain->active != 1): ?>
<a href="<?php echo wp_nonce_url(add_query_arg(
array('page' => 'domainmapping', 'action' => 'delete', 'domain' => $domain['domain']),
array('page' => 'domainmapping', 'action' => 'delete', 'domain' => $domain->domain),
admin_url('tools.php')
), 'delete' . $domain['domain']); ?>" class="button button-small button-link-delete" onclick="return confirm('<?php _e('Are you sure you want to delete this domain?', 'wp-domain-mapping'); ?>');">
), 'delete' . $domain->domain); ?>" class="button button-small button-link-delete" onclick="return confirm('<?php _e('Are you sure you want to delete this domain?', 'wp-domain-mapping'); ?>');">
<?php _e('Delete', 'wp-domain-mapping'); ?>
</a>
<?php endif; ?>
@ -160,24 +155,3 @@ if (!isset($logs) || !is_array($logs)) {
<?php endif; ?>
</div>
</div>
<style>
/* Main cards */
.domain-mapping-card {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
margin-top: 20px;
padding: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.domains-table .column-primary {
width: 150px;
text-align: center;
}
.domains-table .column-actions {
width: 120px;
}
</style>

View file

@ -1,19 +1,26 @@
.card {
/* WP Domain Mapping Admin Styles */
/* Main cards */
.domain-mapping-card {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
max-width: unset;
margin-top: 20px;
padding: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.styles-sync-tabs {
/* Tabs */
.domain-mapping-tabs {
display: flex;
flex-wrap: wrap;
gap: 5px;
border-bottom: 1px solid #c3c4c7;
margin-bottom: 20px;
}
.styles-tab {
.domain-mapping-tab {
padding: 8px 16px;
border: none;
background: none;
@ -21,44 +28,362 @@
font-size: 14px;
border-bottom: 2px solid transparent;
}
.styles-tab.active {
.domain-mapping-tab.active {
border-bottom: 2px solid #007cba;
font-weight: 600;
background: #f0f0f1;
}
.styles-tab:hover:not(.active) {
.domain-mapping-tab:hover:not(.active) {
background: #f0f0f1;
border-bottom-color: #dcdcde;
}
.styles-sync-content { flex: 1; }
.tablenav { margin: 10px 0; }
.tablenav-pages { float: right; }
.tablenav-pages a, .tablenav-pages span { padding: 5px 10px; }
.form-table th { width: 200px; }
.form-table td { padding: 10px
System: 0; }
.description { color: #666; font-size: 12px; }
.notice { padding: 8px 12px; border-radius: 3px; }
.notice-success { background-color: #dff0d8; border-left: 4px solid #46b450; }
.notice-error { background-color: #f2dede; border-left: 4px solid #dc3232; }
.domain-mapping-content {
flex: 1;
}
/* Responsive Styles */
/* Search form */
.search-form {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 15px;
margin-bottom: 15px;
}
.search-form-field {
display: flex;
flex-direction: column;
min-width: 180px;
}
.search-form-field label {
margin-bottom: 5px;
font-weight: 500;
}
.search-form-submit {
display: flex;
align-items: center;
gap: 10px;
}
/* Tables */
.tablenav {
margin: 10px 0;
display: flex;
align-items: center;
}
.tablenav-pages {
margin-left: auto;
}
.tablenav-pages a,
.tablenav-pages span.current {
display: inline-block;
min-width: 17px;
padding: 3px 5px 7px;
background: #f0f0f1;
font-size: 16px;
line-height: 1;
font-weight: 400;
text-align: center;
text-decoration: none;
}
.tablenav-pages span.current {
background: #007cba;
color: #fff;
border-color: #007cba;
}
.displaying-num {
margin-left: 10px;
color: #555;
}
.domains-table th,
.logs-table th,
.domains-health-table th {
font-weight: 600;
}
.column-site-id {
width: 80px;
}
.column-site-name {
width: 20%;
}
.column-primary {
width: 80px;
}
.column-actions {
width: 120px;
}
/* Health table specific */
.column-domain {
width: 20%;
}
.column-site {
width: 20%;
}
.column-dns,
.column-ssl,
.column-status {
width: 10%;
}
.column-last-check {
width: 15%;
}
/* Site ID Badge */
.dm-site-id-badge {
display: inline-block;
background: #2271b1;
color: #fff;
font-weight: 500;
padding: 0 5px;
border-radius: 3px;
font-size: 12px;
}
/* Form elements */
.form-table th {
width: 200px;
padding: 15px 10px 15px 0;
}
.form-table td {
padding: 15px 0;
}
.description {
color: #666;
font-size: 13px;
margin-top: 4px;
}
/* Notices */
.notice {
padding: 8px 12px;
border-radius: 3px;
margin: 5px 0 15px;
}
.notice p {
margin: 0.5em 0;
padding: 2px;
}
.notice-success {
background-color: #f0f9eb;
border-left: 4px solid #46b450;
}
.notice-error {
background-color: #fef0f0;
border-left: 4px solid #dc3232;
}
.notice-warning {
background-color: #fff8e5;
border-left: 4px solid #ffb900;
}
.notice-info {
background-color: #f0f6fa;
border-left: 4px solid #00a0d2;
}
/* DNS instructions */
.dns-instructions {
margin-top: 10px;
}
.dns-example {
background: #f9f9f9;
border: 1px solid #e5e5e5;
border-radius: 3px;
padding: 15px;
margin: 15px 0;
}
.dns-example h4 {
margin-top: 0;
}
.dns-tips {
list-style: disc;
margin-left: 20px;
}
.dns-tips li {
margin-bottom: 8px;
}
/* Status indicators */
.dashicons-yes-alt,
.dashicons-no-alt {
font-size: 24px;
width: 24px;
height: 24px;
}
/* Logs table styles */
.logs-filter {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.logs-table .column-user,
.logs-table .column-site {
width: 15%;
}
.logs-table .column-action {
width: 10%;
}
.logs-table .column-date {
width: 15%;
}
.log-action {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.log-action-add {
background-color: #dff0d8;
color: #3c763d;
}
.log-action-edit {
background-color: #d9edf7;
color: #31708f;
}
.log-action-delete {
background-color: #f2dede;
color: #a94442;
}
/* Health details */
.health-details-content {
padding: 15px;
background: #f9f9f9;
border-radius: 3px;
}
.health-details-content h4 {
margin-top: 0;
}
/* Progress bar */
.progress-bar-outer {
background-color: #f0f0f1;
border-radius: 4px;
height: 20px;
width: 100%;
overflow: hidden;
margin: 10px 0;
}
.progress-bar-inner {
background-color: #2271b1;
height: 100%;
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
font-weight: 500;
}
/* Import/Export specific */
.import-summary,
.import-details {
margin-top: 15px;
}
.import-details table {
margin-top: 10px;
}
.import-details .notice-success {
background-color: #f0f9eb;
}
.import-details .notice-error {
background-color: #fef0f0;
}
.import-details .notice-warning {
background-color: #fff8e5;
}
/* Responsive */
@media screen and (max-width: 782px) {
.search-form {
flex-direction: column;
align-items: stretch;
}
.search-form-field {
min-width: 100%;
}
.form-table th {
width: 100%;
display: block;
}
.form-table td {
display: block;
padding: 5px 0 15px;
padding: 0 0 15px;
}
.wp-list-table th.column-primary ~ th,
.wp-list-table td.column-primary ~ td {
.domain-mapping-tabs {
flex-direction: column;
}
.domain-mapping-tab {
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 */
.wp-list-table th.column-primary ~ th:not(.column-actions),
.wp-list-table td.column-primary ~ td:not(.column-actions) {
display: none;
}
.wp-list-table th.column-primary,
.wp-list-table td.column-primary {
.wp-list-table td.column-primary,
.wp-list-table th.column-actions,
.wp-list-table td.column-actions {
display: table-cell;
}
}
}

View file

@ -2,60 +2,72 @@
* WP Domain Mapping admin JavaScript
*/
jQuery(document).ready(function($) {
// Tab switching
$('.styles-tab').on('click', function() {
$('.styles-tab').removeClass('active');
$(this).addClass('active');
var tab = $(this).data('tab');
$('.styles-section').hide();
$('.styles-section[data-section="' + tab + '"]').show();
});
// Helper function for showing notices
function showNotice(selector, message, type) {
$(selector)
.removeClass('notice-success notice-error notice-warning notice-info')
.addClass('notice-' + type)
.html('<p>' + message + '</p>')
.show()
.delay(5000)
.fadeOut();
}
// Domain form validation
$('#edit-domain-form').on('submit', function(e) {
var domain = $('#domain').val();
var blogId = $('#blog_id').val();
var domain = $('#domain').val().trim();
var blogId = $('#blog_id').val().trim();
if (!domain) {
e.preventDefault();
alert(wpDomainMapping.messages.domainRequired);
showNotice('#edit-domain-status', wpDomainMapping.messages.domainRequired, 'error');
$('#domain').focus();
return false;
}
if (!blogId) {
e.preventDefault();
alert(wpDomainMapping.messages.siteRequired);
showNotice('#edit-domain-status', wpDomainMapping.messages.siteRequired, 'error');
$('#blog_id').focus();
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,}$/;
if (!domainPattern.test(domain)) {
e.preventDefault();
showNotice('#edit-domain-status', 'Please enter a valid domain format (e.g., example.com)', 'error');
$('#domain').focus();
return false;
}
});
// Check all domains
// Check all domains functionality
$('#select-all').on('change', function() {
$('.domain-checkbox').prop('checked', this.checked);
});
// AJAX domain operations
function showNotice(selector, message, type) {
$(selector).removeClass('notice-success notice-error')
.addClass('notice-' + type)
.html('<p>' + message + '</p>')
.show()
.delay(3000)
.fadeOut();
}
// Update select all when individual checkboxes change
$('.domain-checkbox').on('change', function() {
if (!this.checked) {
$('#select-all').prop('checked', false);
} else if ($('.domain-checkbox:checked').length === $('.domain-checkbox').length) {
$('#select-all').prop('checked', true);
}
});
// Save domain
// Handle domain edit/add form submission via AJAX
$('#edit-domain-form').on('submit', function(e) {
e.preventDefault();
var formData = $(this).serializeArray();
formData.push({name: 'action', value: 'dm_handle_actions'});
formData.push({name: 'action_type', value: 'save'});
formData.push({name: 'nonce', value: wpDomainMapping.nonce});
$('#edit-domain-status').text(wpDomainMapping.messages.saving).show();
$('#edit-domain-status').html('<p>' + wpDomainMapping.messages.saving + '</p>').show();
$.ajax({
url: wpDomainMapping.ajaxUrl,
type: 'POST',
@ -63,7 +75,12 @@ jQuery(document).ready(function($) {
success: function(response) {
if (response.success) {
showNotice('#edit-domain-status', response.data, 'success');
setTimeout(function() { location.reload(); }, 1000);
setTimeout(function() {
// Redirect to domains page without edit parameter
var url = new URL(window.location);
url.searchParams.delete('edit_domain');
window.location.href = url.toString();
}, 1500);
} else {
showNotice('#edit-domain-status', response.data || wpDomainMapping.messages.error, 'error');
}
@ -74,61 +91,498 @@ jQuery(document).ready(function($) {
});
});
// Bulk actions
// Handle bulk domain actions
$('#domain-list-form').on('submit', function(e) {
e.preventDefault();
var selectedDomains = [];
$('.domain-checkbox:checked').each(function() {
selectedDomains.push($(this).val());
});
if (selectedDomains.length === 0) {
showNotice('#domain-status', wpDomainMapping.messages.noSelection, 'error');
return;
}
var action = $('#bulk-action-selector-top').val();
if (action === '-1') return;
if (action === '-1') {
showNotice('#domain-status', 'Please select an action.', 'error');
return;
}
if (confirm('Are you sure you want to delete the selected domains?')) {
$('#domain-status').text(wpDomainMapping.messages.processing).show();
$.ajax({
url: wpDomainMapping.ajaxUrl,
type: 'POST',
data: {
action: 'dm_handle_actions',
action_type: 'delete',
domains: selectedDomains,
nonce: wpDomainMapping.nonce
},
success: function(response) {
if (response.success) {
showNotice('#domain-status', response.data, 'success');
setTimeout(function() { location.reload(); }, 1000);
} else {
showNotice('#domain-status', response.data || wpDomainMapping.messages.error, 'error');
}
},
error: function() {
showNotice('#domain-status', wpDomainMapping.messages.error, 'error');
if (action === 'delete' && !confirm('Are you sure you want to delete the selected domains? This action cannot be undone.')) {
return;
}
$('#domain-status').html('<p>' + wpDomainMapping.messages.processing + '</p>').show();
$.ajax({
url: wpDomainMapping.ajaxUrl,
type: 'POST',
data: {
action: 'dm_handle_actions',
action_type: action,
domains: selectedDomains,
nonce: wpDomainMapping.nonce
},
success: function(response) {
if (response.success) {
showNotice('#domain-status', response.data, 'success');
setTimeout(function() {
location.reload();
}, 1500);
} else {
showNotice('#domain-status', response.data || wpDomainMapping.messages.error, 'error');
}
});
},
error: function() {
showNotice('#domain-status', wpDomainMapping.messages.error, 'error');
}
});
});
// Single domain delete functionality
$('.domain-delete-button').on('click', function(e) {
e.preventDefault();
if (!confirm('Are you sure you want to delete this domain? This action cannot be undone.')) {
return;
}
var domain = $(this).data('domain');
var $row = $(this).closest('tr');
$.ajax({
url: wpDomainMapping.ajaxUrl,
type: 'POST',
data: {
action: 'dm_handle_actions',
action_type: 'delete',
domain: domain,
nonce: wpDomainMapping.nonce
},
success: function(response) {
if (response.success) {
$row.fadeOut(function() {
$(this).remove();
});
showNotice('#domain-status', response.data, 'success');
} else {
showNotice('#domain-status', response.data || wpDomainMapping.messages.error, 'error');
}
},
error: function() {
showNotice('#domain-status', wpDomainMapping.messages.error, 'error');
}
});
});
// Tab switching functionality (for admin settings page)
$('.domain-mapping-tab').on('click', function() {
var tab = $(this).data('tab');
// Update active tab
$('.domain-mapping-tab').removeClass('active');
$(this).addClass('active');
// Show corresponding content
$('.domain-mapping-section').hide();
$('.domain-mapping-section[data-section="' + tab + '"]').show();
// Update URL without reloading
if (history.pushState) {
var url = new URL(window.location);
url.searchParams.set('tab', tab);
window.history.pushState({}, '', url);
}
});
// Copy to clipboard functionality
$('.copy-to-clipboard').on('click', function() {
var text = $(this).data('text');
// Auto-fill server IP if detected
if ($('#ipaddress').length && !$('#ipaddress').val()) {
// This would be populated server-side, but we can enhance it client-side if needed
}
// Domain input formatting - remove protocols and trailing slashes
$('#domain').on('blur', function() {
var domain = $(this).val().trim();
// Remove http:// or https://
domain = domain.replace(/^https?:\/\//, '');
// Remove trailing slash
domain = domain.replace(/\/$/, '');
// Convert to lowercase
domain = domain.toLowerCase();
$(this).val(domain);
});
// Copy to clipboard functionality for DNS instructions
$('.copy-to-clipboard').on('click', function(e) {
e.preventDefault();
var text = $(this).data('text') || $(this).prev('code').text();
// Create temporary input
var tempInput = $('<input>');
$('body').append(tempInput);
tempInput.val(text).select();
document.execCommand('copy');
try {
document.execCommand('copy');
var $btn = $(this);
var originalText = $btn.text();
$btn.text('Copied!').addClass('copied');
setTimeout(function() {
$btn.text(originalText).removeClass('copied');
}, 2000);
} catch (err) {
console.log('Copy failed');
}
tempInput.remove();
var $btn = $(this);
var originalText = $btn.text();
$btn.text('Copied!');
setTimeout(function() { $btn.text(originalText); }, 2000);
});
});
// Form validation enhancement
$('form').on('submit', function() {
var $submitBtn = $(this).find('input[type="submit"], button[type="submit"]');
var originalText = $submitBtn.val() || $submitBtn.text();
// Disable submit button to prevent double submission
$submitBtn.prop('disabled', true);
// Re-enable after a delay
setTimeout(function() {
$submitBtn.prop('disabled', false);
}, 3000);
});
// Enhanced table row highlighting
$('.wp-list-table tbody tr').hover(
function() {
$(this).addClass('hover');
},
function() {
$(this).removeClass('hover');
}
);
// Auto-hide notices after delay
$('.notice.is-dismissible').delay(8000).fadeOut();
// Confirmation for destructive actions
$('a[href*="action=delete"], .delete a').on('click', function(e) {
if (!confirm('Are you sure you want to delete this item? This action cannot be undone.')) {
e.preventDefault();
return false;
}
});
// Enhanced search functionality
$('#domain-filter-form input[type="text"]').on('keypress', function(e) {
if (e.which === 13) { // Enter key
$(this).closest('form').submit();
}
});
// Tooltips for status indicators
if ($.fn.tooltip) {
$('.dashicons[title]').tooltip();
}
// Progress indicator for long-running operations
function showProgress(message) {
var $progress = $('<div class="progress-indicator">' +
'<p>' + message + '</p>' +
'<div class="progress-bar">' +
'<div class="progress-fill"></div>' +
'</div>' +
'</div>');
$('body').append($progress);
return $progress;
}
function hideProgress($progress) {
if ($progress) {
$progress.fadeOut(function() {
$(this).remove();
});
}
}
// Enhanced error handling
$(document).ajaxError(function(event, xhr, settings, thrownError) {
if (xhr.status === 403) {
showNotice('.wrap', 'Permission denied. Please refresh the page and try again.', 'error');
} else if (xhr.status === 500) {
showNotice('.wrap', 'Server error occurred. Please try again later.', 'error');
}
});
// Initialize any existing tab from URL
if (typeof URLSearchParams !== 'undefined') {
var urlParams = new URLSearchParams(window.location.search);
var currentTab = urlParams.get('tab');
if (currentTab && $('.domain-mapping-tab[data-tab="' + currentTab + '"]').length) {
$('.domain-mapping-tab[data-tab="' + currentTab + '"]').click();
}
}
// Smooth scrolling for anchor links
$('a[href^="#"]').on('click', function(e) {
var target = $(this.getAttribute('href'));
if (target.length) {
e.preventDefault();
$('html, body').stop().animate({
scrollTop: target.offset().top - 100
}, 500);
}
});
// Auto-refresh functionality for health checks
var autoRefreshInterval;
function startAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
// Only auto-refresh on health tab
if ($('.domain-mapping-section[data-section="health"]').is(':visible')) {
autoRefreshInterval = setInterval(function() {
// Check if any health checks are running
if ($('.check-domain-health:disabled').length === 0) {
// Optionally auto-refresh health status every 5 minutes
// location.reload();
}
}, 300000); // 5 minutes
}
}
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
}
// Start auto-refresh when health tab is active
$('.domain-mapping-tab[data-tab="health"]').on('click', startAutoRefresh);
// Stop auto-refresh when leaving health tab
$('.domain-mapping-tab:not([data-tab="health"])').on('click', stopAutoRefresh);
// Initialize auto-refresh if health tab is already active
if ($('.domain-mapping-tab[data-tab="health"]').hasClass('active')) {
startAutoRefresh();
}
// Clean up on page unload
$(window).on('beforeunload', function() {
stopAutoRefresh();
});
// Domain tabs switching
$('.domain-mapping-tab').on('click', function() {
$('.domain-mapping-tab').removeClass('active');
$(this).addClass('active');
var tab = $(this).data('tab');
$('.domain-mapping-section').hide();
$('.domain-mapping-section[data-section="' + tab + '"]').show();
});
// Health check single domain
$(document).on('click', '.check-domain-health', function() {
var $button = $(this);
var domain = $button.data('domain');
var originalText = $button.text();
$button.prop('disabled', true).text('Checking...');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'dm_check_domain_health',
domain: domain,
nonce: $('input[name="dm_manual_health_check_nonce"]').val() || wpDomainMapping.nonce
},
success: function(response) {
if (response.success) {
// Refresh page to show updated results
location.reload();
} else {
alert(response.data || 'An error occurred during the health check.');
$button.prop('disabled', false).text(originalText);
}
},
error: function() {
alert('An error occurred during the health check.');
$button.prop('disabled', false).text(originalText);
}
});
});
// Import form handling
$('#domain-mapping-import-form').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
formData.append('action', 'dm_import_csv');
// Show progress bar
$('#import-progress').show();
$('#import-results').hide();
$.ajax({
url: ajaxurl,
type: 'POST',
data: formData,
dataType: 'json',
contentType: false,
processData: false,
success: function(response) {
$('#import-progress').hide();
$('#import-results').show();
if (response.success) {
$('.import-summary').html(
'<div class="notice notice-success"><p>' +
response.data.message +
'</p></div>'
);
if (response.data.details && response.data.details.length > 0) {
var details = '<table class="widefat striped">' +
'<thead><tr>' +
'<th>Status</th>' +
'<th>Details</th>' +
'</tr></thead><tbody>';
$.each(response.data.details, function(i, item) {
var statusClass = '';
if (item.status === 'error') {
statusClass = 'style="background-color: #fef0f0;"';
} else if (item.status === 'warning') {
statusClass = 'style="background-color: #fff8e5;"';
} else if (item.status === 'success') {
statusClass = 'style="background-color: #f0f9eb;"';
}
details += '<tr ' + statusClass + '>' +
'<td><strong>' + item.status.toUpperCase() + '</strong></td>' +
'<td>' + item.message + '</td>' +
'</tr>';
});
details += '</tbody></table>';
$('.import-details').html(details);
}
} else {
$('.import-summary').html(
'<div class="notice notice-error"><p>' +
(response.data || 'Import failed.') +
'</p></div>'
);
}
},
error: function() {
$('#import-progress').hide();
$('#import-results').show();
$('.import-summary').html(
'<div class="notice notice-error"><p>' +
'An error occurred during import.' +
'</p></div>'
);
},
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function(evt) {
if (evt.lengthComputable) {
var percentComplete = (evt.loaded / evt.total) * 100;
$('.progress-bar-inner').css('width', percentComplete + '%');
$('.progress-text').text(Math.round(percentComplete) + '%');
}
}, false);
return xhr;
}
});
});
// Enhanced domain validation
$('#domain').on('input', function() {
var domain = $(this).val();
var $feedback = $('#domain-feedback');
if (!$feedback.length) {
$(this).after('<div id="domain-feedback" class="description"></div>');
$feedback = $('#domain-feedback');
}
if (domain.length > 0) {
// Check for common issues
if (domain.indexOf('http') === 0) {
$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)) {
$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();
}
} else {
$feedback.hide();
}
});
// Keyboard shortcuts
$(document).on('keydown', function(e) {
// Ctrl/Cmd + S to save forms
if ((e.ctrlKey || e.metaKey) && e.which === 83) {
var $activeForm = $('form:visible').first();
if ($activeForm.length) {
e.preventDefault();
$activeForm.submit();
}
}
// Escape key to close modals or cancel actions
if (e.which === 27) {
$('.notice.is-dismissible').fadeOut();
}
});
// Real-time search for domain filter
var searchTimeout;
$('#domain-filter-form input[name="s"]').on('input', function() {
clearTimeout(searchTimeout);
var searchTerm = $(this).val();
searchTimeout = setTimeout(function() {
if (searchTerm.length > 2 || searchTerm.length === 0) {
// Auto-submit form after delay
$('#domain-filter-form').submit();
}
}, 1000);
});
// Initialize page based on current state
function initializePage() {
// Check if we're on the domains page and have messages to show
if (window.location.href.indexOf('page=domains') > -1) {
if (window.location.href.indexOf('updated=add') > -1) {
showNotice('#domain-status', 'Domain added successfully.', 'success');
} else if (window.location.href.indexOf('updated=del') > -1) {
showNotice('#domain-status', 'Domain deleted successfully.', 'success');
}
}
// Auto-focus first input field
$('input[type="text"]:visible:first').focus();
}
// Initialize when document is ready
initializePage();
});

740
includes/class-admin.php Normal file
View file

@ -0,0 +1,740 @@
<?php
/**
* Admin functionality for WP Domain Mapping plugin
*
* @package WP Domain Mapping
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WP Domain Mapping Admin Class
*/
class WP_Domain_Mapping_Admin {
/**
* Class instance
*
* @var WP_Domain_Mapping_Admin
*/
private static $instance = null;
/**
* Database table names
*
* @var array
*/
private $tables = array();
/**
* Get class instance
*
* @return WP_Domain_Mapping_Admin
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->tables = dm_get_table_names();
$this->init();
}
/**
* Initialize admin functionality
*/
private function init() {
// Setup admin-related hooks
$this->setup_admin_hooks();
// Load admin pages
require_once WP_DOMAIN_MAPPING_DIR_PATH . 'admin/pages.php';
}
/**
* Setup admin hooks
*/
private function setup_admin_hooks() {
// Add menu pages
add_action( 'admin_menu', array( $this, 'add_admin_pages' ) );
add_action( 'network_admin_menu', array( $this, 'add_network_admin_pages' ) );
// Add AJAX handlers
add_action( 'wp_ajax_dm_handle_actions', array( $this, 'ajax_handle_actions' ) );
// Add domain link to sites list
add_filter( 'manage_sites_action_links', array( $this, 'add_domain_link_to_sites' ), 10, 2 );
// Add custom columns
add_filter( 'wpmu_blogs_columns', array( $this, 'add_domain_mapping_columns' ) );
add_action( 'manage_blogs_custom_column', array( $this, 'display_domain_mapping_column' ), 1, 3 );
add_action( 'manage_sites_custom_column', array( $this, 'display_domain_mapping_column' ), 1, 3 );
// Handle user domain mapping actions
if ( isset( $_GET['page'] ) && 'domainmapping' === $_GET['page'] ) {
add_action( 'admin_init', array( $this, 'handle_user_domain_actions' ) );
}
// Enqueue admin scripts and styles
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
}
/**
* Add admin menu pages
*/
public function add_admin_pages() {
global $current_site, $wpdb, $wp_db_version;
if ( ! isset( $current_site ) && $wp_db_version >= 15260 ) {
add_action( 'admin_notices', array( $this, 'domain_mapping_warning' ) );
return false;
}
if ( isset($current_site->path) && $current_site->path != "/" ) {
wp_die( esc_html__( "The domain mapping plugin only works if the site is installed in /. This is a limitation of how virtual servers work and is very difficult to work around.", 'wp-domain-mapping' ) );
}
if ( get_site_option( 'dm_user_settings' ) && $current_site->blog_id != $wpdb->blogid && ! $this->sunrise_warning( false ) ) {
add_management_page(
__( 'Domain Mapping', 'wp-domain-mapping' ),
__( 'Domain Mapping', 'wp-domain-mapping' ),
'manage_options',
'domainmapping',
array( $this, 'render_manage_page' )
);
}
}
/**
* Add network admin menu pages
*/
public function add_network_admin_pages() {
add_submenu_page(
'settings.php',
__( 'Domain Mapping', 'wp-domain-mapping' ),
__( 'Domain Mapping', 'wp-domain-mapping' ),
'manage_network',
'domain-mapping',
array( $this, 'render_admin_page' )
);
add_submenu_page(
'sites.php',
__( 'Domains', 'wp-domain-mapping' ),
__( 'Domains', 'wp-domain-mapping' ),
'manage_network',
'domains',
array( $this, 'render_domains_admin' )
);
}
/**
* Enqueue admin scripts and styles
*/
public function enqueue_admin_assets( $hook ) {
// Only load on our admin pages
if ( ! in_array( $hook, array( 'sites_page_domains', 'settings_page_domain-mapping', 'tools_page_domainmapping' ) ) ) {
return;
}
wp_enqueue_style(
'wp-domain-mapping-admin',
WP_DOMAIN_MAPPING_DIR_URL . 'assets/css/admin.css',
array(),
WP_DOMAIN_MAPPING_VERSION
);
wp_enqueue_script(
'wp-domain-mapping-admin',
WP_DOMAIN_MAPPING_DIR_URL . 'assets/js/admin.js',
array( 'jquery' ),
WP_DOMAIN_MAPPING_VERSION,
true
);
wp_localize_script( 'wp-domain-mapping-admin', 'wpDomainMapping', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'domain_mapping' ),
'messages' => array(
'domainRequired' => __( 'Domain is required.', 'wp-domain-mapping' ),
'siteRequired' => __( 'Site ID is required.', 'wp-domain-mapping' ),
'saving' => __( 'Saving...', 'wp-domain-mapping' ),
'processing' => __( 'Processing...', 'wp-domain-mapping' ),
'error' => __( 'An error occurred.', 'wp-domain-mapping' ),
'noSelection' => __( 'Please select at least one domain.', 'wp-domain-mapping' ),
)
));
}
/**
* Check if sunrise.php is properly configured
*
* @param bool $die Whether to die with an error message
* @return bool True if there's a problem, false if everything is okay
*/
public function sunrise_warning( $die = true ) {
if ( ! file_exists( WP_CONTENT_DIR . '/sunrise.php' ) ) {
if ( ! $die ) return true;
if ( dm_is_site_admin() ) {
wp_die(
sprintf(
/* translators: %1$s: Content directory, %2$s: WordPress install path */
esc_html__( 'Please copy sunrise.php to %1$s/sunrise.php and ensure the SUNRISE definition is in %2$swp-config.php', 'wp-domain-mapping' ),
esc_html( WP_CONTENT_DIR ),
esc_html( ABSPATH )
)
);
} else {
wp_die( esc_html__( "This plugin has not been configured correctly yet.", 'wp-domain-mapping' ) );
}
} elseif ( ! defined( 'SUNRISE' ) ) {
if ( ! $die ) return true;
if ( dm_is_site_admin() ) {
wp_die(
sprintf(
/* translators: %s: WordPress install path */
esc_html__( 'Please uncomment the line <em>define( \'SUNRISE\', \'on\' );</em> or add it to your %swp-config.php', 'wp-domain-mapping' ),
esc_html( ABSPATH )
)
);
} else {
wp_die( esc_html__( "This plugin has not been configured correctly yet.", 'wp-domain-mapping' ) );
}
} elseif ( ! defined( 'SUNRISE_LOADED' ) ) {
if ( ! $die ) return true;
if ( dm_is_site_admin() ) {
wp_die(
sprintf(
/* translators: %s: WordPress install path */
esc_html__( 'Please edit your %swp-config.php and move the line <em>define( \'SUNRISE\', \'on\' );</em> above the last require_once() in that file or make sure you updated sunrise.php.', 'wp-domain-mapping' ),
esc_html( ABSPATH )
)
);
} else {
wp_die( esc_html__( "This plugin has not been configured correctly yet.", 'wp-domain-mapping' ) );
}
}
return false;
}
/**
* Display warning message for network configuration
*/
public function domain_mapping_warning() {
echo '<div class="notice notice-error"><p><strong>' . esc_html__( 'Domain Mapping Disabled.', 'wp-domain-mapping' ) . '</strong> ' .
sprintf(
/* translators: %s: URL to WordPress network creation documentation */
wp_kses(
__( 'You must <a href="%1$s">create a network</a> for it to work.', 'wp-domain-mapping' ),
array( 'a' => array( 'href' => array() ) )
),
'http://codex.wordpress.org/Create_A_Network'
) . '</p></div>';
}
/**
* AJAX handler for domain actions
*/
public function ajax_handle_actions() {
check_ajax_referer( 'domain_mapping', 'nonce' );
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'] ) : '' ) );
$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();
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' ) );
}
// Check if domain exists for another blog
$exists = $wpdb->get_var( $wpdb->prepare(
"SELECT domain FROM {$this->tables['domains']} WHERE blog_id != %d AND domain = %s",
$blog_id, $domain
));
if ( null == $exists ) {
$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 ) {
// 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
$success = $wpdb->update(
$this->tables['domains'],
array(
'blog_id' => $blog_id,
'domain' => $domain,
'active' => $active
),
array( 'domain' => $orig_domain ),
array( '%d', '%s', '%d' ),
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( __( 'Domain already exists for another site.', 'wp-domain-mapping' ) );
}
} else {
wp_send_json_error( __( 'Invalid site ID.', 'wp-domain-mapping' ) );
}
break;
case 'delete':
$domains = isset( $_POST['domains'] ) ? array_map( 'sanitize_text_field', (array) $_POST['domains'] ) : array( $domain );
$wpdb->query( 'START TRANSACTION' );
$deleted = 0;
try {
foreach ( $domains as $del_domain ) {
if ( empty( $del_domain ) ) continue;
// 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 ( $affected_blog_id ) {
// Delete the domain
$result = $wpdb->delete(
$this->tables['domains'],
array( 'domain' => $del_domain ),
array( '%s' )
);
if ( $result ) {
$deleted++;
// Log the action
dm_log_action( 'delete', $del_domain, $affected_blog_id, $current_user_id );
}
}
}
if ( $deleted > 0 ) {
$wpdb->query( 'COMMIT' );
$message = sprintf(
_n(
'Domain deleted successfully.',
'%d domains deleted successfully.',
$deleted,
'wp-domain-mapping'
),
$deleted
);
wp_send_json_success( $message );
} else {
$wpdb->query( 'ROLLBACK' );
wp_send_json_error( __( 'No domains were deleted.', 'wp-domain-mapping' ) );
}
} catch ( Exception $e ) {
$wpdb->query( 'ROLLBACK' );
wp_send_json_error( __( 'An error occurred while deleting domains.', 'wp-domain-mapping' ) );
}
break;
default:
wp_send_json_error( __( 'Invalid action.', 'wp-domain-mapping' ) );
}
}
/**
* Handle user domain mapping actions
*/
public function handle_user_domain_actions() {
global $wpdb, $parent_file;
$url = add_query_arg( array( 'page' => 'domainmapping' ), admin_url( $parent_file ) );
if ( ! empty( $_POST['action'] ) ) {
$domain = isset( $_POST['domain'] ) ? sanitize_text_field( $_POST['domain'] ) : '';
if ( empty( $domain ) ) {
wp_die( esc_html__( "You must enter a domain", 'wp-domain-mapping' ) );
}
check_admin_referer( 'domain_mapping' );
do_action( 'dm_handle_actions_init', $domain );
switch ( $_POST['action'] ) {
case "add":
do_action( 'dm_handle_actions_add', $domain );
// Validate domain format
if ( ! dm_validate_domain( $domain ) ) {
wp_die( esc_html__( "Invalid domain format", 'wp-domain-mapping' ) );
}
// Check if domain already exists
$domain_exists = $wpdb->get_row( $wpdb->prepare(
"SELECT blog_id FROM {$wpdb->blogs} WHERE domain = %s OR (SELECT blog_id FROM {$this->tables['domains']} WHERE domain = %s)",
$domain, $domain
));
if ( null == $domain_exists ) {
// If primary, reset other domains to not primary
if ( isset( $_POST['primary'] ) && $_POST['primary'] ) {
$wpdb->update(
$this->tables['domains'],
array( 'active' => 0 ),
array( 'blog_id' => $wpdb->blogid ),
array( '%d' ),
array( '%d' )
);
}
// Insert new domain
$wpdb->insert(
$this->tables['domains'],
array(
'blog_id' => $wpdb->blogid,
'domain' => $domain,
'active' => isset( $_POST['primary'] ) ? 1 : 0
),
array( '%d', '%s', '%d' )
);
wp_redirect( add_query_arg( array( 'updated' => 'add' ), $url ) );
exit;
} else {
wp_redirect( add_query_arg( array( 'updated' => 'exists' ), $url ) );
exit;
}
break;
case "primary":
do_action( 'dm_handle_actions_primary', $domain );
// Reset all domains to not primary
$wpdb->update(
$this->tables['domains'],
array( 'active' => 0 ),
array( 'blog_id' => $wpdb->blogid ),
array( '%d' ),
array( '%d' )
);
// Check if domain is not the original domain
$core = WP_Domain_Mapping_Core::get_instance();
$orig_url = parse_url( $core->get_original_url( 'siteurl' ) );
if ( $domain != $orig_url['host'] ) {
// Set the selected domain as primary
$wpdb->update(
$this->tables['domains'],
array( 'active' => 1 ),
array(
'domain' => $domain,
'blog_id' => $wpdb->blogid
),
array( '%d' ),
array( '%s', '%d' )
);
}
wp_redirect( add_query_arg( array( 'updated' => 'primary' ), $url ) );
exit;
break;
}
} elseif ( isset( $_GET['action'] ) && $_GET['action'] == 'delete' ) {
$domain = sanitize_text_field( $_GET['domain'] );
if ( empty( $domain ) ) {
wp_die( esc_html__( "You must enter a domain", 'wp-domain-mapping' ) );
}
check_admin_referer( "delete" . $_GET['domain'] );
do_action( 'dm_handle_actions_del', $domain );
// Delete the domain
$wpdb->delete(
$this->tables['domains'],
array( 'domain' => $domain ),
array( '%s' )
);
wp_redirect( add_query_arg( array( 'updated' => 'del' ), $url ) );
exit;
}
}
/**
* Render domains admin page
*/
public function render_domains_admin() {
if ( ! dm_is_site_admin() ) {
return false;
}
$this->sunrise_warning();
// Include the admin page template
dm_render_domains_page();
}
/**
* Render admin configuration page
*/
public function render_admin_page() {
if ( ! dm_is_site_admin() ) {
return false;
}
$this->sunrise_warning();
global $current_site;
if ( isset($current_site->path) && $current_site->path != "/" ) {
wp_die( sprintf(
esc_html__( "<strong>Warning!</strong> This plugin will only work if WordPress is installed in the root directory of your webserver. It is currently installed in '%s'.", "wp-domain-mapping" ),
esc_html( $current_site->path )
));
}
// Initialize options if needed
if ( get_site_option( 'dm_remote_login', 'NA' ) == 'NA' ) {
add_site_option( 'dm_remote_login', 1 );
}
if ( get_site_option( 'dm_redirect_admin', 'NA' ) == 'NA' ) {
add_site_option( 'dm_redirect_admin', 1 );
}
if ( get_site_option( 'dm_user_settings', 'NA' ) == 'NA' ) {
add_site_option( 'dm_user_settings', 1 );
}
// Handle form submission
if ( ! empty( $_POST['action'] ) && $_POST['action'] == 'update' ) {
check_admin_referer( 'domain_mapping' );
// Validate and save IP addresses
$ipok = true;
$ipaddresses = explode( ',', sanitize_text_field( $_POST['ipaddress'] ) );
foreach ( $ipaddresses as $address ) {
if ( ( $ip = trim( $address ) ) && ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
$ipok = false;
break;
}
}
if ( $ipok ) {
update_site_option( 'dm_ipaddress', sanitize_text_field( $_POST['ipaddress'] ) );
}
// Save remote login option
if ( intval( $_POST['always_redirect_admin'] ) == 0 ) {
$_POST['dm_remote_login'] = 0;
}
update_site_option( 'dm_remote_login', intval( $_POST['dm_remote_login'] ) );
// Validate and save CNAME
if ( ! preg_match( '/(--|\.\.)/', $_POST['cname'] ) && preg_match( '|^([a-zA-Z0-9-\.])+$|', $_POST['cname'] ) ) {
update_site_option( 'dm_cname', sanitize_text_field( $_POST['cname'] ) );
} else {
update_site_option( 'dm_cname', '' );
}
// Save other options
update_site_option( 'dm_301_redirect', isset( $_POST['permanent_redirect'] ) ? intval( $_POST['permanent_redirect'] ) : 0 );
update_site_option( 'dm_redirect_admin', isset( $_POST['always_redirect_admin'] ) ? intval( $_POST['always_redirect_admin'] ) : 0 );
update_site_option( 'dm_user_settings', isset( $_POST['dm_user_settings'] ) ? intval( $_POST['dm_user_settings'] ) : 0 );
update_site_option( 'dm_no_primary_domain', isset( $_POST['dm_no_primary_domain'] ) ? intval( $_POST['dm_no_primary_domain'] ) : 0 );
// Add settings saved notice
add_settings_error(
'dm_settings',
'settings_updated',
__( 'Settings saved.', 'wp-domain-mapping' ),
'updated'
);
}
// Include the admin page template
dm_render_admin_page();
}
/**
* Render user management page
*/
public function render_manage_page() {
global $wpdb, $parent_file;
if ( isset( $_GET['updated'] ) ) {
do_action( 'dm_echo_updated_msg' );
}
$this->sunrise_warning();
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' ),
array( 'a' => array( 'href' => array() ) )
);
} else {
esc_html_e( "This plugin has not been configured correctly yet.", 'wp-domain-mapping' );
}
return false;
}
$protocol = is_ssl() ? 'https://' : 'http://';
$domains = dm_get_domains_by_blog_id( $wpdb->blogid );
// Include the user page template
dm_render_user_page( $protocol, $domains );
}
/**
* Add "Domains" link to sites list
*
* @param array $actions The row actions
* @param int $blog_id The blog ID
* @return array Modified actions
*/
public function add_domain_link_to_sites( $actions, $blog_id ) {
$domains_url = add_query_arg(
array( 'page' => 'domains', 'blog_id' => $blog_id ),
network_admin_url( 'sites.php' )
);
$actions['domains'] = '<a href="' . esc_url( $domains_url ) . '">' .
esc_html__( 'Domains', 'wp-domain-mapping' ) . '</a>';
return $actions;
}
/**
* Add domain mapping columns to sites list
*
* @param array $columns The columns
* @return array Modified columns
*/
public function add_domain_mapping_columns( $columns ) {
$columns['map'] = __( 'Mapping' );
return $columns;
}
/**
* Display domain mapping column content
*
* @param string $column The column name
* @param int $blog_id The blog ID
*/
public function display_domain_mapping_column( $column, $blog_id ) {
global $wpdb;
static $maps = false;
if ( $column == 'map' ) {
if ( $maps === false ) {
$work = $wpdb->get_results( "SELECT blog_id, domain FROM {$this->tables['domains']} ORDER BY blog_id" );
$maps = array();
if ( $work ) {
foreach ( $work as $blog ) {
$maps[$blog->blog_id][] = $blog->domain;
}
}
}
if ( ! empty( $maps[$blog_id] ) && is_array( $maps[$blog_id] ) ) {
foreach ( $maps[$blog_id] as $blog ) {
echo esc_html( $blog ) . '<br />';
}
}
}
}
/**
* Default updated messages
*/
public function echo_default_updated_msg() {
if ( ! isset( $_GET['updated'] ) ) {
return;
}
switch ( $_GET['updated'] ) {
case "add":
$msg = __( 'New domain added.', 'wp-domain-mapping' );
break;
case "exists":
$msg = __( 'New domain already exists.', 'wp-domain-mapping' );
break;
case "primary":
$msg = __( 'New primary domain.', 'wp-domain-mapping' );
break;
case "del":
$msg = __( 'Domain deleted.', 'wp-domain-mapping' );
break;
default:
return;
}
echo "<div class='notice notice-success'><p>" . esc_html( $msg ) . "</p></div>";
}
}
// Add default updated messages action
add_action( 'dm_echo_updated_msg', array( WP_Domain_Mapping_Admin::class, 'echo_default_updated_msg' ) );

593
includes/class-core.php Normal file
View file

@ -0,0 +1,593 @@
<?php
/**
* Core functionality for WP Domain Mapping plugin
*
* @package WP Domain Mapping
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WP Domain Mapping Core Class
*/
class WP_Domain_Mapping_Core {
/**
* Class instance
*
* @var WP_Domain_Mapping_Core
*/
private static $instance = null;
/**
* Database table names
*
* @var array
*/
private $tables = array();
/**
* Get class instance
*
* @return WP_Domain_Mapping_Core
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->tables = dm_get_table_names();
$this->init();
}
/**
* Initialize core functionality
*/
private function init() {
// Setup general hooks
$this->setup_general_hooks();
// Setup domain mapping filters if DOMAIN_MAPPING is defined
if ( defined( 'DOMAIN_MAPPING' ) ) {
$this->setup_domain_mapping_filters();
} else {
add_filter( 'admin_url', array( $this, 'domain_mapping_adminurl' ), 10, 3 );
}
}
/**
* Setup general hooks
*/
private function setup_general_hooks() {
// Delete domain mappings when a blog is deleted
add_action( 'delete_blog', array( $this, 'delete_blog_domain_mapping' ), 1, 2 );
// Handle domain redirection
add_action( 'template_redirect', array( $this, 'redirect_to_mapped_domain' ) );
// Handle admin area redirection
add_action( 'admin_init', array( $this, 'redirect_admin' ) );
// Handle remote login
if ( isset( $_GET['dm'] ) ) {
add_action( 'template_redirect', array( $this, 'remote_login_js' ) );
}
}
/**
* Setup domain mapping filters when DOMAIN_MAPPING is defined
*/
private function setup_domain_mapping_filters() {
add_filter( 'plugins_url', array( $this, 'domain_mapping_plugins_uri' ), 1 );
add_filter( 'theme_root_uri', array( $this, 'domain_mapping_themes_uri' ), 1 );
add_filter( 'pre_option_siteurl', array( $this, 'domain_mapping_siteurl' ) );
add_filter( 'pre_option_home', array( $this, 'domain_mapping_siteurl' ) );
add_filter( 'the_content', array( $this, 'domain_mapping_post_content' ) );
add_action( 'wp_head', array( $this, 'remote_login_js_loader' ) );
add_action( 'login_head', array( $this, 'redirect_login_to_orig' ) );
add_action( 'wp_logout', array( $this, 'remote_logout_loader' ), 9999 );
add_filter( 'stylesheet_uri', array( $this, 'domain_mapping_post_content' ) );
add_filter( 'stylesheet_directory', array( $this, 'domain_mapping_post_content' ) );
add_filter( 'stylesheet_directory_uri', array( $this, 'domain_mapping_post_content' ) );
add_filter( 'template_directory', array( $this, 'domain_mapping_post_content' ) );
add_filter( 'template_directory_uri', array( $this, 'domain_mapping_post_content' ) );
add_filter( 'plugins_url', array( $this, 'domain_mapping_post_content' ) );
}
/**
* Create required database tables
*/
public function create_tables() {
global $wpdb;
// Only network admins can create tables
if ( ! dm_is_site_admin() ) {
return;
}
// Initialize remote login hash
$this->get_hash();
// Use static variable to prevent repeated execution
static $tables_created = false;
if ( $tables_created ) {
return;
}
$created = 0;
$charset_collate = $wpdb->get_charset_collate();
// Create domain_mapping table
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,
`blog_id` bigint(20) NOT NULL,
`domain` varchar(255) NOT NULL,
`active` tinyint(4) default '1',
PRIMARY KEY (`id`),
KEY `blog_id` (`blog_id`,`domain`,`active`)
) $charset_collate;" );
$created = 1;
}
// Create domain_mapping_logins table
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`)
) $charset_collate;" );
$created = 1;
}
// Create domain_mapping_logs table
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,
`user_id` bigint(20) NOT NULL,
`action` varchar(50) NOT NULL,
`domain` varchar(255) NOT NULL,
`blog_id` bigint(20) NOT NULL,
`timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) $charset_collate;" );
$created = 1;
}
// If any table was created, show success message and mark as created
if ( $created ) {
add_action( 'admin_notices', function() {
echo '<div class="notice notice-success"><p><strong>' .
esc_html__( 'Domain mapping database tables created.', 'wp-domain-mapping' ) .
'</strong></p></div>';
});
$tables_created = true;
}
}
/**
* Delete blog domain mappings when a blog is deleted
*
* @param int $blog_id The blog ID
* @param bool $drop Whether to drop tables
*/
public function delete_blog_domain_mapping( $blog_id, $drop ) {
global $wpdb;
if ( $blog_id && $drop ) {
$domains = $wpdb->get_col( $wpdb->prepare(
"SELECT domain FROM {$this->tables['domains']} WHERE blog_id = %d",
$blog_id
));
do_action( 'dm_delete_blog_domain_mappings', $domains );
$wpdb->delete(
$this->tables['domains'],
array( 'blog_id' => $blog_id ),
array( '%d' )
);
}
}
/**
* Redirect to mapped domain
*/
public function redirect_to_mapped_domain() {
global $current_blog, $wpdb;
if ( is_main_site() || ( isset( $_GET['preview'] ) && $_GET['preview'] == 'true' ) ||
( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && $_POST['customize'] == 'on' ) ) {
return;
}
$protocol = is_ssl() ? 'https://' : 'http://';
$url = $this->domain_mapping_siteurl( false );
if ( $url && $url != untrailingslashit( $protocol . $current_blog->domain . $current_blog->path ) ) {
$redirect = get_site_option( 'dm_301_redirect' ) ? '301' : '302';
if ( ( defined( 'VHOST' ) && constant("VHOST") != 'yes' ) ||
( defined( 'SUBDOMAIN_INSTALL' ) && constant( 'SUBDOMAIN_INSTALL' ) == false ) ) {
$_SERVER['REQUEST_URI'] = str_replace( $current_blog->path, '/', $_SERVER['REQUEST_URI'] );
}
wp_redirect( $url . $_SERVER['REQUEST_URI'], $redirect );
exit;
}
}
/**
* Redirect admin area if needed
*/
public function redirect_admin() {
if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin/admin-ajax.php' ) !== false ) {
return;
}
if ( get_site_option( 'dm_redirect_admin' ) ) {
$url = $this->get_original_url( 'siteurl' );
if ( false === strpos( $url, $_SERVER['HTTP_HOST'] ) ) {
wp_redirect( untrailingslashit( $url ) . $_SERVER['REQUEST_URI'] );
exit;
}
} else {
global $current_blog;
$url = $this->domain_mapping_siteurl( false );
$request_uri = str_replace( $current_blog->path, '/', $_SERVER['REQUEST_URI'] );
if ( false === strpos( $url, $_SERVER['HTTP_HOST'] ) ) {
wp_redirect( str_replace( '//wp-admin', '/wp-admin', trailingslashit( $url ) . $request_uri ) );
exit;
}
}
}
/**
* Map domain for site URL
*
* @param string $setting The setting value
* @return string The mapped URL
*/
public function domain_mapping_siteurl( $setting ) {
global $wpdb, $current_blog;
static $return_url = array();
// Check if already cached
if ( isset( $return_url[$wpdb->blogid] ) ) {
if ( $return_url[$wpdb->blogid] !== false ) {
return $return_url[$wpdb->blogid];
}
return $setting;
}
$s = $wpdb->suppress_errors();
// Handle primary domain checking based on settings
if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) {
if ( isset( $_SERVER['HTTP_HOST'] ) ) {
$domain = $wpdb->get_var( $wpdb->prepare(
"SELECT domain FROM {$this->tables['domains']} WHERE blog_id = %d AND domain = %s LIMIT 1",
$wpdb->blogid, $_SERVER['HTTP_HOST']
));
if ( null == $domain ) {
$return_url[$wpdb->blogid] = untrailingslashit( $this->get_original_url( "siteurl" ) );
return $return_url[$wpdb->blogid];
}
}
} else {
$domain = $wpdb->get_var( $wpdb->prepare(
"SELECT domain FROM {$this->tables['domains']} WHERE blog_id = %d AND active = 1 LIMIT 1",
$wpdb->blogid
));
if ( null == $domain ) {
$return_url[$wpdb->blogid] = untrailingslashit( $this->get_original_url( "siteurl" ) );
return $return_url[$wpdb->blogid];
}
}
$wpdb->suppress_errors( $s );
$protocol = is_ssl() ? 'https://' : 'http://';
if ( $domain ) {
$return_url[$wpdb->blogid] = untrailingslashit( $protocol . $domain );
$setting = $return_url[$wpdb->blogid];
} else {
$return_url[$wpdb->blogid] = false;
}
return $setting;
}
/**
* Get the original URL for a site
*
* @param string $url_type The URL type (siteurl or home)
* @param int $blog_id The blog ID
* @return string The original URL
*/
public function get_original_url( $url_type, $blog_id = 0 ) {
global $wpdb;
$id = $blog_id ?: $wpdb->blogid;
static $orig_urls = array();
if ( ! isset( $orig_urls[$id] ) ) {
// Remove filter to avoid infinite loop
if ( defined( 'DOMAIN_MAPPING' ) ) {
remove_filter( 'pre_option_' . $url_type, array( $this, 'domain_mapping_siteurl' ) );
}
$orig_url = $blog_id == 0 ? get_option( $url_type ) : get_blog_option( $blog_id, $url_type );
$orig_url = is_ssl() ? str_replace( "http://", "https://", $orig_url ) : str_replace( "https://", "http://", $orig_url );
$orig_urls[$id] = $orig_url;
// Restore filter
if ( defined( 'DOMAIN_MAPPING' ) ) {
add_filter( 'pre_option_' . $url_type, array( $this, 'domain_mapping_siteurl' ) );
}
}
return $orig_urls[$id];
}
/**
* Map domain for admin URL
*
* @param string $url The URL
* @param string $path The path
* @param int $blog_id The blog ID
* @return string The mapped URL
*/
public function domain_mapping_adminurl( $url, $path, $blog_id = 0 ) {
$index = strpos( $url, '/wp-admin' );
if ( $index !== false ) {
$url = $this->get_original_url( 'siteurl', $blog_id ) . substr( $url, $index );
if ( ( is_ssl() || force_ssl_admin() ) && 0 === strpos( $url, 'http://' ) ) {
$url = 'https://' . substr( $url, 7 );
}
}
return $url;
}
/**
* Map domains in post content
*
* @param string $post_content The post content
* @return string The mapped content
*/
public function domain_mapping_post_content( $post_content ) {
$orig_url = $this->get_original_url( 'siteurl' );
$url = $this->domain_mapping_siteurl( 'NA' );
if ( $url == 'NA' ) {
return $post_content;
}
return str_replace( $orig_url, $url, $post_content );
}
/**
* Map domains in plugin URLs
*
* @param string $full_url The full URL
* @param string $path The path
* @param string $plugin The plugin
* @return string The mapped URL
*/
public function domain_mapping_plugins_uri( $full_url, $path = null, $plugin = null ) {
return get_option( 'siteurl' ) . substr( $full_url, stripos( $full_url, PLUGINDIR ) - 1 );
}
/**
* Map domains in theme URLs
*
* @param string $full_url The full URL
* @return string The mapped URL
*/
public function domain_mapping_themes_uri( $full_url ) {
return str_replace( $this->get_original_url( 'siteurl' ), get_option( 'siteurl' ), $full_url );
}
/**
* Get the domain mapping hash
*
* @return string The hash
*/
public function get_hash() {
$remote_login_hash = get_site_option( 'dm_hash' );
if ( null == $remote_login_hash ) {
$remote_login_hash = md5( time() );
update_site_option( 'dm_hash', $remote_login_hash );
}
return $remote_login_hash;
}
/**
* Redirect login to original domain
*/
public function redirect_login_to_orig() {
if ( ! get_site_option( 'dm_remote_login' ) ||
( isset( $_GET['action'] ) && $_GET['action'] == 'logout' ) ||
isset( $_GET['loggedout'] ) ) {
return;
}
$url = $this->get_original_url( 'siteurl' );
if ( $url != site_url() ) {
$url .= "/wp-login.php";
echo "<script type='text/javascript'>\nwindow.location = '" . esc_url( $url ) . "'</script>";
}
}
/**
* Handle logout across domains
*/
public function remote_logout_loader() {
global $current_site, $current_blog, $wpdb;
$protocol = is_ssl() ? 'https://' : 'http://';
$hash = $this->get_hash();
$key = md5( time() );
$wpdb->insert(
$this->tables['logins'],
array(
'id' => $key,
'user_id' => 0,
'blog_id' => $current_blog->blog_id,
't' => current_time( 'mysql' )
),
array( '%s', '%d', '%d', '%s' )
);
if ( get_site_option( 'dm_redirect_admin' ) ) {
wp_redirect( $protocol . $current_site->domain . $current_site->path .
"?dm={$hash}&action=logout&blogid={$current_blog->blog_id}&k={$key}&t=" . mt_rand() );
exit;
}
}
/**
* Handle remote login JS
*/
public function remote_login_js() {
global $current_blog, $current_user, $wpdb;
if ( 0 == get_site_option( 'dm_remote_login' ) ) {
return;
}
$hash = $this->get_hash();
$protocol = is_ssl() ? 'https://' : 'http://';
if ( $_GET['dm'] == $hash ) {
if ( $_GET['action'] == 'load' ) {
if ( ! is_user_logged_in() ) {
exit;
}
$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' )
);
$url = add_query_arg(
array(
'action' => 'login',
'dm' => $hash,
'k' => $key,
't' => mt_rand()
),
$_GET['back']
);
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
));
if ( $details ) {
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 )
));
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;
} 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']
));
if ( $details ) {
$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
*/
public function remote_login_js_loader() {
global $current_site, $current_blog;
if ( 0 == get_site_option( 'dm_remote_login' ) || is_user_logged_in() ) {
return;
}
$protocol = is_ssl() ? 'https://' : 'http://';
$hash = $this->get_hash();
echo "<script src='" . esc_url( $protocol . $current_site->domain . $current_site->path .
"?dm={$hash}&action=load&blogid={$current_blog->blog_id}&siteid={$current_blog->site_id}&t=" .
mt_rand() . "&back=" . urlencode( $protocol . $current_blog->domain . $_SERVER['REQUEST_URI'] ) ) .
"' type='text/javascript'></script>";
}
}

File diff suppressed because it is too large Load diff

341
includes/functions.php Normal file
View file

@ -0,0 +1,341 @@
<?php
/**
* Common functions for WP Domain Mapping plugin
*
* @package WP Domain Mapping
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Ensure URL has a protocol
*
* @param string $domain Domain name
* @return string Domain with protocol
*/
function dm_ensure_protocol( $domain ) {
if ( preg_match( '#^https?://#', $domain ) ) {
return $domain;
}
return 'http://' . $domain;
}
/**
* Clean domain name (remove protocol and trailing slash)
*
* @param string $domain Domain name
* @return string Cleaned domain
*/
function dm_clean_domain( $domain ) {
// Remove protocol
$domain = preg_replace( '#^https?://#', '', $domain );
// Remove trailing slash
$domain = rtrim( $domain, '/' );
// Convert IDN to ASCII (Punycode)
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 );
} else {
// PHP < 7.2
$domain = idn_to_ascii( $domain );
}
}
return $domain;
}
/**
* Validate a domain name
*
* @param string $domain The domain
* @return bool True if valid
*/
function dm_validate_domain( $domain ) {
// Basic validation
return (bool) preg_match( '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i', $domain );
}
/**
* Display IDN warning message
*
* @return string Warning message
*/
function dm_idn_warning() {
return sprintf(
/* translators: %s: URL to punycode converter */
wp_kses(
__( 'International Domain Names should be in <a href="%s" target="_blank">punycode</a> format.', 'wp-domain-mapping' ),
array( 'a' => array( 'href' => array(), 'target' => array() ) )
),
'https://www.punycoder.com/'
);
}
/**
* Check if user is a site admin
*
* @return bool True if user is a site admin
*/
function dm_is_site_admin() {
return current_user_can( 'manage_network' );
}
/**
* Get domain mapping table names
*
* @return array Array of table names
*/
function dm_get_table_names() {
global $wpdb;
return array(
'domains' => $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS,
'logins' => $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGINS,
'logs' => $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGS,
);
}
/**
* Log domain mapping action
*
* @param string $action Action type
* @param string $domain Domain name
* @param int $blog_id Blog ID
* @param int $user_id User ID (optional)
*/
function dm_log_action( $action, $domain, $blog_id, $user_id = null ) {
global $wpdb;
if ( null === $user_id ) {
$user_id = get_current_user_id();
}
$tables = dm_get_table_names();
$wpdb->insert(
$tables['logs'],
array(
'user_id' => $user_id,
'action' => $action,
'domain' => $domain,
'blog_id' => $blog_id
),
array( '%d', '%s', '%s', '%d' )
);
}
/**
* Get domain by name
*
* @param string $domain Domain name
* @return object|null Domain object or null
*/
function dm_get_domain_by_name( $domain ) {
global $wpdb;
$tables = dm_get_table_names();
return $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$tables['domains']} WHERE domain = %s",
$domain
));
}
/**
* Get domains by blog ID
*
* @param int $blog_id Blog ID
* @return array Array of domain objects
*/
function dm_get_domains_by_blog_id( $blog_id ) {
global $wpdb;
$tables = dm_get_table_names();
return $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$tables['domains']} WHERE blog_id = %d ORDER BY active DESC, domain ASC",
$blog_id
));
}
/**
* Add a new domain mapping
*
* @param int $blog_id Blog ID
* @param string $domain Domain name
* @param int $active Whether domain is primary (1) or not (0)
* @return bool|int False on failure, insert ID on success
*/
function dm_add_domain( $blog_id, $domain, $active = 0 ) {
global $wpdb;
$tables = dm_get_table_names();
$domain = dm_clean_domain( $domain );
// Validate domain
if ( ! dm_validate_domain( $domain ) ) {
return false;
}
// Check if domain already exists
if ( dm_get_domain_by_name( $domain ) ) {
return false;
}
// If setting as primary, reset other domains
if ( $active ) {
$wpdb->update(
$tables['domains'],
array( 'active' => 0 ),
array( 'blog_id' => $blog_id ),
array( '%d' ),
array( '%d' )
);
}
// Insert new domain
$result = $wpdb->insert(
$tables['domains'],
array(
'blog_id' => $blog_id,
'domain' => $domain,
'active' => $active
),
array( '%d', '%s', '%d' )
);
if ( $result ) {
dm_log_action( 'add', $domain, $blog_id );
return $wpdb->insert_id;
}
return false;
}
/**
* Update domain mapping
*
* @param string $domain Domain name
* @param int $blog_id Blog ID
* @param int $active Whether domain is primary (1) or not (0)
* @return bool True on success, false on failure
*/
function dm_update_domain( $domain, $blog_id, $active ) {
global $wpdb;
$tables = dm_get_table_names();
// If setting as primary, reset other domains
if ( $active ) {
$wpdb->update(
$tables['domains'],
array( 'active' => 0 ),
array( 'blog_id' => $blog_id ),
array( '%d' ),
array( '%d' )
);
}
// Update domain
$result = $wpdb->update(
$tables['domains'],
array( 'active' => $active ),
array( 'domain' => $domain ),
array( '%d' ),
array( '%s' )
);
if ( $result !== false ) {
dm_log_action( 'edit', $domain, $blog_id );
return true;
}
return false;
}
/**
* Delete domain mapping
*
* @param string $domain Domain name
* @return bool True on success, false on failure
*/
function dm_delete_domain( $domain ) {
global $wpdb;
$tables = dm_get_table_names();
// Get domain info for logging
$domain_info = dm_get_domain_by_name( $domain );
if ( ! $domain_info ) {
return false;
}
// Delete domain
$result = $wpdb->delete(
$tables['domains'],
array( 'domain' => $domain ),
array( '%s' )
);
if ( $result ) {
dm_log_action( 'delete', $domain, $domain_info->blog_id );
return true;
}
return false;
}
/**
* Save health check result
*
* @param string $domain Domain name
* @param array $result Health check result
*/
function dm_save_health_result( $domain, $result ) {
$health_results = get_site_option( 'dm_domain_health_results', array() );
$domain_key = md5( $domain );
$health_results[$domain_key] = $result;
update_site_option( 'dm_domain_health_results', $health_results );
}
/**
* Get health check result
*
* @param string $domain Domain name
* @return array|null Health check result or null
*/
function dm_get_health_result( $domain ) {
$health_results = get_site_option( 'dm_domain_health_results', array() );
$domain_key = md5( $domain );
return isset( $health_results[$domain_key] ) ? $health_results[$domain_key] : null;
}
/**
* Format action name for display
*
* @param string $action Action name
* @return string Formatted action name
*/
function dm_format_action_name( $action ) {
switch ( $action ) {
case 'add':
return __( 'Added', 'wp-domain-mapping' );
case 'edit':
return __( 'Updated', 'wp-domain-mapping' );
case 'delete':
return __( 'Deleted', 'wp-domain-mapping' );
case 'import':
return __( 'Imported', 'wp-domain-mapping' );
default:
return ucfirst( $action );
}
}

File diff suppressed because it is too large Load diff