From 31bf7145ea0e6017c20bfbb6e24b6c80e53a793f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E6=B4=BE=E5=A4=87=E6=A1=88?= <130886204+modiqi@users.noreply.github.com> Date: Tue, 20 May 2025 22:52:24 +0800 Subject: [PATCH] =?UTF-8?q?v2.0=20=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/domains-page.php | 514 ++++++++ admin/logs-table.php | 288 +++++ admin/settings-page.php | 522 ++++++++ admin/templates.php | 964 ++++++++++++++ admin/user-page.php | 183 +++ assets/css/admin.css | 64 + assets/js/admin.js | 134 ++ includes/admin-ui.php | 389 ++++++ includes/class-health.php | 897 +++++++++++++ includes/class-importer.php | 592 +++++++++ includes/class-site-id.php | 132 ++ includes/class-tools.php | 709 ++++++++++ sunrise.php | 96 +- wp-domain-mapping.php | 2434 ++++++++++++++++++++--------------- 14 files changed, 6815 insertions(+), 1103 deletions(-) create mode 100644 admin/domains-page.php create mode 100644 admin/logs-table.php create mode 100644 admin/settings-page.php create mode 100644 admin/templates.php create mode 100644 admin/user-page.php create mode 100644 assets/css/admin.css create mode 100644 assets/js/admin.js create mode 100644 includes/admin-ui.php create mode 100644 includes/class-health.php create mode 100644 includes/class-importer.php create mode 100644 includes/class-site-id.php create mode 100644 includes/class-tools.php diff --git a/admin/domains-page.php b/admin/domains-page.php new file mode 100644 index 0000000..2899122 --- /dev/null +++ b/admin/domains-page.php @@ -0,0 +1,514 @@ + +
+

+ + + + + + + + + +

+ +
+

+ + +
+ +
+

+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + + + +
+
+
+
+ +
+
+ + +
+ +
+
+ +
+ prepare( + "domain LIKE %s", + '%' . $wpdb->esc_like( sanitize_text_field( $_GET['s'] ) ) . '%' + ); + } + + if ( ! empty( $_GET['blog_id'] ) ) { + $where[] = $wpdb->prepare( + "blog_id = %d", + absint( $_GET['blog_id'] ) + ); + } + + if ( isset( $_GET['active'] ) && $_GET['active'] !== '' ) { + $where[] = $wpdb->prepare( + "active = %d", + absint( $_GET['active'] ) + ); + } + + $where_sql = $where ? ' WHERE ' . implode( ' AND ', $where ) : ''; + + // Count total items for pagination + $total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_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", + $offset, + $per_page + )); + + // Display the domains table + dm_domain_listing( $rows ); + + // Pagination + if ( $total_pages > 1 ) : + ?> +
+
+ add_query_arg( 'paged', '%#%' ), + 'format' => '', + 'prev_text' => __( '«' ), + 'next_text' => __( '»' ), + 'total' => $total_pages, + 'current' => $paged, + 'mid_size' => 2, + 'end_size' => 1, + ) ); + ?> + + + +
+
+ + +

+ Note: %s', 'wp-domain-mapping' ), + dm_idn_warning() + ); + ?> +

+
+
+ + +
+
+ +
+

+ + + + + + + + + + + + + + + +
+ get_var( "SELECT COUNT(DISTINCT blog_id) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS ); + echo esc_html( $sites_with_domains ); + ?> +
+
+
+ + + + diff --git a/admin/logs-table.php b/admin/logs-table.php new file mode 100644 index 0000000..739649b --- /dev/null +++ b/admin/logs-table.php @@ -0,0 +1,288 @@ +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 '

' . esc_html__( 'No domain mapping logs available.', 'wp-domain-mapping' ) . '

'; + return; +} +?> + +
+
+ + + +
+ + + + + + + + + + +
+
+ + 1 ) : ?> +
+ add_query_arg( 'logs_paged', '%#%' ), + 'format' => '', + 'prev_text' => __( '«' ), + 'next_text' => __( '»' ), + 'total' => $total_pages, + 'current' => $paged, + 'mid_size' => 2, + 'end_size' => 1, + ) ); + ?> + + + +
+ +
+ + + + + + + + + + + + + user_id ); + $username = $user_data + ? sprintf( + '%s', + esc_url( network_admin_url( 'user-edit.php?user_id=' . $log->user_id ) ), + esc_html( $user_data->user_login ) + ) + : sprintf( esc_html__( 'User #%d', 'wp-domain-mapping' ), $log->user_id ); + + // Format action for display + switch ( $log->action ) { + case 'add': + $action_display = '' . esc_html__( 'Added', 'wp-domain-mapping' ) . ''; + break; + case 'edit': + $action_display = '' . esc_html__( 'Updated', 'wp-domain-mapping' ) . ''; + break; + case 'delete': + $action_display = '' . esc_html__( 'Deleted', 'wp-domain-mapping' ) . ''; + break; + default: + $action_display = '' . esc_html( ucfirst( $log->action ) ) . ''; + } + + // Get site name + $site_name = get_blog_option( $log->blog_id, 'blogname', '' ); + $site_link = ! empty( $site_name ) + ? sprintf( + '%s', + esc_url( network_admin_url( 'site-info.php?id=' . $log->blog_id ) ), + esc_html( $site_name ) + ) + : sprintf( esc_html__( 'Site #%d', 'wp-domain-mapping' ), $log->blog_id ); + + // Format timestamp for display + $timestamp = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $log->timestamp ); + $time_diff = human_time_diff( strtotime( $log->timestamp ), current_time( 'timestamp' ) ); + ?> + + + + + + + + + +
+ + domain ); ?> + + + + + + + + +
+ + 1 ) : ?> +
+
+ add_query_arg( 'logs_paged', '%#%' ), + 'format' => '', + 'prev_text' => __( '«' ), + 'next_text' => __( '»' ), + 'total' => $total_pages, + 'current' => $paged, + 'mid_size' => 2, + 'end_size' => 1, + ) ); + ?> + + + +
+
+ + + diff --git a/admin/settings-page.php b/admin/settings-page.php new file mode 100644 index 0000000..229d0a0 --- /dev/null +++ b/admin/settings-page.php @@ -0,0 +1,522 @@ + +
+

+ + + + + + + + + +

+ +
+
+ + + +

+

+ + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + +

+ + + + +

+
+
+ +
+

+ +
+ +

+

+ ' . esc_html( $dm_cname ) . '' + ); + ?> +

+
+

+ + + + + + + + + + + + + + + +
CNAME@
+
+ + + +

+

+ ' . esc_html( $dm_ipaddress ) . '' + ); + ?> +

+
+

+ + + + + + + + + + $ip ) : + ?> + + + + + + + +
A@
+
+ + + +
+

+ +

+
+ + +

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + ' . esc_html( WP_CONTENT_DIR ) . '' + ); + ?> + +
+ + + + + + + + ' . esc_html( SUNRISE ) . '' + ); + ?> + + + define( 'SUNRISE', 'on' ); + +
+ + + + + + + + + + + +
+ + + + + + + + + + ' . esc_html( COOKIE_DOMAIN ) . '' + ); + ?> + +
+ base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS, + $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGINS, + $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGS, + ) as $table ) { + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) != $table ) { + $tables_exist = false; + break; + } + } + ?> + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+
+ + diff --git a/admin/templates.php b/admin/templates.php new file mode 100644 index 0000000..b2ea2b9 --- /dev/null +++ b/admin/templates.php @@ -0,0 +1,964 @@ + + + +

+ +

+ + + + + + +
+ + +

+ +

+
+ + + + + +
+ + +

+ +

+
+ + + + + +
+ + +

+ +

+
+ + + + +

+ + + + +

+ + + +
+

+ +
+ +

+

+ ' . esc_html($dm_cname) . '' + ); + ?> +

+
+

+ + + + + + + + + + + + + + + +
CNAME@
+
+ + + +

+

+ ' . esc_html($dm_ipaddress) . '' + ); + ?> +

+
+

+ + + + + + + + + + $ip) : + ?> + + + + + + + +
A@
+
+ + + +
+

+ +

+
+ + +

+ +
+
+ +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + ' . esc_html(WP_CONTENT_DIR) . '' + ); + ?> + +
+ + + + + + + + ' . esc_html(SUNRISE) . '' + ); + ?> + + + define( 'SUNRISE', 'on' ); + +
+ + + + + + + + + + + +
+ + + + + + + + + + ' . esc_html(COOKIE_DOMAIN) . '' + ); + ?> + +
+ base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS, + $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGINS, + $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGS, + ) as $table) { + if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) { + $tables_exist = false; + break; + } + } + ?> + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + get_domains_by_blog_id($wpdb->blogid); + + // Display updated messages + if (isset($_GET['updated'])) { + echo '

'; + switch ($_GET['updated']) { + case 'add': + _e('Domain added successfully.', 'wp-domain-mapping'); + break; + case 'exists': + _e('This domain is already mapped to a site.', 'wp-domain-mapping'); + break; + case 'primary': + _e('Primary domain updated.', 'wp-domain-mapping'); + break; + case 'del': + _e('Domain deleted successfully.', 'wp-domain-mapping'); + break; + } + echo '

'; + } + ?> +
+

+ +
+

+
+ + + + + + + + + + + + +
+ +

+ +

+
+
+ + +
+
+ +

+ +

+
+
+ + +
+

+ + + + + + + + + + + + + + + + + + +
+ + domain); ?> + + + + active == 1): ?> + + + + + + + + + active != 1): ?> + + + + +
+
+ + +
+

+ + +
+

+
+ +

+ + +

+

%s', 'wp-domain-mapping'), esc_html($cname)); ?>

+ + + +

+

%s', 'wp-domain-mapping'), esc_html($ipaddress)); ?>

+ + +

+ +
+
+ base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + $domains = $wpdb->get_results(" + SELECT d.*, b.domain as original_domain, b.path + FROM {$table} d + JOIN {$wpdb->blogs} b ON d.blog_id = b.blog_id + ORDER BY d.blog_id ASC, d.active DESC + "); + + // Get health check results + $health_results = get_site_option('dm_domain_health_results', array()); + + // Display page + ?> +
+

+ +

' . + __('Domain health check completed.', 'wp-domain-mapping') . + '

'; + } + + if (isset($_GET['settings-updated']) && $_GET['settings-updated']) { + echo '

' . + __('Settings saved.', 'wp-domain-mapping') . + '

'; + } + ?> + +
+

+ +

+

+ + + +
+

+ +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + domain); + $health_data = isset($health_results[$domain_key]) ? $health_results[$domain_key] : null; + $site_name = get_blog_option($domain->blog_id, 'blogname', __('Unknown', 'wp-domain-mapping')); + ?> + + + + + + + + + + + + + + + + + + + + + +
+ domain); ?> + active) : ?> + + + + + +
+ original_domain . $domain->path); ?> +
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+

+ +
+ + + + + + + + + + + + + + + + + + + + + + +

+ +

+
+
+ +

' . + sprintf( + _n( + '%d domain mapping imported successfully.', + '%d domain mappings imported successfully.', + $count, + 'wp-domain-mapping' + ), + $count + ) . + '

'; + } + + if (isset($_GET['export']) && $_GET['export'] == 'success') { + echo '

' . + __('Domain mappings exported successfully.', 'wp-domain-mapping') . + '

'; + } + ?> +
+

+ +
+

+

+ +
+ + + +
+ +
+ +
+ + +

+
+ +

+ +

+
+
+ +
+

+

+ +
+ + + +
+
+ +

+
+ +

+
+ +
+ +
+ +
+ +

+
+ +
+ +

+
+ +

+ +

+
+ + + + +
+ +
+

+

+ + + + + + + + + + + + + + + + + + + + + +
blog_iddomainactive
1example.com1
2example.org0
+ + +
+
+ + +
+

+ + +
+

+ +

+
+ + +
+

+
+ + + + + + + + + + + + +
+ +

+ +

+
+
+ + +
+
+ +

+ +

+
+
+ + +
+

+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+ + +
+

+ + +
+

+
+ +

+ + +

+

%s', 'wp-domain-mapping'), esc_html($cname)); ?>

+ + + +

+

%s', 'wp-domain-mapping'), esc_html($ipaddress)); ?>

+ + +

+ +
+
+ + diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..fb2fd4c --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,64 @@ +.card { + background: #fff; + border: 1px solid #ccd0d4; + border-radius: 4px; + max-width: unset; + margin-top: 20px; + padding: 20px; +} +.styles-sync-tabs { + display: flex; + flex-wrap: wrap; + gap: 5px; + border-bottom: 1px solid #c3c4c7; + margin-bottom: 20px; +} +.styles-tab { + padding: 8px 16px; + border: none; + background: none; + cursor: pointer; + font-size: 14px; + border-bottom: 2px solid transparent; +} +.styles-tab.active { + border-bottom: 2px solid #007cba; + font-weight: 600; + background: #f0f0f1; +} +.styles-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; } + +/* Responsive Styles */ +@media screen and (max-width: 782px) { + .form-table th { + width: 100%; + display: block; + } + .form-table td { + display: block; + padding: 5px 0 15px; + } + .wp-list-table th.column-primary ~ th, + .wp-list-table td.column-primary ~ td { + display: none; + } + .wp-list-table th.column-primary, + .wp-list-table td.column-primary { + display: table-cell; + } +} \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..8dab280 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,134 @@ +/** + * 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(); + }); + + // Domain form validation + $('#edit-domain-form').on('submit', function(e) { + var domain = $('#domain').val(); + var blogId = $('#blog_id').val(); + + if (!domain) { + e.preventDefault(); + alert(wpDomainMapping.messages.domainRequired); + $('#domain').focus(); + return false; + } + + if (!blogId) { + e.preventDefault(); + alert(wpDomainMapping.messages.siteRequired); + $('#blog_id').focus(); + return false; + } + }); + + // Check all domains + $('#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('

' + message + '

') + .show() + .delay(3000) + .fadeOut(); + } + + // Save domain + $('#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(); + + $.ajax({ + url: wpDomainMapping.ajaxUrl, + type: 'POST', + data: formData, + success: function(response) { + if (response.success) { + showNotice('#edit-domain-status', response.data, 'success'); + setTimeout(function() { location.reload(); }, 1000); + } else { + showNotice('#edit-domain-status', response.data || wpDomainMapping.messages.error, 'error'); + } + }, + error: function() { + showNotice('#edit-domain-status', wpDomainMapping.messages.error, 'error'); + } + }); + }); + + // 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', wpDomainMapping.messages.noSelection, 'error'); + return; + } + + var action = $('#bulk-action-selector-top').val(); + if (action === '-1') 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'); + } + }); + } + }); + + // Copy to clipboard functionality + $('.copy-to-clipboard').on('click', function() { + var text = $(this).data('text'); + var tempInput = $(''); + $('body').append(tempInput); + tempInput.val(text).select(); + document.execCommand('copy'); + tempInput.remove(); + + var $btn = $(this); + var originalText = $btn.text(); + $btn.text('Copied!'); + setTimeout(function() { $btn.text(originalText); }, 2000); + }); +}); \ No newline at end of file diff --git a/includes/admin-ui.php b/includes/admin-ui.php new file mode 100644 index 0000000..7d6b93f --- /dev/null +++ b/includes/admin-ui.php @@ -0,0 +1,389 @@ +blog_id = ''; + $row->domain = ''; + $row->active = 1; + } + ?> +
+ + + + + + + + + + + + + + + + + + + +
+ + +

+ View all sites to find the ID.', 'wp-domain-mapping' ), + array( 'a' => array( 'href' => array(), 'target' => array() ) ) + ), + esc_url( $site_list_url ) + ); + ?> +

+ +
+ +

+ +

+
+ active, 1 ); ?> /> + + + +
+

+
+

+ + + + + + + +

+
+

' . esc_html__( 'No domains found.', 'wp-domain-mapping' ) . '

'; + return; + } + + $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' ) + ); + ?> +
+
+ + + +
+
+ + + + + + + + + + + + + + blog_id, 'blogname', esc_html__( 'Unknown', 'wp-domain-mapping' ) ); + ?> + + + + + + + + + + +
+ +
+ + + + blog_id ); ?> + + + + + + domain ); ?> + + + + active == 1 ) : ?> + + + + + + + +
+ + + + + + active != 1 ) : ?> + + + + + + +
+
+ + +

' . + esc_html__( 'Warning! Primary domains are currently disabled in network settings.', 'wp-domain-mapping' ) . + '

'; + } +} + +/** + * 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; +} + +/** + * 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 punycode format.', 'wp-domain-mapping' ), + array( 'a' => array( 'href' => array(), 'target' => array() ) ) + ), + 'https://www.punycoder.com/' + ); +} + +/** + * Render domain logs table + */ +function dm_domain_logs() { + global $wpdb; + + $table_logs = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_LOGS; + + // Make sure the table exists + if ($wpdb->get_var("SHOW TABLES LIKE '$table_logs'") != $table_logs) { + echo '

' . + esc_html__('Domain mapping logs table is missing. Please deactivate and reactivate the plugin.', 'wp-domain-mapping') . + '

'; + return; + } + + $logs = $wpdb->get_results( "SELECT * FROM {$table_logs} ORDER BY timestamp DESC LIMIT 50" ); + + if ( ! $logs ) { + echo '

' . esc_html__( 'No logs available.', 'wp-domain-mapping' ) . '

'; + return; + } + ?> + + + + + + + + + + + + user_id ); + $username = $user_data ? $user_data->user_login : sprintf( __( 'User #%d', 'wp-domain-mapping' ), $log->user_id ); + + // Format action for display + switch ( $log->action ) { + case 'add': + $action_display = '' . __( 'Added', 'wp-domain-mapping' ) . ''; + break; + case 'edit': + $action_display = '' . __( 'Updated', 'wp-domain-mapping' ) . ''; + break; + case 'delete': + $action_display = '' . __( 'Deleted', 'wp-domain-mapping' ) . ''; + break; + default: + $action_display = '' . esc_html( $log->action ) . ''; + } + + // Format timestamp for display + $timestamp = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $log->timestamp ); + ?> + + + + + + + + + +
domain ); ?> + + blog_id ); ?> + + + +
+ + + db = WP_Domain_Mapping_DB::get_instance(); + + // 添加菜单页面 + add_action('network_admin_menu', array($this, 'add_menu_page'), 30); + + // 添加AJAX处理程序 + add_action('wp_ajax_dm_check_domain_health', array($this, 'ajax_check_domain_health')); + + // 添加计划任务钩子 + add_action('dm_domain_health_check', array($this, 'scheduled_health_check')); + + // 初始化计划任务 + $this->initialize_cron(); + + // 处理手动健康检查 + add_action('admin_init', array($this, 'handle_manual_check')); + + // 处理设置保存 + add_action('admin_init', array($this, 'handle_settings_save')); + } + + /** + * 初始化定时健康检查 + */ + private function initialize_cron() { + // 注册激活时的钩子 + register_activation_hook(WP_DOMAIN_MAPPING_BASENAME, array($this, 'schedule_health_check')); + + // 注册停用时的钩子 + register_deactivation_hook(WP_DOMAIN_MAPPING_BASENAME, array($this, 'unschedule_health_check')); + } + + /** + * 添加健康监控菜单 + */ + public function add_menu_page() { + add_submenu_page( + 'settings.php', + __('Domain Health', 'wp-domain-mapping'), + __('Domain Health', 'wp-domain-mapping'), + 'manage_network', + 'domain-mapping-health', + array($this, 'render_page') + ); + } + + /** + * 计划健康检查任务 + */ + public function schedule_health_check() { + if (!wp_next_scheduled('dm_domain_health_check')) { + wp_schedule_event(time(), 'daily', 'dm_domain_health_check'); + } + } + + /** + * 取消健康检查任务 + */ + public function unschedule_health_check() { + $timestamp = wp_next_scheduled('dm_domain_health_check'); + if ($timestamp) { + wp_unschedule_event($timestamp, 'dm_domain_health_check'); + } + } + + /** + * 处理手动健康检查 + */ + public function handle_manual_check() { + if (isset($_POST['dm_manual_health_check']) && $_POST['dm_manual_health_check']) { + // 验证nonce + if (!isset($_POST['dm_manual_health_check_nonce']) || !wp_verify_nonce($_POST['dm_manual_health_check_nonce'], 'dm_manual_health_check')) { + wp_die(__('Security check failed.', 'wp-domain-mapping')); + } + + // 检查权限 + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to perform this action.', 'wp-domain-mapping')); + } + + // 执行健康检查 + $this->run_health_check_for_all_domains(); + + // 重定向回健康页面 + wp_redirect(add_query_arg(array('page' => 'domain-mapping-health', 'checked' => 1), network_admin_url('settings.php'))); + exit; + } + } + + /** + * 处理设置保存 + */ + public function handle_settings_save() { + if (isset($_POST['dm_health_settings']) && $_POST['dm_health_settings']) { + // 验证nonce + if (!isset($_POST['dm_health_settings_nonce']) || !wp_verify_nonce($_POST['dm_health_settings_nonce'], 'dm_health_settings')) { + wp_die(__('Security check failed.', 'wp-domain-mapping')); + } + + // 检查权限 + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to perform this action.', 'wp-domain-mapping')); + } + + // 保存设置 + $health_check_enabled = isset($_POST['health_check_enabled']) ? (bool) $_POST['health_check_enabled'] : false; + $health_notifications_enabled = isset($_POST['health_notifications_enabled']) ? (bool) $_POST['health_notifications_enabled'] : false; + $notification_email = isset($_POST['notification_email']) ? sanitize_email($_POST['notification_email']) : ''; + $ssl_expiry_threshold = isset($_POST['ssl_expiry_threshold']) ? intval($_POST['ssl_expiry_threshold']) : 14; + + update_site_option('dm_health_check_enabled', $health_check_enabled); + update_site_option('dm_health_notifications_enabled', $health_notifications_enabled); + update_site_option('dm_notification_email', $notification_email); + update_site_option('dm_ssl_expiry_threshold', $ssl_expiry_threshold); + + // 如果启用了自动检查,确保计划任务已设置 + if ($health_check_enabled) { + $this->schedule_health_check(); + } else { + $this->unschedule_health_check(); + } + + // 重定向回健康页面 + wp_redirect(add_query_arg(array('page' => 'domain-mapping-health', 'settings-updated' => 1), network_admin_url('settings.php'))); + exit; + } + } + + /** + * 渲染健康监控页面 + */ + public function render_page() { + // 检查权限 + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to access this page.', 'wp-domain-mapping')); + } + + // 获取所有域名 + global $wpdb; + $table = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + $domains = $wpdb->get_results(" + SELECT d.*, b.domain as original_domain, b.path + FROM {$table} d + JOIN {$wpdb->blogs} b ON d.blog_id = b.blog_id + ORDER BY d.blog_id ASC, d.active DESC + "); + + // 获取健康检查结果(如果有) + $health_results = get_site_option('dm_domain_health_results', array()); + + // 渲染页面 + ?> +
+

+ +

' . + __('Domain health check completed.', 'wp-domain-mapping') . + '

'; + } + + if (isset($_GET['settings-updated']) && $_GET['settings-updated']) { + echo '

' . + __('Settings saved.', 'wp-domain-mapping') . + '

'; + } + ?> + +
+

+ +

+

+ + + +
+

+ +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + domain); + $health_data = isset($health_results[$domain_key]) ? $health_results[$domain_key] : null; + $site_name = get_blog_option($domain->blog_id, 'blogname', __('Unknown', 'wp-domain-mapping')); + ?> + + + + + + + + + + + + + + + + + + + + + +
+ domain); ?> + active) : ?> + + + + + +
+ original_domain . $domain->path); ?> +
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+

+ +
+ + + + + + + + + + + + + + + + + + + + + + +

+ +

+
+
+ + + + check_domain_health($domain); + + // 保存结果 + $this->save_health_check_result($domain, $result); + + // 返回结果 + wp_send_json_success($result); + } + + /** + * 计划任务:执行所有域名健康检查 + */ + public function scheduled_health_check() { + // 检查是否启用了自动健康检查 + if (!get_site_option('dm_health_check_enabled', true)) { + return; + } + + $this->run_health_check_for_all_domains(); + } + + /** + * 对所有域名执行健康检查 + */ + private function run_health_check_for_all_domains() { + global $wpdb; + $table = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + + // 获取所有域名 + $domains = $wpdb->get_col("SELECT domain FROM {$table}"); + + // 初始化通知信息 + $issues = array(); + + // 检查每个域名 + foreach ($domains as $domain) { + $result = $this->check_domain_health($domain); + $this->save_health_check_result($domain, $result); + + // 检查是否有问题 + if ($this->has_health_issues($result)) { + $issues[$domain] = $result; + } + } + + // 如果启用了通知并且有问题,发送通知 + if (!empty($issues) && get_site_option('dm_health_notifications_enabled', true)) { + $this->send_health_notification($issues); + } + + return true; + } + + /** + * 检查域名健康状态 + * + * @param string $domain 要检查的域名 + * @return array 健康检查结果 + */ + private function check_domain_health($domain) { + $result = array( + 'domain' => $domain, + 'last_check' => current_time('mysql'), + 'dns_status' => 'error', + 'dns_message' => __('DNS check not performed', 'wp-domain-mapping'), + 'resolved_ip' => '', + 'ssl_valid' => false, + 'ssl_expiry' => '', + 'accessible' => false, + 'response_code' => 0 + ); + + // 获取服务器IP或CNAME + $server_ip = get_site_option('dm_ipaddress', ''); + $server_cname = get_site_option('dm_cname', ''); + + // 检查DNS设置 + $domain_ip = gethostbyname($domain); + $result['resolved_ip'] = $domain_ip; + + if ($domain_ip && $domain_ip !== $domain) { + if ($server_ip && strpos($server_ip, $domain_ip) !== false) { + $result['dns_status'] = 'success'; + $result['dns_message'] = __('Domain A record is correctly pointing to server IP.', 'wp-domain-mapping'); + } elseif ($server_cname && function_exists('dns_get_record')) { + $dns_records = @dns_get_record($domain, DNS_CNAME); + $has_valid_cname = false; + + if ($dns_records) { + foreach ($dns_records as $record) { + if (isset($record['target']) && + ( + $record['target'] === $server_cname || + strpos($record['target'], $server_cname) !== false + )) { + $has_valid_cname = true; + break; + } + } + } + + if ($has_valid_cname) { + $result['dns_status'] = 'success'; + $result['dns_message'] = __('Domain CNAME record is correctly configured.', 'wp-domain-mapping'); + } else { + $result['dns_message'] = __('Domain is not pointing to the correct server.', 'wp-domain-mapping'); + } + } else { + $result['dns_message'] = __('Cannot verify DNS configuration.', 'wp-domain-mapping'); + } + } else { + $result['dns_message'] = __('Domain does not resolve to an IP address.', 'wp-domain-mapping'); + } + + // 检查网站可访问性和SSL + $response = $this->test_domain_connection($domain); + + if ($response) { + $result['accessible'] = $response['accessible']; + $result['response_code'] = $response['response_code']; + $result['ssl_valid'] = $response['ssl_valid']; + $result['ssl_expiry'] = $response['ssl_expiry']; + } + + return $result; + } + + /** + * 测试域名连接 + * + * @param string $domain 域名 + * @return array|false 连接测试结果 + */ + private function test_domain_connection($domain) { + if (!function_exists('curl_init')) { + return false; + } + + $result = array( + 'accessible' => false, + 'response_code' => 0, + 'ssl_valid' => false, + 'ssl_expiry' => '' + ); + + // 测试HTTPS连接 + $ch = curl_init('https://' . $domain); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + $response = curl_exec($ch); + $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $result['response_code'] = $response_code; + + if ($response !== false && $response_code > 0) { + $result['accessible'] = ($response_code >= 200 && $response_code < 400); + $result['ssl_valid'] = ($response !== false); + + // 获取SSL证书信息 + $ssl_info = curl_getinfo($ch, CURLINFO_CERTINFO); + if (!empty($ssl_info) && isset($ssl_info[0]['Expire date'])) { + $result['ssl_expiry'] = $ssl_info[0]['Expire date']; + } + } + + curl_close($ch); + + // 如果HTTPS失败,尝试HTTP + if (!$result['accessible']) { + $ch = curl_init('http://' . $domain); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + + $response = curl_exec($ch); + $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if ($response !== false && $response_code > 0) { + $result['accessible'] = ($response_code >= 200 && $response_code < 400); + $result['response_code'] = $response_code; + } + + curl_close($ch); + } + + return $result; + } + + /** + * 保存健康检查结果 + * + * @param string $domain 域名 + * @param array $result 健康检查结果 + */ + private function save_health_check_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); + } + + /** + * 检查结果是否有健康问题 + * + * @param array $result 健康检查结果 + * @return bool 是否有问题 + */ + private function has_health_issues($result) { + // 检查DNS问题 + if ($result['dns_status'] !== 'success') { + return true; + } + + // 检查SSL问题 + if (!$result['ssl_valid']) { + return true; + } + + // 检查SSL即将到期 + if (!empty($result['ssl_expiry'])) { + $expiry_date = strtotime($result['ssl_expiry']); + $threshold = get_site_option('dm_ssl_expiry_threshold', 14); + $threshold_date = strtotime('+' . $threshold . ' days'); + + if ($expiry_date <= $threshold_date) { + return true; + } + } + + // 检查可访问性问题 + if (!$result['accessible']) { + return true; + } + + return false; + } + + /** + * 发送健康问题通知 + * + * @param array $issues 有问题的域名及其健康检查结果 + */ + private function send_health_notification($issues) { + $notification_email = get_site_option('dm_notification_email', get_option('admin_email')); + + if (empty($notification_email)) { + return; + } + + $site_name = get_bloginfo('name'); + $subject = sprintf(__('[%s] Domain Mapping Health Alert', 'wp-domain-mapping'), $site_name); + + // 构建邮件内容 + $message = sprintf(__('Domain health issues were detected on %s.', 'wp-domain-mapping'), $site_name) . "\n\n"; + $message .= __('The following domains have issues:', 'wp-domain-mapping') . "\n\n"; + + foreach ($issues as $domain => $result) { + $message .= sprintf(__('Domain: %s', 'wp-domain-mapping'), $domain) . "\n"; + + // 添加DNS状态 + if ($result['dns_status'] !== 'success') { + $message .= " - " . sprintf(__('DNS Issue: %s', 'wp-domain-mapping'), $result['dns_message']) . "\n"; + } + + // 添加SSL状态 + if (!$result['ssl_valid']) { + $message .= " - " . __('SSL Certificate is invalid or missing.', 'wp-domain-mapping') . "\n"; + } elseif (!empty($result['ssl_expiry'])) { + $expiry_date = strtotime($result['ssl_expiry']); + $threshold = get_site_option('dm_ssl_expiry_threshold', 14); + $threshold_date = strtotime('+' . $threshold . ' days'); + + if ($expiry_date <= $threshold_date) { + $message .= " - " . sprintf( + __('SSL Certificate expires on %s (within %d days).', 'wp-domain-mapping'), + date('Y-m-d', $expiry_date), + $threshold + ) . "\n"; + } + } + + // 添加可访问性状态 + if (!$result['accessible']) { + $message .= " - " . __('Site is not accessible.', 'wp-domain-mapping') . "\n"; + if ($result['response_code'] > 0) { + $message .= " " . sprintf(__('HTTP Response Code: %d', 'wp-domain-mapping'), $result['response_code']) . "\n"; + } + } + + $message .= "\n"; + } + + // 添加解决方案链接 + $message .= sprintf( + __('To view and manage these issues, please visit: %s', 'wp-domain-mapping'), + network_admin_url('settings.php?page=domain-mapping-health') + ) . "\n"; + + // 发送邮件 + wp_mail($notification_email, $subject, $message); + } +} diff --git a/includes/class-importer.php b/includes/class-importer.php new file mode 100644 index 0000000..16feba3 --- /dev/null +++ b/includes/class-importer.php @@ -0,0 +1,592 @@ +db = WP_Domain_Mapping_DB::get_instance(); + + // 添加菜单页面 + add_action('network_admin_menu', array($this, 'add_menu_page'), 20); + + // 处理表单提交 + add_action('admin_init', array($this, 'handle_form_submission')); + + // AJAX处理程序 + add_action('wp_ajax_dm_import_csv', array($this, 'ajax_import_csv')); + } + + /** + * 添加导入/导出菜单 + */ + public function add_menu_page() { + add_submenu_page( + 'settings.php', + __('Import/Export Domains', 'wp-domain-mapping'), + __('Import/Export Domains', 'wp-domain-mapping'), + 'manage_network', + 'domain-mapping-import-export', + array($this, 'render_page') + ); + } + + /** + * 渲染导入/导出页面 + */ + public function render_page() { + // 检查权限 + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to access this page.', 'wp-domain-mapping')); + } + + // 输出成功消息(如果有) + if (isset($_GET['imported']) && $_GET['imported']) { + $count = intval($_GET['imported']); + echo '

' . + sprintf( + _n( + '%d domain mapping imported successfully.', + '%d domain mappings imported successfully.', + $count, + 'wp-domain-mapping' + ), + $count + ) . + '

'; + } + + if (isset($_GET['export']) && $_GET['export'] == 'success') { + echo '

' . + __('Domain mappings exported successfully.', 'wp-domain-mapping') . + '

'; + } + + // 显示页面 + ?> +
+

+ +
+

+

+ +
+ + + +
+ +
+ +
+ + +

+
+ +

+ +

+
+
+ +
+

+

+ +
+ + + +
+
+ +

+
+ +

+
+ +
+ +
+ +
+ +

+
+ +
+ +

+
+ +

+ +

+
+ + + + +
+ +
+

+

+ + + + + + + + + + + + + + + + + + + + + +
blog_iddomainactive
1example.com1
2example.org0
+ + +
+
+ + + handle_export(); + } + } + + /** + * 处理CSV导出 + */ + private function handle_export() { + // 检查权限 + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to export data.', 'wp-domain-mapping')); + } + + // 验证nonce + if (!isset($_POST['domain_mapping_export_nonce']) || !wp_verify_nonce($_POST['domain_mapping_export_nonce'], 'domain_mapping_export')) { + wp_die(__('Invalid security token. Please try again.', 'wp-domain-mapping')); + } + + // 获取选项 + $include_header = isset($_POST['include_header']) ? (bool) $_POST['include_header'] : false; + $blog_id_filter = isset($_POST['blog_id_filter']) && !empty($_POST['blog_id_filter']) ? intval($_POST['blog_id_filter']) : 0; + + // 获取域名映射数据 + global $wpdb; + $table = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + $sql = "SELECT blog_id, domain, active FROM {$table}"; + + if ($blog_id_filter > 0) { + $sql .= $wpdb->prepare(" WHERE blog_id = %d", $blog_id_filter); + } + + $domains = $wpdb->get_results($sql, ARRAY_A); + + if (empty($domains)) { + // 没有数据 + wp_redirect(add_query_arg(array('page' => 'domain-mapping-import-export', 'export' => 'empty'), network_admin_url('settings.php'))); + exit; + } + + // 设置CSV输出 + $filename = 'domain-mappings-' . date('Y-m-d') . '.csv'; + + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=' . $filename); + + $output = fopen('php://output', 'w'); + + // 添加标题行 + if ($include_header) { + fputcsv($output, array('blog_id', 'domain', 'active')); + } + + // 添加数据行 + foreach ($domains as $domain) { + fputcsv($output, $domain); + } + + fclose($output); + exit; + } + + /** + * AJAX处理CSV导入 + */ + public function ajax_import_csv() { + // 检查权限 + if (!current_user_can('manage_network')) { + wp_send_json_error(__('You do not have sufficient permissions to import data.', 'wp-domain-mapping')); + } + + // 验证nonce + if (!isset($_POST['domain_mapping_import_nonce']) || !wp_verify_nonce($_POST['domain_mapping_import_nonce'], 'domain_mapping_import')) { + wp_send_json_error(__('Invalid security token. Please try again.', 'wp-domain-mapping')); + } + + // 检查文件 + if (!isset($_FILES['csv_file']) || $_FILES['csv_file']['error'] != UPLOAD_ERR_OK) { + wp_send_json_error(__('No file uploaded or upload error.', 'wp-domain-mapping')); + } + + // 获取选项 + $has_header = isset($_POST['has_header']) ? (bool) $_POST['has_header'] : false; + $update_existing = isset($_POST['update_existing']) ? (bool) $_POST['update_existing'] : false; + $validate_sites = isset($_POST['validate_sites']) ? (bool) $_POST['validate_sites'] : true; + + // 打开文件 + $file = fopen($_FILES['csv_file']['tmp_name'], 'r'); + if (!$file) { + wp_send_json_error(__('Could not open the uploaded file.', 'wp-domain-mapping')); + } + + // 初始化计数器和日志 + $imported = 0; + $skipped = 0; + $errors = 0; + $log = array(); + + // 跳过标题行 + if ($has_header) { + fgetcsv($file); + } + + // 处理每一行 + $core = WP_Domain_Mapping_Core::get_instance(); + $row_num = $has_header ? 2 : 1; // 考虑标题行 + + while (($data = fgetcsv($file)) !== false) { + // 检查数据格式 + if (count($data) < 3) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Invalid format. Expected at least 3 columns.', 'wp-domain-mapping'), $row_num) + ); + $errors++; + $row_num++; + continue; + } + + // 解析数据 + $blog_id = intval($data[0]); + $domain = $core->clean_domain(trim($data[1])); + $active = intval($data[2]); + + // 验证blog_id + if ($blog_id <= 0) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Invalid blog ID: %d', 'wp-domain-mapping'), $row_num, $blog_id) + ); + $errors++; + $row_num++; + continue; + } + + // 验证站点是否存在 + if ($validate_sites && !get_blog_details($blog_id)) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Site ID %d does not exist.', 'wp-domain-mapping'), $row_num, $blog_id) + ); + $errors++; + $row_num++; + continue; + } + + // 验证域名格式 + if (!$core->validate_domain($domain)) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Invalid domain format: %s', 'wp-domain-mapping'), $row_num, $domain) + ); + $errors++; + $row_num++; + continue; + } + + // 检查域名是否已经存在于其他站点 + global $wpdb; + $table = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + $existing = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $table WHERE domain = %s", + $domain + )); + + if ($existing) { + if ($existing->blog_id != $blog_id) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Domain %s is already mapped to blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $existing->blog_id) + ); + $errors++; + } elseif (!$update_existing) { + $log[] = array( + 'status' => 'warning', + 'message' => sprintf(__('Row %d: Domain %s already exists for blog ID %d. Skipped.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $skipped++; + } else { + // 更新现有域名 + $result = $wpdb->update( + $table, + array('active' => $active), + array('domain' => $domain), + array('%d'), + array('%s') + ); + + if ($result !== false) { + $log[] = array( + 'status' => 'success', + 'message' => sprintf(__('Row %d: Updated domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $imported++; + } else { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Failed to update domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $errors++; + } + } + } else { + // 添加新域名 + $result = $wpdb->insert( + $table, + array( + 'blog_id' => $blog_id, + 'domain' => $domain, + 'active' => $active + ), + array('%d', '%s', '%d') + ); + + if ($result) { + // 记录操作 + $this->db->log_action('import', $domain, $blog_id); + + // 清除缓存 + $this->db->invalidate_domain_cache($blog_id); + + $log[] = array( + 'status' => 'success', + 'message' => sprintf(__('Row %d: Added domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $imported++; + } else { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Failed to add domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $errors++; + } + } + + $row_num++; + } + + fclose($file); + + // 构建响应 + $message = sprintf( + __('Import completed: %d imported, %d skipped, %d errors.', 'wp-domain-mapping'), + $imported, $skipped, $errors + ); + + wp_send_json_success(array( + 'message' => $message, + 'imported' => $imported, + 'skipped' => $skipped, + 'errors' => $errors, + 'details' => $log + )); + } +} diff --git a/includes/class-site-id.php b/includes/class-site-id.php new file mode 100644 index 0000000..4dd9ca8 --- /dev/null +++ b/includes/class-site-id.php @@ -0,0 +1,132 @@ +id) { + ?> + + add_menu(array( + 'id' => 'wp-site-ID', + 'title' => sprintf(__('Site ID: %d', 'wp-domain-mapping'), $blog_id), + 'href' => is_super_admin() ? esc_url(network_admin_url('site-info.php?id=' . $blog_id)) : '#', + 'meta' => array( + 'title' => is_super_admin() ? __('Edit this site', 'wp-domain-mapping') : __('Current site ID', 'wp-domain-mapping'), + 'class' => 'dm-site-id-menu' + ), + )); + } + + /** + * 向站点列表添加ID列 + * + * @param array $columns 当前列 + * @return array 修改后的列 + */ + public function manage_sites_columns($columns) { + // 在第一列后添加站点ID列 + $columns = array_slice($columns, 0, 1, true) + + array('dm_site_id' => __('Site ID', 'wp-domain-mapping')) + + array_slice($columns, 1, count($columns) - 1, true); + + return $columns; + } + + /** + * 显示站点ID列的内容 + * + * @param string $column 列名 + * @param int $blog_id 站点ID + */ + public function show_site_id($column, $blog_id) { + if ('dm_site_id' == $column) { + echo '' . esc_html($blog_id) . ''; + } + } +} diff --git a/includes/class-tools.php b/includes/class-tools.php new file mode 100644 index 0000000..31efabe --- /dev/null +++ b/includes/class-tools.php @@ -0,0 +1,709 @@ +db = WP_Domain_Mapping_DB::get_instance(); + $this->core = WP_Domain_Mapping_Core::get_instance(); + + // Setup hooks + $this->setup_hooks(); + } + + private function setup_hooks() { + // Add menu pages for tools + add_action('network_admin_menu', array($this, 'add_menu_pages'), 20); + + // Add Site ID to toolbar + add_action('admin_bar_menu', array($this, 'add_site_id_to_toolbar'), 100); + + // Add Site ID column to sites list + add_filter('manage_sites-network_columns', array($this, 'add_site_id_column'), 20); + add_action('manage_sites_custom_column', array($this, 'display_site_id_column'), 10, 2); + add_action('admin_print_styles', array($this, 'site_id_column_style')); + + // AJAX handlers + add_action('wp_ajax_dm_check_domain_health', array($this, 'ajax_check_domain_health')); + add_action('wp_ajax_dm_import_csv', array($this, 'ajax_import_csv')); + + // Scheduled tasks + add_action('dm_domain_health_check', array($this, 'scheduled_health_check')); + + // Admin init actions + 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')); + } + + public function add_menu_pages() { + add_submenu_page( + 'settings.php', + __('Domain Health', 'wp-domain-mapping'), + __('Domain Health', 'wp-domain-mapping'), + 'manage_network', + 'domain-mapping-health', + 'dm_render_health_page' + ); + + add_submenu_page( + 'settings.php', + __('Import/Export Domains', 'wp-domain-mapping'), + __('Import/Export Domains', 'wp-domain-mapping'), + 'manage_network', + 'domain-mapping-import-export', + 'dm_render_import_export_page' + ); + } + + // Site ID Display Functions + public function add_site_id_to_toolbar($admin_bar) { + // Only show to admins or network admins + if (!current_user_can('manage_options') && !is_super_admin()) { + return; + } + + $blog_id = get_current_blog_id(); + + $admin_bar->add_menu(array( + 'id' => 'wp-site-ID', + 'title' => sprintf(__('Site ID: %d', 'wp-domain-mapping'), $blog_id), + 'href' => is_super_admin() ? esc_url(network_admin_url('site-info.php?id=' . $blog_id)) : '#', + 'meta' => array( + 'title' => is_super_admin() ? __('Edit this site', 'wp-domain-mapping') : __('Current site ID', 'wp-domain-mapping'), + 'class' => 'dm-site-id-menu' + ), + )); + } + + public function add_site_id_column($columns) { + // Add site ID column after first column + $columns = array_slice($columns, 0, 1, true) + + array('dm_site_id' => __('Site ID', 'wp-domain-mapping')) + + array_slice($columns, 1, count($columns) - 1, true); + + return $columns; + } + + public function display_site_id_column($column, $blog_id) { + if ('dm_site_id' == $column) { + echo '' . esc_html($blog_id) . ''; + } + } + + public function site_id_column_style() { + if ('sites-network' == get_current_screen()->id) { + ?> + + run_health_check_for_all_domains(); + + // Redirect back to health page + wp_redirect(add_query_arg(array('page' => 'domain-mapping-health', 'checked' => 1), network_admin_url('settings.php'))); + exit; + } + } + + public function handle_health_settings_save() { + if (isset($_POST['dm_health_settings']) && $_POST['dm_health_settings']) { + // Verify nonce + if (!isset($_POST['dm_health_settings_nonce']) || !wp_verify_nonce($_POST['dm_health_settings_nonce'], 'dm_health_settings')) { + wp_die(__('Security check failed.', 'wp-domain-mapping')); + } + + // Check permissions + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to perform this action.', 'wp-domain-mapping')); + } + + // Save settings + $health_check_enabled = isset($_POST['health_check_enabled']) ? (bool) $_POST['health_check_enabled'] : false; + $health_notifications_enabled = isset($_POST['health_notifications_enabled']) ? (bool) $_POST['health_notifications_enabled'] : false; + $notification_email = isset($_POST['notification_email']) ? sanitize_email($_POST['notification_email']) : ''; + $ssl_expiry_threshold = isset($_POST['ssl_expiry_threshold']) ? intval($_POST['ssl_expiry_threshold']) : 14; + + update_site_option('dm_health_check_enabled', $health_check_enabled); + update_site_option('dm_health_notifications_enabled', $health_notifications_enabled); + update_site_option('dm_notification_email', $notification_email); + update_site_option('dm_ssl_expiry_threshold', $ssl_expiry_threshold); + + // If auto check is enabled, ensure cron is set + if ($health_check_enabled) { + $this->schedule_health_check(); + } else { + $this->unschedule_health_check(); + } + + // Redirect back to health page + wp_redirect(add_query_arg(array('page' => 'domain-mapping-health', 'settings-updated' => 1), network_admin_url('settings.php'))); + exit; + } + } + + public function ajax_check_domain_health() { + // Check permissions + if (!current_user_can('manage_network')) { + wp_send_json_error(__('You do not have sufficient permissions to perform this action.', 'wp-domain-mapping')); + } + + // Verify nonce + if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'dm_check_domain_health')) { + wp_send_json_error(__('Security check failed.', 'wp-domain-mapping')); + } + + // Get domain + $domain = isset($_POST['domain']) ? sanitize_text_field($_POST['domain']) : ''; + if (empty($domain)) { + wp_send_json_error(__('No domain specified.', 'wp-domain-mapping')); + } + + // Run health check + $result = $this->check_domain_health($domain); + + // Save result + $this->db->save_health_result($domain, $result); + + // Return result + wp_send_json_success($result); + } + + public function scheduled_health_check() { + // Check if health checks are enabled + if (!get_site_option('dm_health_check_enabled', true)) { + return; + } + + $this->run_health_check_for_all_domains(); + } + + private function run_health_check_for_all_domains() { + global $wpdb; + $table = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + + // Get all domains + $domains = $wpdb->get_col("SELECT domain FROM {$table}"); + + // Initialize issues array + $issues = array(); + + // Check each domain + foreach ($domains as $domain) { + $result = $this->check_domain_health($domain); + $this->db->save_health_result($domain, $result); + + // Check for issues + if ($this->has_health_issues($result)) { + $issues[$domain] = $result; + } + } + + // Send notifications if enabled and issues exist + if (!empty($issues) && get_site_option('dm_health_notifications_enabled', true)) { + $this->send_health_notification($issues); + } + + return true; + } + + private function check_domain_health($domain) { + $result = array( + 'domain' => $domain, + 'last_check' => current_time('mysql'), + 'dns_status' => 'error', + 'dns_message' => __('DNS check not performed', 'wp-domain-mapping'), + 'resolved_ip' => '', + 'ssl_valid' => false, + 'ssl_expiry' => '', + 'accessible' => false, + 'response_code' => 0 + ); + + // Get server IP or CNAME + $server_ip = get_site_option('dm_ipaddress', ''); + $server_cname = get_site_option('dm_cname', ''); + + // Check DNS setting + $domain_ip = gethostbyname($domain); + $result['resolved_ip'] = $domain_ip; + + if ($domain_ip && $domain_ip !== $domain) { + if ($server_ip && strpos($server_ip, $domain_ip) !== false) { + $result['dns_status'] = 'success'; + $result['dns_message'] = __('Domain A record is correctly pointing to server IP.', 'wp-domain-mapping'); + } elseif ($server_cname && function_exists('dns_get_record')) { + $dns_records = @dns_get_record($domain, DNS_CNAME); + $has_valid_cname = false; + + if ($dns_records) { + foreach ($dns_records as $record) { + if (isset($record['target']) && + ( + $record['target'] === $server_cname || + strpos($record['target'], $server_cname) !== false + )) { + $has_valid_cname = true; + break; + } + } + } + + if ($has_valid_cname) { + $result['dns_status'] = 'success'; + $result['dns_message'] = __('Domain CNAME record is correctly configured.', 'wp-domain-mapping'); + } else { + $result['dns_message'] = __('Domain is not pointing to the correct server.', 'wp-domain-mapping'); + } + } else { + $result['dns_message'] = __('Cannot verify DNS configuration.', 'wp-domain-mapping'); + } + } else { + $result['dns_message'] = __('Domain does not resolve to an IP address.', 'wp-domain-mapping'); + } + + // Check site accessibility and SSL + $response = $this->test_domain_connection($domain); + + if ($response) { + $result['accessible'] = $response['accessible']; + $result['response_code'] = $response['response_code']; + $result['ssl_valid'] = $response['ssl_valid']; + $result['ssl_expiry'] = $response['ssl_expiry']; + } + + return $result; + } + + private function test_domain_connection($domain) { + if (!function_exists('curl_init')) { + return false; + } + + $result = array( + 'accessible' => false, + 'response_code' => 0, + 'ssl_valid' => false, + 'ssl_expiry' => '' + ); + + // Test HTTPS connection + $ch = curl_init('https://' . $domain); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_CERTINFO, true); + + $response = curl_exec($ch); + $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $result['response_code'] = $response_code; + + if ($response !== false && $response_code > 0) { + $result['accessible'] = ($response_code >= 200 && $response_code < 400); + $result['ssl_valid'] = ($response !== false); + + // Get SSL certificate info + $cert_info = curl_getinfo($ch, CURLINFO_CERTINFO); + if (!empty($cert_info) && isset($cert_info[0]['Expire date'])) { + $result['ssl_expiry'] = $cert_info[0]['Expire date']; + } + } + + curl_close($ch); + + // If HTTPS failed, try HTTP + if (!$result['accessible']) { + $ch = curl_init('http://' . $domain); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + + $response = curl_exec($ch); + $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if ($response !== false && $response_code > 0) { + $result['accessible'] = ($response_code >= 200 && $response_code < 400); + $result['response_code'] = $response_code; + } + + curl_close($ch); + } + + return $result; + } + + private function has_health_issues($result) { + // Check DNS issues + if ($result['dns_status'] !== 'success') { + return true; + } + + // Check SSL issues + if (!$result['ssl_valid']) { + return true; + } + + // Check SSL expiry + if (!empty($result['ssl_expiry'])) { + $expiry_date = strtotime($result['ssl_expiry']); + $threshold = get_site_option('dm_ssl_expiry_threshold', 14); + $threshold_date = strtotime('+' . $threshold . ' days'); + + if ($expiry_date <= $threshold_date) { + return true; + } + } + + // Check accessibility issues + if (!$result['accessible']) { + return true; + } + + return false; + } + + private function send_health_notification($issues) { + $notification_email = get_site_option('dm_notification_email', get_option('admin_email')); + + if (empty($notification_email)) { + return; + } + + $site_name = get_bloginfo('name'); + $subject = sprintf(__('[%s] Domain Mapping Health Alert', 'wp-domain-mapping'), $site_name); + + // Build email content + $message = sprintf(__('Domain health issues were detected on %s.', 'wp-domain-mapping'), $site_name) . "\n\n"; + $message .= __('The following domains have issues:', 'wp-domain-mapping') . "\n\n"; + + foreach ($issues as $domain => $result) { + $message .= sprintf(__('Domain: %s', 'wp-domain-mapping'), $domain) . "\n"; + + // Add DNS status + if ($result['dns_status'] !== 'success') { + $message .= " - " . sprintf(__('DNS Issue: %s', 'wp-domain-mapping'), $result['dns_message']) . "\n"; + } + + // Add SSL status + if (!$result['ssl_valid']) { + $message .= " - " . __('SSL Certificate is invalid or missing.', 'wp-domain-mapping') . "\n"; + } elseif (!empty($result['ssl_expiry'])) { + $expiry_date = strtotime($result['ssl_expiry']); + $threshold = get_site_option('dm_ssl_expiry_threshold', 14); + $threshold_date = strtotime('+' . $threshold . ' days'); + + if ($expiry_date <= $threshold_date) { + $message .= " - " . sprintf( + __('SSL Certificate expires on %s (within %d days).', 'wp-domain-mapping'), + date('Y-m-d', $expiry_date), + $threshold + ) . "\n"; + } + } + + // Add accessibility status + if (!$result['accessible']) { + $message .= " - " . __('Site is not accessible.', 'wp-domain-mapping') . "\n"; + if ($result['response_code'] > 0) { + $message .= " " . sprintf(__('HTTP Response Code: %d', 'wp-domain-mapping'), $result['response_code']) . "\n"; + } + } + + $message .= "\n"; + } + + // Add resolution link + $message .= sprintf( + __('To view and manage these issues, please visit: %s', 'wp-domain-mapping'), + network_admin_url('settings.php?page=domain-mapping-health') + ) . "\n"; + + // Send email + wp_mail($notification_email, $subject, $message); + } + + // Import/Export Functions + public function handle_export() { + if (!isset($_POST['domain_mapping_export']) || !$_POST['domain_mapping_export']) { + return; + } + + // Check permissions + if (!current_user_can('manage_network')) { + wp_die(__('You do not have sufficient permissions to export data.', 'wp-domain-mapping')); + } + + // Verify nonce + if (!isset($_POST['domain_mapping_export_nonce']) || !wp_verify_nonce($_POST['domain_mapping_export_nonce'], 'domain_mapping_export')) { + wp_die(__('Invalid security token. Please try again.', 'wp-domain-mapping')); + } + + // Get options + $include_header = isset($_POST['include_header']) ? (bool) $_POST['include_header'] : false; + $blog_id_filter = isset($_POST['blog_id_filter']) && !empty($_POST['blog_id_filter']) ? intval($_POST['blog_id_filter']) : 0; + + // Get domain mapping data + global $wpdb; + $table = $wpdb->base_prefix . WP_DOMAIN_MAPPING_TABLE_DOMAINS; + $sql = "SELECT blog_id, domain, active FROM {$table}"; + + if ($blog_id_filter > 0) { + $sql .= $wpdb->prepare(" WHERE blog_id = %d", $blog_id_filter); + } + + $domains = $wpdb->get_results($sql, ARRAY_A); + + if (empty($domains)) { + // No data + wp_redirect(add_query_arg(array('page' => 'domain-mapping-import-export', 'export' => 'empty'), network_admin_url('settings.php'))); + exit; + } + + // Set up CSV output + $filename = 'domain-mappings-' . date('Y-m-d') . '.csv'; + + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=' . $filename); + + $output = fopen('php://output', 'w'); + + // Add header row + if ($include_header) { + fputcsv($output, array('blog_id', 'domain', 'active')); + } + + // Add data rows + foreach ($domains as $domain) { + fputcsv($output, $domain); + } + + fclose($output); + exit; + } + + public function ajax_import_csv() { + // Check permissions + if (!current_user_can('manage_network')) { + wp_send_json_error(__('You do not have sufficient permissions to import data.', 'wp-domain-mapping')); + } + + // Verify nonce + if (!isset($_POST['domain_mapping_import_nonce']) || !wp_verify_nonce($_POST['domain_mapping_import_nonce'], 'domain_mapping_import')) { + wp_send_json_error(__('Invalid security token. Please try again.', 'wp-domain-mapping')); + } + + // Check file + if (!isset($_FILES['csv_file']) || $_FILES['csv_file']['error'] != UPLOAD_ERR_OK) { + wp_send_json_error(__('No file uploaded or upload error.', 'wp-domain-mapping')); + } + + // Get options + $has_header = isset($_POST['has_header']) ? (bool) $_POST['has_header'] : false; + $update_existing = isset($_POST['update_existing']) ? (bool) $_POST['update_existing'] : false; + $validate_sites = isset($_POST['validate_sites']) ? (bool) $_POST['validate_sites'] : true; + + // Open file + $file = fopen($_FILES['csv_file']['tmp_name'], 'r'); + if (!$file) { + wp_send_json_error(__('Could not open the uploaded file.', 'wp-domain-mapping')); + } + + // Initialize counters and log + $imported = 0; + $skipped = 0; + $errors = 0; + $log = array(); + + // Skip header row + if ($has_header) { + fgetcsv($file); + } + + // Process each row + $row_num = $has_header ? 2 : 1; // Account for header row + + while (($data = fgetcsv($file)) !== false) { + // Check data format + if (count($data) < 3) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Invalid format. Expected at least 3 columns.', 'wp-domain-mapping'), $row_num) + ); + $errors++; + $row_num++; + continue; + } + + // Parse data + $blog_id = intval($data[0]); + $domain = $this->core->clean_domain(trim($data[1])); + $active = intval($data[2]); + + // Validate blog_id + if ($blog_id <= 0) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Invalid blog ID: %d', 'wp-domain-mapping'), $row_num, $blog_id) + ); + $errors++; + $row_num++; + continue; + } + + // Validate site exists + if ($validate_sites && !get_blog_details($blog_id)) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Site ID %d does not exist.', 'wp-domain-mapping'), $row_num, $blog_id) + ); + $errors++; + $row_num++; + continue; + } + + // Validate domain format + if (!$this->core->validate_domain($domain)) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Invalid domain format: %s', 'wp-domain-mapping'), $row_num, $domain) + ); + $errors++; + $row_num++; + continue; + } + + // Check if domain already exists + $existing = $this->db->get_domain_by_name($domain); + + if ($existing) { + if ($existing->blog_id != $blog_id) { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Domain %s is already mapped to blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $existing->blog_id) + ); + $errors++; + } elseif (!$update_existing) { + $log[] = array( + 'status' => 'warning', + 'message' => sprintf(__('Row %d: Domain %s already exists for blog ID %d. Skipped.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $skipped++; + } else { + // Update existing domain + $success = $this->db->update_domain($domain, $blog_id, $active); + + if ($success) { + $log[] = array( + 'status' => 'success', + 'message' => sprintf(__('Row %d: Updated domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $imported++; + } else { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Failed to update domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $errors++; + } + } + } else { + // Add new domain + $success = $this->db->add_domain($blog_id, $domain, $active); + + if ($success) { + $log[] = array( + 'status' => 'success', + 'message' => sprintf(__('Row %d: Added domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $imported++; + } else { + $log[] = array( + 'status' => 'error', + 'message' => sprintf(__('Row %d: Failed to add domain %s for blog ID %d.', 'wp-domain-mapping'), + $row_num, $domain, $blog_id) + ); + $errors++; + } + } + + $row_num++; + } + + fclose($file); + + // Build response + $message = sprintf( + __('Import completed: %d imported, %d skipped, %d errors.', 'wp-domain-mapping'), + $imported, $skipped, $errors + ); + + wp_send_json_success(array( + 'message' => $message, + 'imported' => $imported, + 'skipped' => $skipped, + 'errors' => $errors, + 'details' => $log + )); + } +} diff --git a/sunrise.php b/sunrise.php index 6d39824..8c3198f 100644 --- a/sunrise.php +++ b/sunrise.php @@ -1,53 +1,61 @@ dmtable = $wpdb->base_prefix . 'domain_mapping'; -$dm_domain = $_SERVER[ 'HTTP_HOST' ]; +// Enable domain mapping +define('DOMAIN_MAPPING', 1); -if( ( $nowww = preg_replace( '|^www\.|', '', $dm_domain ) ) != $dm_domain ) - $where = $wpdb->prepare( 'domain IN (%s,%s)', $dm_domain, $nowww ); -else - $where = $wpdb->prepare( 'domain = %s', $dm_domain ); - -$wpdb->suppress_errors(); -$domain_mapping_id = $wpdb->get_var( "SELECT blog_id FROM {$wpdb->dmtable} WHERE {$where} ORDER BY CHAR_LENGTH(domain) DESC LIMIT 1" ); -$wpdb->suppress_errors( false ); -if( $domain_mapping_id ) { - $current_blog = $wpdb->get_row("SELECT * FROM {$wpdb->blogs} WHERE blog_id = '$domain_mapping_id' LIMIT 1"); - $current_blog->domain = $dm_domain; - $current_blog->path = '/'; - $blog_id = $domain_mapping_id; - $site_id = $current_blog->site_id; - - define( 'COOKIE_DOMAIN', $dm_domain ); - - $current_site = $wpdb->get_row( "SELECT * from {$wpdb->site} WHERE id = '{$current_blog->site_id}' LIMIT 0,1" ); - $current_site->blog_id = $wpdb->get_var( "SELECT blog_id FROM {$wpdb->blogs} WHERE domain='{$current_site->domain}' AND path='{$current_site->path}'" ); - if ( function_exists( 'get_site_option' ) ) - $current_site->site_name = get_site_option( 'site_name' ); - elseif ( function_exists( 'get_current_site_name' ) ) - $current_site = get_current_site_name( $current_site ); - - define( 'DOMAIN_MAPPING', 1 ); +// Check if we're on the main site already +if (defined('COOKIE_DOMAIN') && COOKIE_DOMAIN == $_SERVER['HTTP_HOST']) { + return; +} + +global $wpdb, $current_blog, $current_site; + +// Get domains table name +$domain_mapping_table = $wpdb->base_prefix . 'domain_mapping'; + +// Check for the current domain in the domain mapping table +$domain = $wpdb->escape($_SERVER['HTTP_HOST']); +$blog_id = $wpdb->get_var("SELECT blog_id FROM {$domain_mapping_table} WHERE domain = '{$domain}' LIMIT 1"); + +// 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"); + + 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']); + + // Define the mapped domain constant + define('MAPPED_DOMAIN', true); + + // Allow other plugins to know this is a mapped domain + $GLOBALS['dm_domain'] = array( + 'original' => $current_blog->domain, + 'mapped' => $_SERVER['HTTP_HOST'] + ); + + // Fix request URI for path sites + if ($current_blog->path != '/' && ($current_blog->path != '/wp/' || strpos($_SERVER['REQUEST_URI'], '/wp/') === false)) { + $current_blog->path = '/'; + } + } } diff --git a/wp-domain-mapping.php b/wp-domain-mapping.php index d327406..b509564 100644 --- a/wp-domain-mapping.php +++ b/wp-domain-mapping.php @@ -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: 1.3.4 + * Version: 2.0.0 * Author: WPDomain.com * Author URI: https://wpdomain.com/ * Network: true @@ -14,1129 +14,1445 @@ * Requires at least: 6.7.2 */ -if (!defined('ABSPATH')) { +// Prevent direct access +if ( ! defined( 'ABSPATH' ) ) { exit; } -define('WP_DOMAIN_MAPPING_VERSION', '1.3.4'); -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__)); +// Define plugin constants +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__ ) ); +/** + * Main Domain Mapping Class + * + * Handles the core functionality of the plugin + */ +class WP_Domain_Mapping { -// Load text domain -function wp_domain_mapping_text_domain() { - load_plugin_textdomain('wp-domain-mapping', false, dirname(plugin_basename(__FILE__)) . '/languages'); -} -add_action('init', 'wp_domain_mapping_text_domain'); + /** + * Plugin instance + * + * @var WP_Domain_Mapping + */ + private static $instance = null; + /** + * Database table names + * + * @var array + */ + private $tables = array(); -// Integrate UpdatePulse Server for updates using PUC v5.3 -require_once plugin_dir_path(__FILE__) . 'lib/plugin-update-checker/plugin-update-checker.php'; -use YahnisElsts\PluginUpdateChecker\v5p3\PucFactory; - -$WpDomainMappingUpdateChecker = PucFactory::buildUpdateChecker( - 'https://updates.weixiaoduo.com/wp-domain-mapping.json', - __FILE__, - 'wp-domain-mapping' -); - -// Warning: Network not configured -function domain_mapping_warning() { - echo '

' . __('Domain Mapping Disabled.', 'wp-domain-mapping') . ' ' . sprintf(__('You must create a network for it to work.', 'wp-domain-mapping'), 'http://codex.wordpress.org/Create_A_Network') . '

'; -} - -// Add admin pages -function dm_add_pages() { - global $current_site, $wpdb, $wp_db_version; - - if (!isset($current_site) && $wp_db_version >= 15260) { - add_action('admin_notices', 'domain_mapping_warning'); - return false; - } - if ($current_site->path != "/") { - wp_die(__("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')); + /** + * Get plugin instance + * + * @return WP_Domain_Mapping + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + return self::$instance; } - if (get_site_option('dm_user_settings') && $current_site->blog_id != $wpdb->blogid && !dm_sunrise_warning(false)) { - add_management_page(__('Domain Mapping', 'wp-domain-mapping'), __('Domain Mapping', 'wp-domain-mapping'), 'manage_options', 'domainmapping', 'dm_manage_page'); - } -} -add_action('admin_menu', 'dm_add_pages'); + /** + * Constructor + */ + private function __construct() { + global $wpdb; -// Add network admin pages -function dm_network_pages() { - add_submenu_page('settings.php', __('Domain Mapping', 'wp-domain-mapping'), __('Domain Mapping', 'wp-domain-mapping'), 'manage_network', 'domain-mapping', 'dm_admin_page'); - add_submenu_page('sites.php', __('Domains', 'wp-domain-mapping'), __('Domains', 'wp-domain-mapping'), 'manage_network', 'domains', 'dm_domains_admin'); -} -add_action('network_admin_menu', 'dm_network_pages'); - -// Default update messages -function dm_echo_default_updated_msg() { - 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; - } - echo "

$msg

"; -} -add_action('dm_echo_updated_msg', 'dm_echo_default_updated_msg'); - -// Create database tables -function maybe_create_db() { - global $wpdb; - - // Initialize remote login hash - get_dm_hash(); - - // Set global table names - $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping'; - $wpdb->dmtablelogins = $wpdb->base_prefix . 'domain_mapping_logins'; - $wpdb->dmtablelogs = $wpdb->base_prefix . 'domain_mapping_logs'; - - // Only network admins can create tables - if (!dm_site_admin()) { - return; - } - - // Use static variable to prevent repeated creation - static $tables_created = false; - if ($tables_created) { - return; - } - - $created = 0; - - // Create domain_mapping table - if ($wpdb->get_var("SHOW TABLES LIKE '{$wpdb->dmtable}'") != $wpdb->dmtable) { - $wpdb->query("CREATE TABLE IF NOT EXISTS `{$wpdb->dmtable}` ( - `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`) - );"); - $created = 1; - } - - // Create domain_mapping_logins table - if ($wpdb->get_var("SHOW TABLES LIKE '{$wpdb->dmtablelogins}'") != $wpdb->dmtablelogins) { - $wpdb->query("CREATE TABLE IF NOT EXISTS `{$wpdb->dmtablelogins}` ( - `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`) - );"); - $created = 1; - } - - // Create domain_mapping_logs table - if ($wpdb->get_var("SHOW TABLES LIKE '{$wpdb->dmtablelogs}'") != $wpdb->dmtablelogs) { - $wpdb->query("CREATE TABLE IF NOT EXISTS `{$wpdb->dmtablelogs}` ( - `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`) - );"); - $created = 1; - } - - // If any table was created, show success message and mark as created - if ($created) { - echo '

' . __('Domain mapping database table created.', 'wp-domain-mapping') . '

'; - $tables_created = true; - } -} - -// Ajax handler for domain actions -function dm_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; - $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping'; - $wpdb->dmtablelogs = $wpdb->base_prefix . 'domain_mapping_logs'; - $action = sanitize_text_field($_POST['action_type']); - $domain = dm_clean_domain(sanitize_text_field(strtolower($_POST['domain']))); - $blog_id = absint($_POST['blog_id']); - $active = isset($_POST['active']) ? absint($_POST['active']) : 0; - $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 && null == $wpdb->get_var($wpdb->prepare("SELECT domain FROM {$wpdb->dmtable} WHERE blog_id != %d AND domain = %s", $blog_id, $domain))) { - if (empty($orig_domain)) { - $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->dmtable} ( `blog_id`, `domain`, `active` ) VALUES ( %d, %s, %d )", $blog_id, $domain, $active)); - $wpdb->insert($wpdb->dmtablelogs, array('user_id' => $current_user_id, 'action' => 'add', 'domain' => $domain, 'blog_id' => $blog_id)); - wp_send_json_success(__('Domain added successfully.', 'wp-domain-mapping')); - } else { - $wpdb->query($wpdb->prepare("UPDATE {$wpdb->dmtable} SET blog_id = %d, domain = %s, active = %d WHERE domain = %s", $blog_id, $domain, $active, $orig_domain)); - $wpdb->insert($wpdb->dmtablelogs, array('user_id' => $current_user_id, 'action' => 'edit', 'domain' => $domain, 'blog_id' => $blog_id)); - wp_send_json_success(__('Domain updated successfully.', 'wp-domain-mapping')); - } - } else { - wp_send_json_error(__('Invalid site ID or domain already exists.', 'wp-domain-mapping')); - } - break; - case 'delete': - $domains = isset($_POST['domains']) ? array_map('sanitize_text_field', (array)$_POST['domains']) : array($domain); - foreach ($domains as $del_domain) { - $affected_blog_id = $wpdb->get_var($wpdb->prepare("SELECT blog_id FROM {$wpdb->dmtable} WHERE domain = %s", $del_domain)); - $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->dmtable} WHERE domain = %s", $del_domain)); - $wpdb->insert($wpdb->dmtablelogs, array('user_id' => $current_user_id, 'action' => 'delete', 'domain' => $del_domain, 'blog_id' => $affected_blog_id)); - } - wp_send_json_success(__('Selected domains deleted successfully.', 'wp-domain-mapping')); - break; - default: - wp_send_json_error(__('Invalid action.', 'wp-domain-mapping')); - } -} -add_action('wp_ajax_dm_handle_actions', 'dm_ajax_handle_actions'); - -// Domains admin page -function dm_domains_admin() { - global $wpdb, $current_site; - if (!dm_site_admin()) { - return false; - } - - dm_sunrise_warning(); - maybe_create_db(); - - if ($current_site->path != "/") { - wp_die(sprintf(__("Warning! 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"), $current_site->path)); - } - - $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping'; - $total_domains = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->dmtable}"); - $primary_domains = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->dmtable} WHERE active = 1"); - - $edit_row = false; - if (isset($_GET['edit_domain'])) { - $edit_domain = sanitize_text_field($_GET['edit_domain']); - $edit_row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->dmtable} WHERE domain = %s", $edit_domain)); - } - - ?> -
-

- - - -

- -
-

- - -
- -
-

-
- -

- - - - - - - -

-
-
- -
-
- - -
-
-
- -
- prepare("domain LIKE %s", '%' . $wpdb->esc_like($_GET['s']) . '%'); - if (!empty($_GET['blog_id'])) $where[] = $wpdb->prepare("blog_id = %d", $_GET['blog_id']); - if (isset($_GET['active']) && $_GET['active'] !== '') $where[] = $wpdb->prepare("active = %d", $_GET['active']); - $where_sql = $where ? ' WHERE ' . implode(' AND ', $where) : ''; - - $total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->dmtable}" . $where_sql); - $total_pages = ceil($total_items / $per_page); - - $rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->dmtable}" . $where_sql . " ORDER BY id DESC LIMIT %d, %d", $offset, $per_page)); - dm_domain_listing($rows); - - echo '
'; - echo '
'; - echo paginate_links(array( - 'base' => add_query_arg('paged', '%#%'), - 'format' => '', - 'prev_text' => __('«'), - 'next_text' => __('»'), - 'total' => $total_pages, - 'current' => $paged, - 'mid_size' => 2, - 'end_size' => 1, - )); - echo '' . sprintf(_n('%s item', '%s items', $total_items, 'wp-domain-mapping'), number_format_i18n($total_items)) . ''; - echo '
'; - echo '
'; - ?> -

Note: %s', 'wp-domain-mapping'), dm_idn_warning()); ?>

-
-
- -
-
- -
-

- - - - - -
-
- -
- - - - - blog_id = ''; - $row->domain = ''; - $row->active = 1; - } - ?> -
- - - - - - - - - - - - - - - - - - - -
- -

-
active, 1); ?> />
Warning! Primary domains are currently disabled.', 'wp-domain-mapping'); ?>
-

-
- dmtable = $wpdb->base_prefix . 'domain_mapping'; - if (!$rows) { - echo '

' . __('No domains found.', 'wp-domain-mapping') . '

'; - return; + } } - $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')); - ?> -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - -
blog_id); ?>domain); ?> - active == 1): ?> - - - - - - -
- - " . __('Warning! Primary domains are currently disabled.', 'wp-domain-mapping') . "

"; + /** + * 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' ) ); + } } -} -function dm_ensure_protocol($domain) { - if (preg_match('#^https?://#', $domain)) { - return $domain; + /** + * 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' ) ); + + // 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 ); + } + + // Handle remote login + if ( isset( $_GET['dm'] ) ) { + add_action( 'template_redirect', array( $this, 'remote_login_js' ) ); + } } - return 'http://' . $domain; -} -function dm_clean_domain($domain) { - $domain = preg_replace('#^https?://#', '', $domain); - $domain = rtrim($domain, '/'); - return $domain; -} + /** + * 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 ); -// Domain logs display -function dm_domain_logs() { - global $wpdb; - $wpdb->dmtablelogs = $wpdb->base_prefix . 'domain_mapping_logs'; - $logs = $wpdb->get_results("SELECT * FROM {$wpdb->dmtablelogs} ORDER BY timestamp DESC LIMIT 20"); - if (!$logs) { - echo '

' . __('No logs available.', 'wp-domain-mapping') . '

'; - return; + 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' ) ); } - ?> - - - - - - - - - - - - - - - - - - - - - -
user_id)->user_login); ?>action); ?>domain); ?>blog_id); ?>timestamp); ?>
- 'domains', 'blog_id' => $blog_id), admin_url('network/sites.php')); - $actions['domains'] = '' . __('Domains', 'wp-domain-mapping') . ''; - return $actions; -} -add_filter('manage_sites_action_links', 'dm_add_domain_link_to_sites', 10, 2); + /** + * Actions to perform on plugin activation + */ + public function plugin_activation() { + // Create database tables + $this->create_tables(); -// Configuration page -function dm_admin_page() { - global $wpdb, $current_site; - if (!dm_site_admin()) { + // Initialize remote login hash + $this->get_hash(); + } + + /** + * 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' ) + ); + } + + /** + * 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 ( $this->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 ( $this->is_site_admin() ) { + wp_die( + sprintf( + /* translators: %s: WordPress install path */ + esc_html__( 'Please uncomment the line define( \'SUNRISE\', \'on\' ); 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 ( $this->is_site_admin() ) { + wp_die( + sprintf( + /* translators: %s: WordPress install path */ + esc_html__( 'Please edit your %swp-config.php and move the line define( \'SUNRISE\', \'on\' ); 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; } - dm_sunrise_warning(); - maybe_create_db(); - - if ($current_site->path != "/") { - wp_die(sprintf(__("Warning! 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"), $current_site->path)); + /** + * Display warning message for network configuration + */ + public function domain_mapping_warning() { + echo '

' . esc_html__( 'Domain Mapping Disabled.', 'wp-domain-mapping' ) . ' ' . + sprintf( + /* translators: %s: URL to WordPress network creation documentation */ + wp_kses( + __( 'You must create a network for it to work.', 'wp-domain-mapping' ), + array( 'a' => array( 'href' => array() ) ) + ), + 'http://codex.wordpress.org/Create_A_Network' + ) . '

'; } - 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); + /** + * Create required database tables + */ + public function create_tables() { + global $wpdb; - if (!empty($_POST['action']) && $_POST['action'] == 'update') { - check_admin_referer('domain_mapping'); - $ipok = true; - $ipaddresses = explode(',', sanitize_text_field($_POST['ipaddress'])); - foreach ($ipaddresses as $address) { - if (($ip = trim($address)) && !preg_match('|^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$|', $ip)) { - $ipok = false; + // Only network admins can create tables + if ( ! $this->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 '

' . + esc_html__( 'Domain mapping database tables created.', 'wp-domain-mapping' ) . + '

'; + }); + $tables_created = true; + } + } + + /** + * 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 = $this->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'] ) ? $this->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 ( ! $this->is_valid_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 + $wpdb->insert( + $this->tables['logs'], + array( + 'user_id' => $current_user_id, + 'action' => 'add', + 'domain' => $domain, + 'blog_id' => $blog_id + ), + array( '%d', '%s', '%s', '%d' ) + ); + + $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 + $wpdb->insert( + $this->tables['logs'], + array( + 'user_id' => $current_user_id, + 'action' => 'edit', + 'domain' => $domain, + 'blog_id' => $blog_id + ), + array( '%d', '%s', '%s', '%d' ) + ); + + $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; - } - } - if ($ipok) update_site_option('dm_ipaddress', $_POST['ipaddress']); - if (intval($_POST['always_redirect_admin']) == 0) $_POST['dm_remote_login'] = 0; - update_site_option('dm_remote_login', intval($_POST['dm_remote_login'])); - 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', ''); - 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); - } - ?> -
-

- - - -

-
-

-

-
- - - - - - - - - - - -
- -

-
- -

-
-

- - - - - - -
/>
/>
/>
/>
/>
-

-
-
-
- - query( 'START TRANSACTION' ); + $deleted = 0; - if (isset($_GET['updated'])) { - do_action('dm_echo_updated_msg'); - } + try { + foreach ( $domains as $del_domain ) { + if ( empty( $del_domain ) ) continue; - dm_sunrise_warning(); + // 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 (!get_site_option('dm_ipaddress') && !get_site_option('dm_cname')) { - if (dm_site_admin()) { - _e("Please set the IP address or CNAME of your server in the site admin page.", 'wp-domain-mapping'); - } else { - _e("This plugin has not been configured correctly yet.", 'wp-domain-mapping'); - } - return false; - } + if ( $affected_blog_id ) { + // Delete the domain + $result = $wpdb->delete( + $this->tables['domains'], + array( 'domain' => $del_domain ), + array( '%s' ) + ); - $protocol = is_ssl() ? 'https://' : 'http://'; - $domains = $wpdb->get_results("SELECT * FROM {$wpdb->dmtable} WHERE blog_id = '{$wpdb->blogid}'", ARRAY_A); - ?> -
-

-
-

- - $orig_url['host'], 'path' => $orig_url['path'], 'active' => 0); ?> -
- - - - 'domainmapping', 'action' => 'delete'), admin_url($parent_file)); - foreach ($domains as $details): - if (0 == $primary_found && $details['domain'] == $orig_url['host']) $details['active'] = 1; - ?> - - - - - - - - -
/>
- - -

-
-

-

Warning! Primary domains are currently disabled.', 'wp-domain-mapping'); ?>

- -
-
-

-
- - -

- -

-

-
- " . sprintf(__('Add a DNS "CNAME" record pointing to: %s', 'wp-domain-mapping'), esc_html(get_site_option('dm_cname'))) . "

"; - } else { - $dm_ipaddress = get_site_option('dm_ipaddress', 'IP not set.'); - echo "

" . sprintf(__('Add a DNS "A" record pointing to: %s', 'wp-domain-mapping'), esc_html($dm_ipaddress)) . "

"; - } - ?> -

Note: %s', 'wp-domain-mapping'), dm_idn_warning()); ?>

-
-
- - 'domainmapping'), admin_url($parent_file)); - if (!empty($_POST['action'])) { - $domain = sanitize_text_field($_POST['domain']); - if (empty($domain)) wp_die(__("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); - if (null == $wpdb->get_row("SELECT blog_id FROM {$wpdb->blogs} WHERE domain = '$domain'") && null == $wpdb->get_row("SELECT blog_id FROM {$wpdb->dmtable} WHERE domain = '$domain'")) { - if ($_POST['primary']) { - $wpdb->query($wpdb->prepare("UPDATE {$wpdb->dmtable} SET active = 0 WHERE blog_id = %d", $wpdb->blogid)); + // Log the action + $wpdb->insert( + $this->tables['logs'], + array( + 'user_id' => $current_user_id, + 'action' => 'delete', + 'domain' => $del_domain, + 'blog_id' => $affected_blog_id + ), + array( '%d', '%s', '%s', '%d' ) + ); + } + } } - $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->dmtable} ( `id`, `blog_id`, `domain`, `active` ) VALUES ( NULL, %d, %s, %d )", $wpdb->blogid, $domain, $_POST['primary'])); - wp_redirect(add_query_arg(array('updated' => 'add'), $url)); + + 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 ( ! $this->is_valid_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 + $orig_url = parse_url( $this->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() { + global $wpdb, $current_site; + + if ( ! $this->is_site_admin() ) { + return false; + } + + $this->sunrise_warning(); + $this->create_tables(); + + if ( isset($current_site->path) && $current_site->path != "/" ) { + wp_die( sprintf( + esc_html__( "Warning! 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 ) + )); + } + + $total_domains = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->tables['domains']}" ); + $primary_domains = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->tables['domains']} WHERE active = 1" ); + + $edit_row = false; + if ( isset( $_GET['edit_domain'] ) ) { + $edit_domain = sanitize_text_field( $_GET['edit_domain'] ); + $edit_row = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM {$this->tables['domains']} WHERE domain = %s", + $edit_domain + )); + } + + // Load admin UI + require_once( WP_DOMAIN_MAPPING_DIR_PATH . 'admin/domains-page.php' ); + } + + /** + * Render domain logs + */ + public function render_domain_logs() { + global $wpdb; + + $logs = $wpdb->get_results( "SELECT * FROM {$this->tables['logs']} ORDER BY timestamp DESC LIMIT 50" ); + + if ( ! $logs ) { + echo '

' . esc_html__( 'No logs available.', 'wp-domain-mapping' ) . '

'; + return; + } + + // Load logs UI + require_once( WP_DOMAIN_MAPPING_DIR_PATH . 'admin/logs-table.php' ); + } + + /** + * Render admin configuration page + */ + public function render_admin_page() { + global $wpdb, $current_site; + + if ( ! $this->is_site_admin() ) { + return false; + } + + $this->sunrise_warning(); + $this->create_tables(); + + if ( isset($current_site->path) && $current_site->path != "/" ) { + wp_die( sprintf( + esc_html__( "Warning! 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' + ); + } + + // Load admin UI + require_once( WP_DOMAIN_MAPPING_DIR_PATH . 'admin/settings-page.php' ); + } + + /** + * 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 ( $this->is_site_admin() ) { + echo wp_kses( + __( "Please set the IP address or CNAME of your server in the site admin page.", '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 = $wpdb->get_results( $wpdb->prepare( + "SELECT * FROM {$this->tables['domains']} WHERE blog_id = %d", + $wpdb->blogid + ), ARRAY_A ); + + // Load user UI + require_once( WP_DOMAIN_MAPPING_DIR_PATH . 'admin/user-page.php' ); + } + + /** + * 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'] = '' . + esc_html__( 'Domains', 'wp-domain-mapping' ) . ''; + + 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 ) . '
'; + } + } + } + } + + /** + * 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; + } + } + } + + /** + * 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 ""; + } + } + + /** + * 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_redirect(add_query_arg(array('updated' => 'exists'), $url)); - exit; + wp_die( esc_html__( "Unknown logout key", 'wp-domain-mapping' ) ); } - case "primary": - do_action('dm_handle_actions_primary', $domain); - $wpdb->query($wpdb->prepare("UPDATE {$wpdb->dmtable} SET active = 0 WHERE blog_id = %d", $wpdb->blogid)); - $orig_url = parse_url(get_original_url('siteurl')); - if ($domain != $orig_url['host']) { - $wpdb->query($wpdb->prepare("UPDATE {$wpdb->dmtable} SET active = 1 WHERE domain = %s", $domain)); - } - wp_redirect(add_query_arg(array('updated' => 'primary'), $url)); - exit; - } - } elseif ($_GET['action'] == 'delete') { - $domain = sanitize_text_field($_GET['domain']); - if (empty($domain)) wp_die(__("You must enter a domain", 'wp-domain-mapping')); - check_admin_referer("delete" . $_GET['domain']); - do_action('dm_handle_actions_del', $domain); - $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->dmtable} WHERE domain = %s", $domain)); - wp_redirect(add_query_arg(array('updated' => 'del'), $url)); - exit; - } -} -if (isset($_GET['page']) && $_GET['page'] == 'domainmapping') { - add_action('admin_init', 'dm_handle_actions'); -} - -// Sunrise check -function dm_sunrise_warning($die = true) { - if (!file_exists(WP_CONTENT_DIR . '/sunrise.php')) { - if (!$die) return true; - if (dm_site_admin()) { - wp_die(sprintf(__("Please copy sunrise.php to %s/sunrise.php and ensure the SUNRISE definition is in %swp-config.php", 'wp-domain-mapping'), WP_CONTENT_DIR, ABSPATH)); - } else { - wp_die(__("This plugin has not been configured correctly yet.", 'wp-domain-mapping')); - } - } elseif (!defined('SUNRISE')) { - if (!$die) return true; - if (dm_site_admin()) { - wp_die(sprintf(__("Please uncomment the line define( 'SUNRISE', 'on' ); or add it to your %swp-config.php", 'wp-domain-mapping'), ABSPATH)); - } else { - wp_die(__("This plugin has not been configured correctly yet.", 'wp-domain-mapping')); - } - } elseif (!defined('SUNRISE_LOADED')) { - if (!$die) return true; - if (dm_site_admin()) { - wp_die(sprintf(__("Please edit your %swp-config.php and move the line define( 'SUNRISE', 'on' ); above the last require_once() in that file or make sure you updated sunrise.php.", 'wp-domain-mapping'), ABSPATH)); - } else { - wp_die(__("This plugin has not been configured correctly yet.", 'wp-domain-mapping')); - } - } - return false; -} - -// Core domain mapping functions -function domain_mapping_siteurl($setting) { - global $wpdb, $current_blog; - static $return_url = array(); - - $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping'; - - if (!isset($return_url[$wpdb->blogid])) { - $s = $wpdb->suppress_errors(); - if (get_site_option('dm_no_primary_domain') == 1) { - $domain = $wpdb->get_var($wpdb->prepare("SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = %d AND domain = %s LIMIT 1", $wpdb->blogid, $_SERVER['HTTP_HOST'])); - if (null == $domain) { - $return_url[$wpdb->blogid] = untrailingslashit(get_original_url("siteurl")); - return $return_url[$wpdb->blogid]; - } - } else { - $domain = $wpdb->get_var($wpdb->prepare("SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = %d AND active = 1 LIMIT 1", $wpdb->blogid)); - if (null == $domain) { - $return_url[$wpdb->blogid] = untrailingslashit(get_original_url("siteurl")); - return $return_url[$wpdb->blogid]; } } - $wpdb->suppress_errors($s); + } + + /** + * 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://'; - if ($domain) { - $return_url[$wpdb->blogid] = untrailingslashit($protocol . $domain); + $hash = $this->get_hash(); + + echo ""; + } + + /** + * 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; } - } elseif ($return_url[$wpdb->blogid] !== false) { - $setting = $return_url[$wpdb->blogid]; + + return $setting; } - return $setting; -} -function get_original_url($url, $blog_id = 0) { - global $wpdb; - $id = $blog_id ?: $wpdb->blogid; - static $orig_urls = array(); + /** + * 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])) { - if (defined('DOMAIN_MAPPING')) remove_filter('pre_option_' . $url, 'domain_mapping_' . $url); - $orig_url = $blog_id == 0 ? get_option($url) : get_blog_option($blog_id, $url); - $orig_url = is_ssl() ? str_replace("http://", "https://", $orig_url) : str_replace("https://", "http://", $orig_url); - $orig_urls[$id] = $orig_url; - if (defined('DOMAIN_MAPPING')) add_filter('pre_option_' . $url, 'domain_mapping_' . $url); - } - return $orig_urls[$id]; -} - -function domain_mapping_adminurl($url, $path, $blog_id = 0) { - $index = strpos($url, '/wp-admin'); - if ($index !== false) { - $url = 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; -} - -function domain_mapping_post_content($post_content) { - $orig_url = get_original_url('siteurl'); - $url = domain_mapping_siteurl('NA'); - if ($url == 'NA') return $post_content; - return str_replace($orig_url, $url, $post_content); -} - -function dm_redirect_admin() { - if (strpos($_SERVER['REQUEST_URI'], 'wp-admin/admin-ajax.php') !== false) return; - if (get_site_option('dm_redirect_admin')) { - $url = get_original_url('siteurl'); - if (false === strpos($url, $_SERVER['HTTP_HOST'])) { - wp_redirect(untrailingslashit($url) . $_SERVER['REQUEST_URI']); - exit; - } - } else { - global $current_blog; - $url = 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; - } - } -} - -function redirect_login_to_orig() { - if (!get_site_option('dm_remote_login') || $_GET['action'] == 'logout' || isset($_GET['loggedout'])) return; - $url = get_original_url('siteurl'); - if ($url != site_url()) { - $url .= "/wp-login.php"; - echo ""; - } -} - -function domain_mapping_plugins_uri($full_url, $path = null, $plugin = null) { - return get_option('siteurl') . substr($full_url, stripos($full_url, PLUGINDIR) - 1); -} - -function domain_mapping_themes_uri($full_url) { - return str_replace(get_original_url('siteurl'), get_option('siteurl'), $full_url); -} - -if (defined('DOMAIN_MAPPING')) { - add_filter('plugins_url', 'domain_mapping_plugins_uri', 1); - add_filter('theme_root_uri', 'domain_mapping_themes_uri', 1); - add_filter('pre_option_siteurl', 'domain_mapping_siteurl'); - add_filter('pre_option_home', 'domain_mapping_siteurl'); - add_filter('the_content', 'domain_mapping_post_content'); - add_action('wp_head', 'remote_login_js_loader'); - add_action('login_head', 'redirect_login_to_orig'); - add_action('wp_logout', 'remote_logout_loader', 9999); - - add_filter('stylesheet_uri', 'domain_mapping_post_content'); - add_filter('stylesheet_directory', 'domain_mapping_post_content'); - add_filter('stylesheet_directory_uri', 'domain_mapping_post_content'); - add_filter('template_directory', 'domain_mapping_post_content'); - add_filter('template_directory_uri', 'domain_mapping_post_content'); - add_filter('plugins_url', 'domain_mapping_post_content'); -} else { - add_filter('admin_url', 'domain_mapping_adminurl', 10, 3); -} -add_action('admin_init', 'dm_redirect_admin'); -if (isset($_GET['dm'])) add_action('template_redirect', 'remote_login_js'); - -function remote_logout_loader() { - global $current_site, $current_blog, $wpdb; - $wpdb->dmtablelogins = $wpdb->base_prefix . 'domain_mapping_logins'; - $protocol = is_ssl() ? 'https://' : 'http://'; - $hash = get_dm_hash(); - $key = md5(time()); - $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->dmtablelogins} ( `id`, `user_id`, `blog_id`, `t` ) VALUES( %s, 0, %d, NOW() )", $key, $current_blog->blog_id)); - 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; - } -} - -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 = 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']); - } - header("Location: {$url}{$_SERVER['REQUEST_URI']}", true, $redirect); - exit; - } -} -add_action('template_redirect', 'redirect_to_mapped_domain'); - -function get_dm_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; -} - -function remote_login_js() { - global $current_blog, $current_user, $wpdb; - if (0 == get_site_option('dm_remote_login')) return; - - $wpdb->dmtablelogins = $wpdb->base_prefix . 'domain_mapping_logins'; - $hash = get_dm_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->query($wpdb->prepare("INSERT INTO {$wpdb->dmtablelogins} ( `id`, `user_id`, `blog_id`, `t` ) VALUES( %s, %d, %d, NOW() )", $key, $current_user->ID, $_GET['blogid'])); - $url = add_query_arg(array('action' => 'login', 'dm' => $hash, 'k' => $key, 't' => mt_rand()), $_GET['back']); - echo "window.location = '$url'"; - exit; - } elseif ($_GET['action'] == 'login') { - if ($details = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->dmtablelogins} WHERE id = %s AND blog_id = %d", $_GET['k'], $wpdb->blogid))) { - if ($details->blog_id == $wpdb->blogid) { - $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->dmtablelogins} WHERE id = %s", $_GET['k'])); - $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->dmtablelogins} WHERE t < %d", (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(__("Incorrect or out of date login key", 'wp-domain-mapping')); - } - } else { - wp_die(__("Unknown login key", 'wp-domain-mapping')); + 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' ) ); } - } elseif ($_GET['action'] == 'logout') { - if ($details = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->dmtablelogins} WHERE id = %s AND blog_id = %d", $_GET['k'], $_GET['blogid']))) { - $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->dmtablelogins} WHERE id = %s", $_GET['k'])); - $blog = get_blog_details($_GET['blogid']); - wp_clear_auth_cookie(); - wp_redirect(trailingslashit($blog->siteurl) . "wp-login.php?loggedout=true"); - exit; - } else { - wp_die(__("Unknown logout key", 'wp-domain-mapping')); + + $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]; } -} -function remote_login_js_loader() { - global $current_site, $current_blog; - if (0 == get_site_option('dm_remote_login') || is_user_logged_in()) return; + /** + * 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' ); - $protocol = is_ssl() ? 'https://' : 'http://'; - $hash = get_dm_hash(); - echo ""; -} + if ( $index !== false ) { + $url = $this->get_original_url( 'siteurl', $blog_id ) . substr( $url, $index ); -function delete_blog_domain_mapping($blog_id, $drop) { - global $wpdb; - $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping'; - if ($blog_id && $drop) { - $domains = $wpdb->get_col($wpdb->prepare("SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = %d", $blog_id)); - do_action('dm_delete_blog_domain_mappings', $domains); - $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->dmtable} WHERE blog_id = %d", $blog_id)); - } -} -add_action('delete_blog', 'delete_blog_domain_mapping', 1, 2); - -function ra_domain_mapping_columns($columns) { - $columns['map'] = __('Mapping'); - return $columns; -} -add_filter('wpmu_blogs_columns', 'ra_domain_mapping_columns'); - -function ra_domain_mapping_field($column, $blog_id) { - global $wpdb; - static $maps = false; - - if ($column == 'map') { - if ($maps === false) { - $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping'; - $work = $wpdb->get_results("SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY blog_id"); - $maps = array(); - if ($work) { - foreach ($work as $blog) { - $maps[$blog->blog_id][] = $blog->domain; - } + if ( ( is_ssl() || force_ssl_admin() ) && 0 === strpos( $url, 'http://' ) ) { + $url = 'https://' . substr( $url, 7 ); } } - if (!empty($maps[$blog_id]) && is_array($maps[$blog_id])) { - foreach ($maps[$blog_id] as $blog) { - echo esc_html($blog) . '
'; - } + + 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; + } + + /** + * Check if user is a site admin + * + * @return bool True if user is a site admin + */ + public function is_site_admin() { + return current_user_can( 'manage_network' ); + } + + /** + * Clean domain name + * + * @param string $domain The domain + * @return string The cleaned domain + */ + public function clean_domain( $domain ) { + // Remove protocol + $domain = preg_replace( '#^https?://#', '', $domain ); + + // Remove trailing slash + $domain = rtrim( $domain, '/' ); + + // Convert to punycode for IDN + if ( function_exists( 'idn_to_ascii' ) && preg_match( '/[^a-z0-9\-\.]/i', $domain ) ) { + $domain = idn_to_ascii( $domain ); + } + + return $domain; + } + + /** + * Validate a domain name + * + * @param string $domain The domain + * @return bool True if valid + */ + public function is_valid_domain( $domain ) { + // Basic validation + return (bool) preg_match( '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i', $domain ); + } + + /** + * Get IDN warning message + * + * @return string The warning message + */ + public function idn_warning() { + return sprintf( + /* translators: %s: URL to punycode converter */ + __( 'International Domain Names should be in punycode format.', 'wp-domain-mapping' ), + "https://www.punycoder.com/" + ); + } + + /** + * Ensure URL has protocol + * + * @param string $domain The domain + * @return string The URL with protocol + */ + public function ensure_protocol( $domain ) { + if ( preg_match( '#^https?://#', $domain ) ) { + return $domain; + } + return 'http://' . $domain; + } + + /** + * 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 "

" . esc_html( $msg ) . "

"; } } -add_action('manage_blogs_custom_column', 'ra_domain_mapping_field', 1, 3); -add_action('manage_sites_custom_column', 'ra_domain_mapping_field', 1, 3); -function dm_site_admin() { - return current_user_can('manage_network'); -} +// Initialize the plugin +function wp_domain_mapping_init() { + // Add default updated messages action + add_action( 'dm_echo_updated_msg', array( WP_Domain_Mapping::get_instance(), 'echo_default_updated_msg' ) ); -function dm_idn_warning() { - return sprintf(__('International Domain Names should be in punycode format.', 'wp-domain-mapping'), "https://www.punycoder.com/"); + // Initialize the plugin + return WP_Domain_Mapping::get_instance(); } -?> +wp_domain_mapping_init(); + +// Include admin UI templates +require_once( WP_DOMAIN_MAPPING_DIR_PATH . 'includes/admin-ui.php' );