diff --git a/admin/domains-page.php b/admin/domains-page.php index 2899122..228322e 100644 --- a/admin/domains-page.php +++ b/admin/domains-page.php @@ -1,6 +1,6 @@

@@ -16,7 +18,7 @@ if ( ! defined( 'ABSPATH' ) ) { - + @@ -26,7 +28,70 @@ if ( ! defined( 'ABSPATH' ) ) {
@@ -112,14 +177,15 @@ if ( ! defined( 'ABSPATH' ) ) { } $where_sql = $where ? ' WHERE ' . implode( ' AND ', $where ) : ''; + $tables = dm_get_table_names(); // Count total items for pagination - $total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS . $where_sql ); + $total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$tables['domains']}" . $where_sql ); $total_pages = ceil( $total_items / $per_page ); // Get the domains with pagination $rows = $wpdb->get_results( $wpdb->prepare( - "SELECT * FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS . $where_sql . " ORDER BY id DESC LIMIT %d, %d", + "SELECT * FROM {$tables['domains']}" . $where_sql . " ORDER BY id DESC LIMIT %d, %d", $offset, $per_page )); @@ -191,7 +257,7 @@ if ( ! defined( 'ABSPATH' ) ) { get_var( "SELECT COUNT(DISTINCT blog_id) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_DOMAINS ); + $sites_with_domains = $wpdb->get_var( "SELECT COUNT(DISTINCT blog_id) FROM {$tables['domains']}" ); echo esc_html( $sites_with_domains ); ?> @@ -201,314 +267,162 @@ if ( ! defined( 'ABSPATH' ) ) {

- - - +?> diff --git a/admin/logs-table.php b/admin/logs-table.php index 739649b..d89c77f 100644 --- a/admin/logs-table.php +++ b/admin/logs-table.php @@ -10,46 +10,12 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -// Get pagination parameters -$per_page = isset( $_GET['logs_per_page'] ) ? absint( $_GET['logs_per_page'] ) : 50; -$paged = isset( $_GET['logs_paged'] ) ? absint( $_GET['logs_paged'] ) : 1; -$offset = ( $paged - 1 ) * $per_page; - -// Get action filter -$action_filter = isset( $_GET['log_action'] ) ? sanitize_text_field( $_GET['log_action'] ) : ''; - -// Build WHERE clause for filtering -$where = array(); -if ( ! empty( $action_filter ) ) { - $where[] = $wpdb->prepare( "action = %s", $action_filter ); -} - -$where_sql = $where ? ' WHERE ' . implode( ' AND ', $where ) : ''; - -// Count total logs for pagination -$total_logs = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_LOGS . $where_sql ); -$total_pages = ceil( $total_logs / $per_page ); - -// Get logs with pagination -$logs = $wpdb->get_results( $wpdb->prepare( - "SELECT * FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_LOGS . $where_sql . " ORDER BY timestamp DESC LIMIT %d, %d", - $offset, - $per_page -)); - -// Get available actions for filter -$actions = $wpdb->get_col( "SELECT DISTINCT action FROM {$wpdb->base_prefix}" . WP_DOMAIN_MAPPING_TABLE_LOGS ); - -if ( ! $logs ) { - echo '

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

'; - return; -} +// This file is included from dm_domain_logs() function ?>
-
@@ -57,21 +23,7 @@ if ( ! $logs ) { @@ -79,7 +31,7 @@ if ( ! $logs ) { - + @@ -136,19 +88,8 @@ if ( ! $logs ) { : sprintf( esc_html__( 'User #%d', 'wp-domain-mapping' ), $log->user_id ); // Format action for display - switch ( $log->action ) { - case 'add': - $action_display = '' . 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 ) ) . ''; - } + $action_display = dm_format_action_name( $log->action ); + $action_class = 'log-action-' . $log->action; // Get site name $site_name = get_blog_option( $log->blog_id, 'blogname', '' ); @@ -166,7 +107,11 @@ if ( ! $logs ) { ?> - + + + + + domain ); ?> @@ -216,73 +161,3 @@ if ( ! $logs ) {
- - diff --git a/admin/pages.php b/admin/pages.php new file mode 100644 index 0000000..56ad00e --- /dev/null +++ b/admin/pages.php @@ -0,0 +1,1033 @@ + +
+

+ + + + + + + + + +

+ + __( 'Settings', 'wp-domain-mapping' ), + 'health' => __( 'Domain Health', 'wp-domain-mapping' ), + 'import-export' => __( 'Import/Export', 'wp-domain-mapping' ) + ); + + // Display success messages for tabs + if ( isset( $_GET['checked'] ) && $_GET['checked'] && $current_tab === 'health' ) { + echo '

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

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

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

'; + } + + if ( isset( $_GET['imported'] ) && $_GET['imported'] && $current_tab === 'import-export' ) { + $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' && $current_tab === 'import-export' ) { + echo '

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

'; + } + ?> + + +
+ $tab_label ) : ?> + + +
+ + +
+ +
> + +
+ + +
> + +
+ + +
> + +
+
+
+ + + +
+ + + + +

+

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

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

+ + + + +

+ +
+ + + + + +
+

+ +
+ +

+

+ ' . 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 ) . '' + ); + ?> + +
+ get_var( "SHOW TABLES LIKE '$table'" ) != $table ) { + $tables_exist = false; + break; + } + } + ?> + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ get_results(" + SELECT d.*, b.domain as original_domain, b.path + FROM {$tables['domains']} 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() ); + ?> + +
+

+ +

+

+ + + +
+

+ +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + 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 ); ?> +
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + + +
+
+ +
+

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

+ +

+
+
+ + + +
+

+

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

+
+ +

+ +

+
+
+ +
+

+

+ +
+ + + +
+
+ +

+
+ +

+
+ +
+ +
+ +
+ +

+
+ +
+ +

+
+ +

+ +

+
+ + + + +
+ +
+

+

+ + + + + + + + + + + + + + + + + + + + + +
blog_iddomainactive
1example.com1
2example.org0
+ + +
+ + + 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 ) + )); + } + + $tables = dm_get_table_names(); + $total_domains = $wpdb->get_var( "SELECT COUNT(*) FROM {$tables['domains']}" ); + $primary_domains = $wpdb->get_var( "SELECT COUNT(*) FROM {$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 {$tables['domains']} WHERE domain = %s", + $edit_domain + )); + } + + // Include the admin page template + require_once WP_DOMAIN_MAPPING_DIR_PATH . 'admin/domains-page.php'; +} + +/** + * Render user domain mapping page + */ +function dm_render_user_page( $protocol = null, $domains = null ) { + global $wpdb; + + if ( null === $protocol ) { + $protocol = is_ssl() ? 'https://' : 'http://'; + } + + if ( null === $domains ) { + $domains = dm_get_domains_by_blog_id( $wpdb->blogid ); + } + + // Include original user page content + require_once WP_DOMAIN_MAPPING_DIR_PATH . 'admin/user-page.php'; +} diff --git a/admin/settings-page.php b/admin/settings-page.php index 229d0a0..181bc88 100644 --- a/admin/settings-page.php +++ b/admin/settings-page.php @@ -39,7 +39,7 @@ settings_errors( 'dm_settings' ); - + diff --git a/admin/user-page.php b/admin/user-page.php index c7b9b11..07bc5fc 100644 --- a/admin/user-page.php +++ b/admin/user-page.php @@ -9,11 +9,6 @@ defined('ABSPATH') || exit; $protocol = isset($protocol) ? $protocol : (is_ssl() ? 'https://' : 'http://'); $domains = isset($domains) ? $domains : array(); - -// Initialize logs array if not set to prevent PHP warnings -if (!isset($logs) || !is_array($logs)) { - $logs = array(); -} ?>
@@ -97,18 +92,18 @@ if (!isset($logs) || !is_array($logs)) { - - + + domain); ?> - + active == 1): ?> @@ -116,11 +111,11 @@ if (!isset($logs) || !is_array($logs)) { - + active != 1): ?> + ), 'delete' . $domain->domain); ?>" class="button button-small button-link-delete" onclick="return confirm('');"> @@ -160,24 +155,3 @@ if (!isset($logs) || !is_array($logs)) {
- - diff --git a/assets/css/admin.css b/assets/css/admin.css index fb2fd4c..cf62040 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -1,19 +1,26 @@ -.card { +/* WP Domain Mapping Admin Styles */ + +/* Main cards */ +.domain-mapping-card { background: #fff; border: 1px solid #ccd0d4; border-radius: 4px; max-width: unset; margin-top: 20px; padding: 20px; + box-shadow: 0 1px 1px rgba(0,0,0,.04); } -.styles-sync-tabs { + +/* Tabs */ +.domain-mapping-tabs { display: flex; flex-wrap: wrap; gap: 5px; border-bottom: 1px solid #c3c4c7; margin-bottom: 20px; } -.styles-tab { + +.domain-mapping-tab { padding: 8px 16px; border: none; background: none; @@ -21,44 +28,362 @@ font-size: 14px; border-bottom: 2px solid transparent; } -.styles-tab.active { + +.domain-mapping-tab.active { border-bottom: 2px solid #007cba; font-weight: 600; background: #f0f0f1; } -.styles-tab:hover:not(.active) { + +.domain-mapping-tab:hover:not(.active) { background: #f0f0f1; border-bottom-color: #dcdcde; } -.styles-sync-content { flex: 1; } -.tablenav { margin: 10px 0; } -.tablenav-pages { float: right; } -.tablenav-pages a, .tablenav-pages span { padding: 5px 10px; } -.form-table th { width: 200px; } -.form-table td { padding: 10px -System: 0; } -.description { color: #666; font-size: 12px; } -.notice { padding: 8px 12px; border-radius: 3px; } -.notice-success { background-color: #dff0d8; border-left: 4px solid #46b450; } -.notice-error { background-color: #f2dede; border-left: 4px solid #dc3232; } +.domain-mapping-content { + flex: 1; +} -/* Responsive Styles */ +/* Search form */ +.search-form { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 15px; + margin-bottom: 15px; +} + +.search-form-field { + display: flex; + flex-direction: column; + min-width: 180px; +} + +.search-form-field label { + margin-bottom: 5px; + font-weight: 500; +} + +.search-form-submit { + display: flex; + align-items: center; + gap: 10px; +} + +/* Tables */ +.tablenav { + margin: 10px 0; + display: flex; + align-items: center; +} + +.tablenav-pages { + margin-left: auto; +} + +.tablenav-pages a, +.tablenav-pages span.current { + display: inline-block; + min-width: 17px; + padding: 3px 5px 7px; + background: #f0f0f1; + font-size: 16px; + line-height: 1; + font-weight: 400; + text-align: center; + text-decoration: none; +} + +.tablenav-pages span.current { + background: #007cba; + color: #fff; + border-color: #007cba; +} + +.displaying-num { + margin-left: 10px; + color: #555; +} + +.domains-table th, +.logs-table th, +.domains-health-table th { + font-weight: 600; +} + +.column-site-id { + width: 80px; +} + +.column-site-name { + width: 20%; +} + +.column-primary { + width: 80px; +} + +.column-actions { + width: 120px; +} + +/* Health table specific */ +.column-domain { + width: 20%; +} + +.column-site { + width: 20%; +} + +.column-dns, +.column-ssl, +.column-status { + width: 10%; +} + +.column-last-check { + width: 15%; +} + +/* Site ID Badge */ +.dm-site-id-badge { + display: inline-block; + background: #2271b1; + color: #fff; + font-weight: 500; + padding: 0 5px; + border-radius: 3px; + font-size: 12px; +} + +/* Form elements */ +.form-table th { + width: 200px; + padding: 15px 10px 15px 0; +} + +.form-table td { + padding: 15px 0; +} + +.description { + color: #666; + font-size: 13px; + margin-top: 4px; +} + +/* Notices */ +.notice { + padding: 8px 12px; + border-radius: 3px; + margin: 5px 0 15px; +} + +.notice p { + margin: 0.5em 0; + padding: 2px; +} + +.notice-success { + background-color: #f0f9eb; + border-left: 4px solid #46b450; +} + +.notice-error { + background-color: #fef0f0; + border-left: 4px solid #dc3232; +} + +.notice-warning { + background-color: #fff8e5; + border-left: 4px solid #ffb900; +} + +.notice-info { + background-color: #f0f6fa; + border-left: 4px solid #00a0d2; +} + +/* DNS instructions */ +.dns-instructions { + margin-top: 10px; +} + +.dns-example { + background: #f9f9f9; + border: 1px solid #e5e5e5; + border-radius: 3px; + padding: 15px; + margin: 15px 0; +} + +.dns-example h4 { + margin-top: 0; +} + +.dns-tips { + list-style: disc; + margin-left: 20px; +} + +.dns-tips li { + margin-bottom: 8px; +} + +/* Status indicators */ +.dashicons-yes-alt, +.dashicons-no-alt { + font-size: 24px; + width: 24px; + height: 24px; +} + +/* Logs table styles */ +.logs-filter { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.logs-table .column-user, +.logs-table .column-site { + width: 15%; +} + +.logs-table .column-action { + width: 10%; +} + +.logs-table .column-date { + width: 15%; +} + +.log-action { + display: inline-block; + padding: 2px 8px; + border-radius: 3px; + font-size: 12px; + font-weight: bold; +} + +.log-action-add { + background-color: #dff0d8; + color: #3c763d; +} + +.log-action-edit { + background-color: #d9edf7; + color: #31708f; +} + +.log-action-delete { + background-color: #f2dede; + color: #a94442; +} + +/* Health details */ +.health-details-content { + padding: 15px; + background: #f9f9f9; + border-radius: 3px; +} + +.health-details-content h4 { + margin-top: 0; +} + +/* Progress bar */ +.progress-bar-outer { + background-color: #f0f0f1; + border-radius: 4px; + height: 20px; + width: 100%; + overflow: hidden; + margin: 10px 0; +} + +.progress-bar-inner { + background-color: #2271b1; + height: 100%; + width: 0%; + transition: width 0.3s ease; +} + +.progress-text { + text-align: center; + font-weight: 500; +} + +/* Import/Export specific */ +.import-summary, +.import-details { + margin-top: 15px; +} + +.import-details table { + margin-top: 10px; +} + +.import-details .notice-success { + background-color: #f0f9eb; +} + +.import-details .notice-error { + background-color: #fef0f0; +} + +.import-details .notice-warning { + background-color: #fff8e5; +} + +/* Responsive */ @media screen and (max-width: 782px) { + .search-form { + flex-direction: column; + align-items: stretch; + } + + .search-form-field { + min-width: 100%; + } + .form-table th { width: 100%; display: block; } + .form-table td { display: block; - padding: 5px 0 15px; + padding: 0 0 15px; } - .wp-list-table th.column-primary ~ th, - .wp-list-table td.column-primary ~ td { + + .domain-mapping-tabs { + flex-direction: column; + } + + .domain-mapping-tab { + width: 100%; + text-align: left; + border-bottom: 1px solid #ddd; + border-radius: 0; + } + + .domain-mapping-tab.active { + border-bottom: 1px solid #007cba; + border-left: 4px solid #007cba; + } + + /* Hide less important columns on mobile */ + .wp-list-table th.column-primary ~ th:not(.column-actions), + .wp-list-table td.column-primary ~ td:not(.column-actions) { display: none; } + .wp-list-table th.column-primary, - .wp-list-table td.column-primary { + .wp-list-table td.column-primary, + .wp-list-table th.column-actions, + .wp-list-table td.column-actions { display: table-cell; } -} \ No newline at end of file +} diff --git a/assets/js/admin.js b/assets/js/admin.js index 8dab280..e8af937 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -2,60 +2,72 @@ * WP Domain Mapping admin JavaScript */ jQuery(document).ready(function($) { - // Tab switching - $('.styles-tab').on('click', function() { - $('.styles-tab').removeClass('active'); - $(this).addClass('active'); - var tab = $(this).data('tab'); - $('.styles-section').hide(); - $('.styles-section[data-section="' + tab + '"]').show(); - }); + + // Helper function for showing notices + function showNotice(selector, message, type) { + $(selector) + .removeClass('notice-success notice-error notice-warning notice-info') + .addClass('notice-' + type) + .html('

' + message + '

') + .show() + .delay(5000) + .fadeOut(); + } // Domain form validation $('#edit-domain-form').on('submit', function(e) { - var domain = $('#domain').val(); - var blogId = $('#blog_id').val(); - + var domain = $('#domain').val().trim(); + var blogId = $('#blog_id').val().trim(); + if (!domain) { e.preventDefault(); - alert(wpDomainMapping.messages.domainRequired); + showNotice('#edit-domain-status', wpDomainMapping.messages.domainRequired, 'error'); $('#domain').focus(); return false; } - + if (!blogId) { e.preventDefault(); - alert(wpDomainMapping.messages.siteRequired); + showNotice('#edit-domain-status', wpDomainMapping.messages.siteRequired, 'error'); $('#blog_id').focus(); return false; } + + // Basic domain format validation + var domainPattern = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$/; + if (!domainPattern.test(domain)) { + e.preventDefault(); + showNotice('#edit-domain-status', 'Please enter a valid domain format (e.g., example.com)', 'error'); + $('#domain').focus(); + return false; + } }); - // Check all domains + // Check all domains functionality $('#select-all').on('change', function() { $('.domain-checkbox').prop('checked', this.checked); }); - // AJAX domain operations - function showNotice(selector, message, type) { - $(selector).removeClass('notice-success notice-error') - .addClass('notice-' + type) - .html('

' + message + '

') - .show() - .delay(3000) - .fadeOut(); - } + // Update select all when individual checkboxes change + $('.domain-checkbox').on('change', function() { + if (!this.checked) { + $('#select-all').prop('checked', false); + } else if ($('.domain-checkbox:checked').length === $('.domain-checkbox').length) { + $('#select-all').prop('checked', true); + } + }); - // Save domain + // Handle domain edit/add form submission via AJAX $('#edit-domain-form').on('submit', function(e) { e.preventDefault(); + var formData = $(this).serializeArray(); formData.push({name: 'action', value: 'dm_handle_actions'}); formData.push({name: 'action_type', value: 'save'}); formData.push({name: 'nonce', value: wpDomainMapping.nonce}); - $('#edit-domain-status').text(wpDomainMapping.messages.saving).show(); - + $('#edit-domain-status').html('

' + wpDomainMapping.messages.saving + '

').show(); + $.ajax({ url: wpDomainMapping.ajaxUrl, type: 'POST', @@ -63,7 +75,12 @@ jQuery(document).ready(function($) { success: function(response) { if (response.success) { showNotice('#edit-domain-status', response.data, 'success'); - setTimeout(function() { location.reload(); }, 1000); + setTimeout(function() { + // Redirect to domains page without edit parameter + var url = new URL(window.location); + url.searchParams.delete('edit_domain'); + window.location.href = url.toString(); + }, 1500); } else { showNotice('#edit-domain-status', response.data || wpDomainMapping.messages.error, 'error'); } @@ -74,61 +91,498 @@ jQuery(document).ready(function($) { }); }); - // Bulk actions + // Handle bulk domain actions $('#domain-list-form').on('submit', function(e) { e.preventDefault(); + var selectedDomains = []; $('.domain-checkbox:checked').each(function() { selectedDomains.push($(this).val()); }); - + if (selectedDomains.length === 0) { showNotice('#domain-status', wpDomainMapping.messages.noSelection, 'error'); return; } var action = $('#bulk-action-selector-top').val(); - if (action === '-1') return; + if (action === '-1') { + showNotice('#domain-status', 'Please select an action.', 'error'); + return; + } - if (confirm('Are you sure you want to delete the selected domains?')) { - $('#domain-status').text(wpDomainMapping.messages.processing).show(); - - $.ajax({ - url: wpDomainMapping.ajaxUrl, - type: 'POST', - data: { - action: 'dm_handle_actions', - action_type: 'delete', - domains: selectedDomains, - nonce: wpDomainMapping.nonce - }, - success: function(response) { - if (response.success) { - showNotice('#domain-status', response.data, 'success'); - setTimeout(function() { location.reload(); }, 1000); - } else { - showNotice('#domain-status', response.data || wpDomainMapping.messages.error, 'error'); - } - }, - error: function() { - showNotice('#domain-status', wpDomainMapping.messages.error, 'error'); + if (action === 'delete' && !confirm('Are you sure you want to delete the selected domains? This action cannot be undone.')) { + return; + } + + $('#domain-status').html('

' + wpDomainMapping.messages.processing + '

').show(); + + $.ajax({ + url: wpDomainMapping.ajaxUrl, + type: 'POST', + data: { + action: 'dm_handle_actions', + action_type: action, + domains: selectedDomains, + nonce: wpDomainMapping.nonce + }, + success: function(response) { + if (response.success) { + showNotice('#domain-status', response.data, 'success'); + setTimeout(function() { + location.reload(); + }, 1500); + } else { + showNotice('#domain-status', response.data || wpDomainMapping.messages.error, 'error'); } - }); + }, + error: function() { + showNotice('#domain-status', wpDomainMapping.messages.error, 'error'); + } + }); + }); + + // Single domain delete functionality + $('.domain-delete-button').on('click', function(e) { + e.preventDefault(); + + if (!confirm('Are you sure you want to delete this domain? This action cannot be undone.')) { + return; + } + + var domain = $(this).data('domain'); + var $row = $(this).closest('tr'); + + $.ajax({ + url: wpDomainMapping.ajaxUrl, + type: 'POST', + data: { + action: 'dm_handle_actions', + action_type: 'delete', + domain: domain, + nonce: wpDomainMapping.nonce + }, + success: function(response) { + if (response.success) { + $row.fadeOut(function() { + $(this).remove(); + }); + showNotice('#domain-status', response.data, 'success'); + } else { + showNotice('#domain-status', response.data || wpDomainMapping.messages.error, 'error'); + } + }, + error: function() { + showNotice('#domain-status', wpDomainMapping.messages.error, 'error'); + } + }); + }); + + // Tab switching functionality (for admin settings page) + $('.domain-mapping-tab').on('click', function() { + var tab = $(this).data('tab'); + + // Update active tab + $('.domain-mapping-tab').removeClass('active'); + $(this).addClass('active'); + + // Show corresponding content + $('.domain-mapping-section').hide(); + $('.domain-mapping-section[data-section="' + tab + '"]').show(); + + // Update URL without reloading + if (history.pushState) { + var url = new URL(window.location); + url.searchParams.set('tab', tab); + window.history.pushState({}, '', url); } }); - // Copy to clipboard functionality - $('.copy-to-clipboard').on('click', function() { - var text = $(this).data('text'); + // Auto-fill server IP if detected + if ($('#ipaddress').length && !$('#ipaddress').val()) { + // This would be populated server-side, but we can enhance it client-side if needed + } + + // Domain input formatting - remove protocols and trailing slashes + $('#domain').on('blur', function() { + var domain = $(this).val().trim(); + // Remove http:// or https:// + domain = domain.replace(/^https?:\/\//, ''); + // Remove trailing slash + domain = domain.replace(/\/$/, ''); + // Convert to lowercase + domain = domain.toLowerCase(); + $(this).val(domain); + }); + + // Copy to clipboard functionality for DNS instructions + $('.copy-to-clipboard').on('click', function(e) { + e.preventDefault(); + + var text = $(this).data('text') || $(this).prev('code').text(); + + // Create temporary input var tempInput = $(''); $('body').append(tempInput); tempInput.val(text).select(); - document.execCommand('copy'); + + try { + document.execCommand('copy'); + var $btn = $(this); + var originalText = $btn.text(); + $btn.text('Copied!').addClass('copied'); + + setTimeout(function() { + $btn.text(originalText).removeClass('copied'); + }, 2000); + } catch (err) { + console.log('Copy failed'); + } + tempInput.remove(); - - var $btn = $(this); - var originalText = $btn.text(); - $btn.text('Copied!'); - setTimeout(function() { $btn.text(originalText); }, 2000); }); -}); \ No newline at end of file + + // Form validation enhancement + $('form').on('submit', function() { + var $submitBtn = $(this).find('input[type="submit"], button[type="submit"]'); + var originalText = $submitBtn.val() || $submitBtn.text(); + + // Disable submit button to prevent double submission + $submitBtn.prop('disabled', true); + + // Re-enable after a delay + setTimeout(function() { + $submitBtn.prop('disabled', false); + }, 3000); + }); + + // Enhanced table row highlighting + $('.wp-list-table tbody tr').hover( + function() { + $(this).addClass('hover'); + }, + function() { + $(this).removeClass('hover'); + } + ); + + // Auto-hide notices after delay + $('.notice.is-dismissible').delay(8000).fadeOut(); + + // Confirmation for destructive actions + $('a[href*="action=delete"], .delete a').on('click', function(e) { + if (!confirm('Are you sure you want to delete this item? This action cannot be undone.')) { + e.preventDefault(); + return false; + } + }); + + // Enhanced search functionality + $('#domain-filter-form input[type="text"]').on('keypress', function(e) { + if (e.which === 13) { // Enter key + $(this).closest('form').submit(); + } + }); + + // Tooltips for status indicators + if ($.fn.tooltip) { + $('.dashicons[title]').tooltip(); + } + + // Progress indicator for long-running operations + function showProgress(message) { + var $progress = $('
' + + '

' + message + '

' + + '
' + + '
' + + '
' + + '
'); + + $('body').append($progress); + return $progress; + } + + function hideProgress($progress) { + if ($progress) { + $progress.fadeOut(function() { + $(this).remove(); + }); + } + } + + // Enhanced error handling + $(document).ajaxError(function(event, xhr, settings, thrownError) { + if (xhr.status === 403) { + showNotice('.wrap', 'Permission denied. Please refresh the page and try again.', 'error'); + } else if (xhr.status === 500) { + showNotice('.wrap', 'Server error occurred. Please try again later.', 'error'); + } + }); + + // Initialize any existing tab from URL + if (typeof URLSearchParams !== 'undefined') { + var urlParams = new URLSearchParams(window.location.search); + var currentTab = urlParams.get('tab'); + if (currentTab && $('.domain-mapping-tab[data-tab="' + currentTab + '"]').length) { + $('.domain-mapping-tab[data-tab="' + currentTab + '"]').click(); + } + } + + // Smooth scrolling for anchor links + $('a[href^="#"]').on('click', function(e) { + var target = $(this.getAttribute('href')); + if (target.length) { + e.preventDefault(); + $('html, body').stop().animate({ + scrollTop: target.offset().top - 100 + }, 500); + } + }); + + // Auto-refresh functionality for health checks + var autoRefreshInterval; + + function startAutoRefresh() { + if (autoRefreshInterval) { + clearInterval(autoRefreshInterval); + } + + // Only auto-refresh on health tab + if ($('.domain-mapping-section[data-section="health"]').is(':visible')) { + autoRefreshInterval = setInterval(function() { + // Check if any health checks are running + if ($('.check-domain-health:disabled').length === 0) { + // Optionally auto-refresh health status every 5 minutes + // location.reload(); + } + }, 300000); // 5 minutes + } + } + + function stopAutoRefresh() { + if (autoRefreshInterval) { + clearInterval(autoRefreshInterval); + } + } + + // Start auto-refresh when health tab is active + $('.domain-mapping-tab[data-tab="health"]').on('click', startAutoRefresh); + + // Stop auto-refresh when leaving health tab + $('.domain-mapping-tab:not([data-tab="health"])').on('click', stopAutoRefresh); + + // Initialize auto-refresh if health tab is already active + if ($('.domain-mapping-tab[data-tab="health"]').hasClass('active')) { + startAutoRefresh(); + } + + // Clean up on page unload + $(window).on('beforeunload', function() { + stopAutoRefresh(); + }); + + // Domain tabs switching + $('.domain-mapping-tab').on('click', function() { + $('.domain-mapping-tab').removeClass('active'); + $(this).addClass('active'); + + var tab = $(this).data('tab'); + $('.domain-mapping-section').hide(); + $('.domain-mapping-section[data-section="' + tab + '"]').show(); + }); + + // Health check single domain + $(document).on('click', '.check-domain-health', function() { + var $button = $(this); + var domain = $button.data('domain'); + var originalText = $button.text(); + + $button.prop('disabled', true).text('Checking...'); + + $.ajax({ + url: ajaxurl, + type: 'POST', + data: { + action: 'dm_check_domain_health', + domain: domain, + nonce: $('input[name="dm_manual_health_check_nonce"]').val() || wpDomainMapping.nonce + }, + success: function(response) { + if (response.success) { + // Refresh page to show updated results + location.reload(); + } else { + alert(response.data || 'An error occurred during the health check.'); + $button.prop('disabled', false).text(originalText); + } + }, + error: function() { + alert('An error occurred during the health check.'); + $button.prop('disabled', false).text(originalText); + } + }); + }); + + // Import form handling + $('#domain-mapping-import-form').on('submit', function(e) { + e.preventDefault(); + + var formData = new FormData(this); + formData.append('action', 'dm_import_csv'); + + // Show progress bar + $('#import-progress').show(); + $('#import-results').hide(); + + $.ajax({ + url: ajaxurl, + type: 'POST', + data: formData, + dataType: 'json', + contentType: false, + processData: false, + success: function(response) { + $('#import-progress').hide(); + $('#import-results').show(); + + if (response.success) { + $('.import-summary').html( + '

' + + response.data.message + + '

' + ); + + if (response.data.details && response.data.details.length > 0) { + var details = '' + + '' + + '' + + '' + + ''; + + $.each(response.data.details, function(i, item) { + var statusClass = ''; + if (item.status === 'error') { + statusClass = 'style="background-color: #fef0f0;"'; + } else if (item.status === 'warning') { + statusClass = 'style="background-color: #fff8e5;"'; + } else if (item.status === 'success') { + statusClass = 'style="background-color: #f0f9eb;"'; + } + + details += '' + + '' + + '' + + ''; + }); + + details += '
StatusDetails
' + item.status.toUpperCase() + '' + item.message + '
'; + $('.import-details').html(details); + } + } else { + $('.import-summary').html( + '

' + + (response.data || 'Import failed.') + + '

' + ); + } + }, + error: function() { + $('#import-progress').hide(); + $('#import-results').show(); + $('.import-summary').html( + '

' + + 'An error occurred during import.' + + '

' + ); + }, + xhr: function() { + var xhr = new window.XMLHttpRequest(); + + xhr.upload.addEventListener('progress', function(evt) { + if (evt.lengthComputable) { + var percentComplete = (evt.loaded / evt.total) * 100; + $('.progress-bar-inner').css('width', percentComplete + '%'); + $('.progress-text').text(Math.round(percentComplete) + '%'); + } + }, false); + + return xhr; + } + }); + }); + + // Enhanced domain validation + $('#domain').on('input', function() { + var domain = $(this).val(); + var $feedback = $('#domain-feedback'); + + if (!$feedback.length) { + $(this).after('
'); + $feedback = $('#domain-feedback'); + } + + if (domain.length > 0) { + // Check for common issues + if (domain.indexOf('http') === 0) { + $feedback.html('Please remove http:// or https://').show(); + } else if (domain.indexOf('/') !== -1) { + $feedback.html('Please remove any paths or slashes').show(); + } else if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$/.test(domain)) { + $feedback.html('Please enter a valid domain format').show(); + } else { + $feedback.html('Domain format looks good').show(); + } + } else { + $feedback.hide(); + } + }); + + // Keyboard shortcuts + $(document).on('keydown', function(e) { + // Ctrl/Cmd + S to save forms + if ((e.ctrlKey || e.metaKey) && e.which === 83) { + var $activeForm = $('form:visible').first(); + if ($activeForm.length) { + e.preventDefault(); + $activeForm.submit(); + } + } + + // Escape key to close modals or cancel actions + if (e.which === 27) { + $('.notice.is-dismissible').fadeOut(); + } + }); + + // Real-time search for domain filter + var searchTimeout; + $('#domain-filter-form input[name="s"]').on('input', function() { + clearTimeout(searchTimeout); + var searchTerm = $(this).val(); + + searchTimeout = setTimeout(function() { + if (searchTerm.length > 2 || searchTerm.length === 0) { + // Auto-submit form after delay + $('#domain-filter-form').submit(); + } + }, 1000); + }); + + // Initialize page based on current state + function initializePage() { + // Check if we're on the domains page and have messages to show + if (window.location.href.indexOf('page=domains') > -1) { + if (window.location.href.indexOf('updated=add') > -1) { + showNotice('#domain-status', 'Domain added successfully.', 'success'); + } else if (window.location.href.indexOf('updated=del') > -1) { + showNotice('#domain-status', 'Domain deleted successfully.', 'success'); + } + } + + // Auto-focus first input field + $('input[type="text"]:visible:first').focus(); + } + + // Initialize when document is ready + initializePage(); +}); diff --git a/includes/class-admin.php b/includes/class-admin.php new file mode 100644 index 0000000..a8c1510 --- /dev/null +++ b/includes/class-admin.php @@ -0,0 +1,740 @@ +tables = dm_get_table_names(); + $this->init(); + } + + /** + * Initialize admin functionality + */ + private function init() { + // Setup admin-related hooks + $this->setup_admin_hooks(); + + // Load admin pages + require_once WP_DOMAIN_MAPPING_DIR_PATH . 'admin/pages.php'; + } + + /** + * Setup admin hooks + */ + private function setup_admin_hooks() { + // Add menu pages + add_action( 'admin_menu', array( $this, 'add_admin_pages' ) ); + add_action( 'network_admin_menu', array( $this, 'add_network_admin_pages' ) ); + + // Add AJAX handlers + add_action( 'wp_ajax_dm_handle_actions', array( $this, 'ajax_handle_actions' ) ); + + // Add domain link to sites list + add_filter( 'manage_sites_action_links', array( $this, 'add_domain_link_to_sites' ), 10, 2 ); + + // Add custom columns + add_filter( 'wpmu_blogs_columns', array( $this, 'add_domain_mapping_columns' ) ); + add_action( 'manage_blogs_custom_column', array( $this, 'display_domain_mapping_column' ), 1, 3 ); + add_action( 'manage_sites_custom_column', array( $this, 'display_domain_mapping_column' ), 1, 3 ); + + // Handle user domain mapping actions + if ( isset( $_GET['page'] ) && 'domainmapping' === $_GET['page'] ) { + add_action( 'admin_init', array( $this, 'handle_user_domain_actions' ) ); + } + + // Enqueue admin scripts and styles + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); + } + + /** + * Add admin menu pages + */ + public function add_admin_pages() { + global $current_site, $wpdb, $wp_db_version; + + if ( ! isset( $current_site ) && $wp_db_version >= 15260 ) { + add_action( 'admin_notices', array( $this, 'domain_mapping_warning' ) ); + return false; + } + + if ( isset($current_site->path) && $current_site->path != "/" ) { + wp_die( esc_html__( "The domain mapping plugin only works if the site is installed in /. This is a limitation of how virtual servers work and is very difficult to work around.", 'wp-domain-mapping' ) ); + } + + if ( get_site_option( 'dm_user_settings' ) && $current_site->blog_id != $wpdb->blogid && ! $this->sunrise_warning( false ) ) { + add_management_page( + __( 'Domain Mapping', 'wp-domain-mapping' ), + __( 'Domain Mapping', 'wp-domain-mapping' ), + 'manage_options', + 'domainmapping', + array( $this, 'render_manage_page' ) + ); + } + } + + /** + * Add network admin menu pages + */ + public function add_network_admin_pages() { + add_submenu_page( + 'settings.php', + __( 'Domain Mapping', 'wp-domain-mapping' ), + __( 'Domain Mapping', 'wp-domain-mapping' ), + 'manage_network', + 'domain-mapping', + array( $this, 'render_admin_page' ) + ); + + add_submenu_page( + 'sites.php', + __( 'Domains', 'wp-domain-mapping' ), + __( 'Domains', 'wp-domain-mapping' ), + 'manage_network', + 'domains', + array( $this, 'render_domains_admin' ) + ); + } + + /** + * Enqueue admin scripts and styles + */ + public function enqueue_admin_assets( $hook ) { + // Only load on our admin pages + if ( ! in_array( $hook, array( 'sites_page_domains', 'settings_page_domain-mapping', 'tools_page_domainmapping' ) ) ) { + return; + } + + wp_enqueue_style( + 'wp-domain-mapping-admin', + WP_DOMAIN_MAPPING_DIR_URL . 'assets/css/admin.css', + array(), + WP_DOMAIN_MAPPING_VERSION + ); + + wp_enqueue_script( + 'wp-domain-mapping-admin', + WP_DOMAIN_MAPPING_DIR_URL . 'assets/js/admin.js', + array( 'jquery' ), + WP_DOMAIN_MAPPING_VERSION, + true + ); + + wp_localize_script( 'wp-domain-mapping-admin', 'wpDomainMapping', array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'domain_mapping' ), + 'messages' => array( + 'domainRequired' => __( 'Domain is required.', 'wp-domain-mapping' ), + 'siteRequired' => __( 'Site ID is required.', 'wp-domain-mapping' ), + 'saving' => __( 'Saving...', 'wp-domain-mapping' ), + 'processing' => __( 'Processing...', 'wp-domain-mapping' ), + 'error' => __( 'An error occurred.', 'wp-domain-mapping' ), + 'noSelection' => __( 'Please select at least one domain.', 'wp-domain-mapping' ), + ) + )); + } + + /** + * Check if sunrise.php is properly configured + * + * @param bool $die Whether to die with an error message + * @return bool True if there's a problem, false if everything is okay + */ + public function sunrise_warning( $die = true ) { + if ( ! file_exists( WP_CONTENT_DIR . '/sunrise.php' ) ) { + if ( ! $die ) return true; + if ( dm_is_site_admin() ) { + wp_die( + sprintf( + /* translators: %1$s: Content directory, %2$s: WordPress install path */ + esc_html__( 'Please copy sunrise.php to %1$s/sunrise.php and ensure the SUNRISE definition is in %2$swp-config.php', 'wp-domain-mapping' ), + esc_html( WP_CONTENT_DIR ), + esc_html( ABSPATH ) + ) + ); + } else { + wp_die( esc_html__( "This plugin has not been configured correctly yet.", 'wp-domain-mapping' ) ); + } + } elseif ( ! defined( 'SUNRISE' ) ) { + if ( ! $die ) return true; + if ( dm_is_site_admin() ) { + wp_die( + sprintf( + /* translators: %s: WordPress install path */ + esc_html__( 'Please uncomment the line 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 ( dm_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; + } + + /** + * 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' + ) . '

'; + } + + /** + * AJAX handler for domain actions + */ + public function ajax_handle_actions() { + check_ajax_referer( 'domain_mapping', 'nonce' ); + + if ( ! current_user_can( 'manage_network' ) ) { + wp_send_json_error( __( 'Permission denied.', 'wp-domain-mapping' ) ); + } + + global $wpdb; + + $action = sanitize_text_field( $_POST['action_type'] ); + $domain = dm_clean_domain( sanitize_text_field( isset( $_POST['domain'] ) ? strtolower( $_POST['domain'] ) : '' ) ); + $blog_id = isset( $_POST['blog_id'] ) ? absint( $_POST['blog_id'] ) : 0; + $active = isset( $_POST['active'] ) ? absint( $_POST['active'] ) : 0; + $orig_domain = isset( $_POST['orig_domain'] ) ? dm_clean_domain( sanitize_text_field( $_POST['orig_domain'] ) ) : ''; + $current_user_id = get_current_user_id(); + + switch ( $action ) { + case 'save': + if ( $blog_id != 0 && $blog_id != 1 ) { + // Validate domain format + if ( ! dm_validate_domain( $domain ) ) { + wp_send_json_error( __( 'Invalid domain format.', 'wp-domain-mapping' ) ); + } + + // Check if domain exists for another blog + $exists = $wpdb->get_var( $wpdb->prepare( + "SELECT domain FROM {$this->tables['domains']} WHERE blog_id != %d AND domain = %s", + $blog_id, $domain + )); + + if ( null == $exists ) { + $wpdb->query( 'START TRANSACTION' ); + + try { + if ( empty( $orig_domain ) ) { + // Insert new domain + $success = $wpdb->insert( + $this->tables['domains'], + array( + 'blog_id' => $blog_id, + 'domain' => $domain, + 'active' => $active + ), + array( '%d', '%s', '%d' ) + ); + + if ( $success ) { + // Log the action + dm_log_action( 'add', $domain, $blog_id, $current_user_id ); + + $wpdb->query( 'COMMIT' ); + wp_send_json_success( __( 'Domain added successfully.', 'wp-domain-mapping' ) ); + } else { + $wpdb->query( 'ROLLBACK' ); + wp_send_json_error( __( 'Failed to add domain.', 'wp-domain-mapping' ) ); + } + } else { + // Update existing domain + $success = $wpdb->update( + $this->tables['domains'], + array( + 'blog_id' => $blog_id, + 'domain' => $domain, + 'active' => $active + ), + array( 'domain' => $orig_domain ), + array( '%d', '%s', '%d' ), + array( '%s' ) + ); + + if ( $success !== false ) { + // Log the action + dm_log_action( 'edit', $domain, $blog_id, $current_user_id ); + + $wpdb->query( 'COMMIT' ); + wp_send_json_success( __( 'Domain updated successfully.', 'wp-domain-mapping' ) ); + } else { + $wpdb->query( 'ROLLBACK' ); + wp_send_json_error( __( 'No changes were made or update failed.', 'wp-domain-mapping' ) ); + } + } + } catch ( Exception $e ) { + $wpdb->query( 'ROLLBACK' ); + wp_send_json_error( __( 'An error occurred while saving domain.', 'wp-domain-mapping' ) ); + } + } else { + wp_send_json_error( __( 'Domain already exists for another site.', 'wp-domain-mapping' ) ); + } + } else { + wp_send_json_error( __( 'Invalid site ID.', 'wp-domain-mapping' ) ); + } + break; + + case 'delete': + $domains = isset( $_POST['domains'] ) ? array_map( 'sanitize_text_field', (array) $_POST['domains'] ) : array( $domain ); + + $wpdb->query( 'START TRANSACTION' ); + $deleted = 0; + + try { + foreach ( $domains as $del_domain ) { + if ( empty( $del_domain ) ) continue; + + // Get blog_id before deletion for logging + $affected_blog_id = $wpdb->get_var( $wpdb->prepare( + "SELECT blog_id FROM {$this->tables['domains']} WHERE domain = %s", + $del_domain + )); + + if ( $affected_blog_id ) { + // Delete the domain + $result = $wpdb->delete( + $this->tables['domains'], + array( 'domain' => $del_domain ), + array( '%s' ) + ); + + if ( $result ) { + $deleted++; + + // Log the action + dm_log_action( 'delete', $del_domain, $affected_blog_id, $current_user_id ); + } + } + } + + if ( $deleted > 0 ) { + $wpdb->query( 'COMMIT' ); + $message = sprintf( + _n( + 'Domain deleted successfully.', + '%d domains deleted successfully.', + $deleted, + 'wp-domain-mapping' + ), + $deleted + ); + wp_send_json_success( $message ); + } else { + $wpdb->query( 'ROLLBACK' ); + wp_send_json_error( __( 'No domains were deleted.', 'wp-domain-mapping' ) ); + } + } catch ( Exception $e ) { + $wpdb->query( 'ROLLBACK' ); + wp_send_json_error( __( 'An error occurred while deleting domains.', 'wp-domain-mapping' ) ); + } + break; + + default: + wp_send_json_error( __( 'Invalid action.', 'wp-domain-mapping' ) ); + } + } + + /** + * Handle user domain mapping actions + */ + public function handle_user_domain_actions() { + global $wpdb, $parent_file; + + $url = add_query_arg( array( 'page' => 'domainmapping' ), admin_url( $parent_file ) ); + + if ( ! empty( $_POST['action'] ) ) { + $domain = isset( $_POST['domain'] ) ? sanitize_text_field( $_POST['domain'] ) : ''; + + if ( empty( $domain ) ) { + wp_die( esc_html__( "You must enter a domain", 'wp-domain-mapping' ) ); + } + + check_admin_referer( 'domain_mapping' ); + + do_action( 'dm_handle_actions_init', $domain ); + + switch ( $_POST['action'] ) { + case "add": + do_action( 'dm_handle_actions_add', $domain ); + + // Validate domain format + if ( ! dm_validate_domain( $domain ) ) { + wp_die( esc_html__( "Invalid domain format", 'wp-domain-mapping' ) ); + } + + // Check if domain already exists + $domain_exists = $wpdb->get_row( $wpdb->prepare( + "SELECT blog_id FROM {$wpdb->blogs} WHERE domain = %s OR (SELECT blog_id FROM {$this->tables['domains']} WHERE domain = %s)", + $domain, $domain + )); + + if ( null == $domain_exists ) { + // If primary, reset other domains to not primary + if ( isset( $_POST['primary'] ) && $_POST['primary'] ) { + $wpdb->update( + $this->tables['domains'], + array( 'active' => 0 ), + array( 'blog_id' => $wpdb->blogid ), + array( '%d' ), + array( '%d' ) + ); + } + + // Insert new domain + $wpdb->insert( + $this->tables['domains'], + array( + 'blog_id' => $wpdb->blogid, + 'domain' => $domain, + 'active' => isset( $_POST['primary'] ) ? 1 : 0 + ), + array( '%d', '%s', '%d' ) + ); + + wp_redirect( add_query_arg( array( 'updated' => 'add' ), $url ) ); + exit; + } else { + wp_redirect( add_query_arg( array( 'updated' => 'exists' ), $url ) ); + exit; + } + break; + + case "primary": + do_action( 'dm_handle_actions_primary', $domain ); + + // Reset all domains to not primary + $wpdb->update( + $this->tables['domains'], + array( 'active' => 0 ), + array( 'blog_id' => $wpdb->blogid ), + array( '%d' ), + array( '%d' ) + ); + + // Check if domain is not the original domain + $core = WP_Domain_Mapping_Core::get_instance(); + $orig_url = parse_url( $core->get_original_url( 'siteurl' ) ); + + if ( $domain != $orig_url['host'] ) { + // Set the selected domain as primary + $wpdb->update( + $this->tables['domains'], + array( 'active' => 1 ), + array( + 'domain' => $domain, + 'blog_id' => $wpdb->blogid + ), + array( '%d' ), + array( '%s', '%d' ) + ); + } + + wp_redirect( add_query_arg( array( 'updated' => 'primary' ), $url ) ); + exit; + break; + } + } elseif ( isset( $_GET['action'] ) && $_GET['action'] == 'delete' ) { + $domain = sanitize_text_field( $_GET['domain'] ); + + if ( empty( $domain ) ) { + wp_die( esc_html__( "You must enter a domain", 'wp-domain-mapping' ) ); + } + + check_admin_referer( "delete" . $_GET['domain'] ); + + do_action( 'dm_handle_actions_del', $domain ); + + // Delete the domain + $wpdb->delete( + $this->tables['domains'], + array( 'domain' => $domain ), + array( '%s' ) + ); + + wp_redirect( add_query_arg( array( 'updated' => 'del' ), $url ) ); + exit; + } + } + + /** + * Render domains admin page + */ + public function render_domains_admin() { + if ( ! dm_is_site_admin() ) { + return false; + } + + $this->sunrise_warning(); + + // Include the admin page template + dm_render_domains_page(); + } + + /** + * Render admin configuration page + */ + public function render_admin_page() { + if ( ! dm_is_site_admin() ) { + return false; + } + + $this->sunrise_warning(); + + global $current_site; + if ( isset($current_site->path) && $current_site->path != "/" ) { + wp_die( sprintf( + esc_html__( "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' + ); + } + + // Include the admin page template + dm_render_admin_page(); + } + + /** + * Render user management page + */ + public function render_manage_page() { + global $wpdb, $parent_file; + + if ( isset( $_GET['updated'] ) ) { + do_action( 'dm_echo_updated_msg' ); + } + + $this->sunrise_warning(); + + if ( ! get_site_option( 'dm_ipaddress' ) && ! get_site_option( 'dm_cname' ) ) { + if ( dm_is_site_admin() ) { + echo wp_kses( + __( "Please set the IP address or CNAME of your server in the 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 = dm_get_domains_by_blog_id( $wpdb->blogid ); + + // Include the user page template + dm_render_user_page( $protocol, $domains ); + } + + /** + * Add "Domains" link to sites list + * + * @param array $actions The row actions + * @param int $blog_id The blog ID + * @return array Modified actions + */ + public function add_domain_link_to_sites( $actions, $blog_id ) { + $domains_url = add_query_arg( + array( 'page' => 'domains', 'blog_id' => $blog_id ), + network_admin_url( 'sites.php' ) + ); + + $actions['domains'] = '' . + 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 ) . '
'; + } + } + } + } + + /** + * 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 default updated messages action +add_action( 'dm_echo_updated_msg', array( WP_Domain_Mapping_Admin::class, 'echo_default_updated_msg' ) ); diff --git a/includes/class-core.php b/includes/class-core.php new file mode 100644 index 0000000..10e1c4e --- /dev/null +++ b/includes/class-core.php @@ -0,0 +1,593 @@ +tables = dm_get_table_names(); + $this->init(); + } + + /** + * Initialize core functionality + */ + private function init() { + // Setup general hooks + $this->setup_general_hooks(); + + // Setup domain mapping filters if DOMAIN_MAPPING is defined + if ( defined( 'DOMAIN_MAPPING' ) ) { + $this->setup_domain_mapping_filters(); + } else { + add_filter( 'admin_url', array( $this, 'domain_mapping_adminurl' ), 10, 3 ); + } + } + + /** + * Setup general hooks + */ + private function setup_general_hooks() { + // Delete domain mappings when a blog is deleted + add_action( 'delete_blog', array( $this, 'delete_blog_domain_mapping' ), 1, 2 ); + + // Handle domain redirection + add_action( 'template_redirect', array( $this, 'redirect_to_mapped_domain' ) ); + + // Handle admin area redirection + add_action( 'admin_init', array( $this, 'redirect_admin' ) ); + + // Handle remote login + if ( isset( $_GET['dm'] ) ) { + add_action( 'template_redirect', array( $this, 'remote_login_js' ) ); + } + } + + /** + * Setup domain mapping filters when DOMAIN_MAPPING is defined + */ + private function setup_domain_mapping_filters() { + add_filter( 'plugins_url', array( $this, 'domain_mapping_plugins_uri' ), 1 ); + add_filter( 'theme_root_uri', array( $this, 'domain_mapping_themes_uri' ), 1 ); + add_filter( 'pre_option_siteurl', array( $this, 'domain_mapping_siteurl' ) ); + add_filter( 'pre_option_home', array( $this, 'domain_mapping_siteurl' ) ); + add_filter( 'the_content', array( $this, 'domain_mapping_post_content' ) ); + add_action( 'wp_head', array( $this, 'remote_login_js_loader' ) ); + add_action( 'login_head', array( $this, 'redirect_login_to_orig' ) ); + add_action( 'wp_logout', array( $this, 'remote_logout_loader' ), 9999 ); + + add_filter( 'stylesheet_uri', array( $this, 'domain_mapping_post_content' ) ); + add_filter( 'stylesheet_directory', array( $this, 'domain_mapping_post_content' ) ); + add_filter( 'stylesheet_directory_uri', array( $this, 'domain_mapping_post_content' ) ); + add_filter( 'template_directory', array( $this, 'domain_mapping_post_content' ) ); + add_filter( 'template_directory_uri', array( $this, 'domain_mapping_post_content' ) ); + add_filter( 'plugins_url', array( $this, 'domain_mapping_post_content' ) ); + } + + /** + * Create required database tables + */ + public function create_tables() { + global $wpdb; + + // Only network admins can create tables + if ( ! dm_is_site_admin() ) { + return; + } + + // Initialize remote login hash + $this->get_hash(); + + // Use static variable to prevent repeated execution + static $tables_created = false; + if ( $tables_created ) { + return; + } + + $created = 0; + $charset_collate = $wpdb->get_charset_collate(); + + // Create domain_mapping table + if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->tables['domains']}'" ) != $this->tables['domains'] ) { + $wpdb->query( "CREATE TABLE IF NOT EXISTS `{$this->tables['domains']}` ( + `id` bigint(20) NOT NULL auto_increment, + `blog_id` bigint(20) NOT NULL, + `domain` varchar(255) NOT NULL, + `active` tinyint(4) default '1', + PRIMARY KEY (`id`), + KEY `blog_id` (`blog_id`,`domain`,`active`) + ) $charset_collate;" ); + $created = 1; + } + + // Create domain_mapping_logins table + if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->tables['logins']}'" ) != $this->tables['logins'] ) { + $wpdb->query( "CREATE TABLE IF NOT EXISTS `{$this->tables['logins']}` ( + `id` varchar(32) NOT NULL, + `user_id` bigint(20) NOT NULL, + `blog_id` bigint(20) NOT NULL, + `t` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) + ) $charset_collate;" ); + $created = 1; + } + + // Create domain_mapping_logs table + if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->tables['logs']}'" ) != $this->tables['logs'] ) { + $wpdb->query( "CREATE TABLE IF NOT EXISTS `{$this->tables['logs']}` ( + `id` bigint(20) NOT NULL auto_increment, + `user_id` bigint(20) NOT NULL, + `action` varchar(50) NOT NULL, + `domain` varchar(255) NOT NULL, + `blog_id` bigint(20) NOT NULL, + `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) + ) $charset_collate;" ); + $created = 1; + } + + // If any table was created, show success message and mark as created + if ( $created ) { + add_action( 'admin_notices', function() { + echo '

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

'; + }); + $tables_created = true; + } + } + + /** + * Delete blog domain mappings when a blog is deleted + * + * @param int $blog_id The blog ID + * @param bool $drop Whether to drop tables + */ + public function delete_blog_domain_mapping( $blog_id, $drop ) { + global $wpdb; + + if ( $blog_id && $drop ) { + $domains = $wpdb->get_col( $wpdb->prepare( + "SELECT domain FROM {$this->tables['domains']} WHERE blog_id = %d", + $blog_id + )); + + do_action( 'dm_delete_blog_domain_mappings', $domains ); + + $wpdb->delete( + $this->tables['domains'], + array( 'blog_id' => $blog_id ), + array( '%d' ) + ); + } + } + + /** + * Redirect to mapped domain + */ + public function redirect_to_mapped_domain() { + global $current_blog, $wpdb; + + if ( is_main_site() || ( isset( $_GET['preview'] ) && $_GET['preview'] == 'true' ) || + ( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && $_POST['customize'] == 'on' ) ) { + return; + } + + $protocol = is_ssl() ? 'https://' : 'http://'; + $url = $this->domain_mapping_siteurl( false ); + + if ( $url && $url != untrailingslashit( $protocol . $current_blog->domain . $current_blog->path ) ) { + $redirect = get_site_option( 'dm_301_redirect' ) ? '301' : '302'; + + if ( ( defined( 'VHOST' ) && constant("VHOST") != 'yes' ) || + ( defined( 'SUBDOMAIN_INSTALL' ) && constant( 'SUBDOMAIN_INSTALL' ) == false ) ) { + $_SERVER['REQUEST_URI'] = str_replace( $current_blog->path, '/', $_SERVER['REQUEST_URI'] ); + } + + wp_redirect( $url . $_SERVER['REQUEST_URI'], $redirect ); + exit; + } + } + + /** + * Redirect admin area if needed + */ + public function redirect_admin() { + if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin/admin-ajax.php' ) !== false ) { + return; + } + + if ( get_site_option( 'dm_redirect_admin' ) ) { + $url = $this->get_original_url( 'siteurl' ); + + if ( false === strpos( $url, $_SERVER['HTTP_HOST'] ) ) { + wp_redirect( untrailingslashit( $url ) . $_SERVER['REQUEST_URI'] ); + exit; + } + } else { + global $current_blog; + $url = $this->domain_mapping_siteurl( false ); + $request_uri = str_replace( $current_blog->path, '/', $_SERVER['REQUEST_URI'] ); + + if ( false === strpos( $url, $_SERVER['HTTP_HOST'] ) ) { + wp_redirect( str_replace( '//wp-admin', '/wp-admin', trailingslashit( $url ) . $request_uri ) ); + exit; + } + } + } + + /** + * Map domain for site URL + * + * @param string $setting The setting value + * @return string The mapped URL + */ + public function domain_mapping_siteurl( $setting ) { + global $wpdb, $current_blog; + static $return_url = array(); + + // Check if already cached + if ( isset( $return_url[$wpdb->blogid] ) ) { + if ( $return_url[$wpdb->blogid] !== false ) { + return $return_url[$wpdb->blogid]; + } + return $setting; + } + + $s = $wpdb->suppress_errors(); + + // Handle primary domain checking based on settings + if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) { + if ( isset( $_SERVER['HTTP_HOST'] ) ) { + $domain = $wpdb->get_var( $wpdb->prepare( + "SELECT domain FROM {$this->tables['domains']} WHERE blog_id = %d AND domain = %s LIMIT 1", + $wpdb->blogid, $_SERVER['HTTP_HOST'] + )); + + if ( null == $domain ) { + $return_url[$wpdb->blogid] = untrailingslashit( $this->get_original_url( "siteurl" ) ); + return $return_url[$wpdb->blogid]; + } + } + } else { + $domain = $wpdb->get_var( $wpdb->prepare( + "SELECT domain FROM {$this->tables['domains']} WHERE blog_id = %d AND active = 1 LIMIT 1", + $wpdb->blogid + )); + + if ( null == $domain ) { + $return_url[$wpdb->blogid] = untrailingslashit( $this->get_original_url( "siteurl" ) ); + return $return_url[$wpdb->blogid]; + } + } + + $wpdb->suppress_errors( $s ); + + $protocol = is_ssl() ? 'https://' : 'http://'; + + if ( $domain ) { + $return_url[$wpdb->blogid] = untrailingslashit( $protocol . $domain ); + $setting = $return_url[$wpdb->blogid]; + } else { + $return_url[$wpdb->blogid] = false; + } + + return $setting; + } + + /** + * Get the original URL for a site + * + * @param string $url_type The URL type (siteurl or home) + * @param int $blog_id The blog ID + * @return string The original URL + */ + public function get_original_url( $url_type, $blog_id = 0 ) { + global $wpdb; + $id = $blog_id ?: $wpdb->blogid; + static $orig_urls = array(); + + if ( ! isset( $orig_urls[$id] ) ) { + // Remove filter to avoid infinite loop + if ( defined( 'DOMAIN_MAPPING' ) ) { + remove_filter( 'pre_option_' . $url_type, array( $this, 'domain_mapping_siteurl' ) ); + } + + $orig_url = $blog_id == 0 ? get_option( $url_type ) : get_blog_option( $blog_id, $url_type ); + $orig_url = is_ssl() ? str_replace( "http://", "https://", $orig_url ) : str_replace( "https://", "http://", $orig_url ); + $orig_urls[$id] = $orig_url; + + // Restore filter + if ( defined( 'DOMAIN_MAPPING' ) ) { + add_filter( 'pre_option_' . $url_type, array( $this, 'domain_mapping_siteurl' ) ); + } + } + + return $orig_urls[$id]; + } + + /** + * Map domain for admin URL + * + * @param string $url The URL + * @param string $path The path + * @param int $blog_id The blog ID + * @return string The mapped URL + */ + public function domain_mapping_adminurl( $url, $path, $blog_id = 0 ) { + $index = strpos( $url, '/wp-admin' ); + + if ( $index !== false ) { + $url = $this->get_original_url( 'siteurl', $blog_id ) . substr( $url, $index ); + + if ( ( is_ssl() || force_ssl_admin() ) && 0 === strpos( $url, 'http://' ) ) { + $url = 'https://' . substr( $url, 7 ); + } + } + + return $url; + } + + /** + * Map domains in post content + * + * @param string $post_content The post content + * @return string The mapped content + */ + public function domain_mapping_post_content( $post_content ) { + $orig_url = $this->get_original_url( 'siteurl' ); + $url = $this->domain_mapping_siteurl( 'NA' ); + + if ( $url == 'NA' ) { + return $post_content; + } + + return str_replace( $orig_url, $url, $post_content ); + } + + /** + * Map domains in plugin URLs + * + * @param string $full_url The full URL + * @param string $path The path + * @param string $plugin The plugin + * @return string The mapped URL + */ + public function domain_mapping_plugins_uri( $full_url, $path = null, $plugin = null ) { + return get_option( 'siteurl' ) . substr( $full_url, stripos( $full_url, PLUGINDIR ) - 1 ); + } + + /** + * Map domains in theme URLs + * + * @param string $full_url The full URL + * @return string The mapped URL + */ + public function domain_mapping_themes_uri( $full_url ) { + return str_replace( $this->get_original_url( 'siteurl' ), get_option( 'siteurl' ), $full_url ); + } + + /** + * Get the domain mapping hash + * + * @return string The hash + */ + public function get_hash() { + $remote_login_hash = get_site_option( 'dm_hash' ); + + if ( null == $remote_login_hash ) { + $remote_login_hash = md5( time() ); + update_site_option( 'dm_hash', $remote_login_hash ); + } + + return $remote_login_hash; + } + + /** + * Redirect login to original domain + */ + public function redirect_login_to_orig() { + if ( ! get_site_option( 'dm_remote_login' ) || + ( isset( $_GET['action'] ) && $_GET['action'] == 'logout' ) || + isset( $_GET['loggedout'] ) ) { + return; + } + + $url = $this->get_original_url( 'siteurl' ); + + if ( $url != site_url() ) { + $url .= "/wp-login.php"; + echo ""; + } + } + + /** + * Handle logout across domains + */ + public function remote_logout_loader() { + global $current_site, $current_blog, $wpdb; + + $protocol = is_ssl() ? 'https://' : 'http://'; + $hash = $this->get_hash(); + $key = md5( time() ); + + $wpdb->insert( + $this->tables['logins'], + array( + 'id' => $key, + 'user_id' => 0, + 'blog_id' => $current_blog->blog_id, + 't' => current_time( 'mysql' ) + ), + array( '%s', '%d', '%d', '%s' ) + ); + + if ( get_site_option( 'dm_redirect_admin' ) ) { + wp_redirect( $protocol . $current_site->domain . $current_site->path . + "?dm={$hash}&action=logout&blogid={$current_blog->blog_id}&k={$key}&t=" . mt_rand() ); + exit; + } + } + + /** + * Handle remote login JS + */ + public function remote_login_js() { + global $current_blog, $current_user, $wpdb; + + if ( 0 == get_site_option( 'dm_remote_login' ) ) { + return; + } + + $hash = $this->get_hash(); + $protocol = is_ssl() ? 'https://' : 'http://'; + + if ( $_GET['dm'] == $hash ) { + if ( $_GET['action'] == 'load' ) { + if ( ! is_user_logged_in() ) { + exit; + } + + $key = md5( time() . mt_rand() ); + + $wpdb->insert( + $this->tables['logins'], + array( + 'id' => $key, + 'user_id' => $current_user->ID, + 'blog_id' => $_GET['blogid'], + 't' => current_time( 'mysql' ) + ), + array( '%s', '%d', '%d', '%s' ) + ); + + $url = add_query_arg( + array( + 'action' => 'login', + 'dm' => $hash, + 'k' => $key, + 't' => mt_rand() + ), + $_GET['back'] + ); + + echo "window.location = '" . esc_url( $url ) . "'"; + exit; + + } elseif ( $_GET['action'] == 'login' ) { + $details = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM {$this->tables['logins']} WHERE id = %s AND blog_id = %d", + $_GET['k'], $wpdb->blogid + )); + + if ( $details ) { + if ( $details->blog_id == $wpdb->blogid ) { + $wpdb->delete( + $this->tables['logins'], + array( 'id' => $_GET['k'] ), + array( '%s' ) + ); + + $wpdb->query( $wpdb->prepare( + "DELETE FROM {$this->tables['logins']} WHERE t < %s", + date( 'Y-m-d H:i:s', time() - 120 ) + )); + + wp_set_auth_cookie( $details->user_id ); + + wp_redirect( remove_query_arg( + array( 'dm', 'action', 'k', 't' ), + $protocol . $current_blog->domain . $_SERVER['REQUEST_URI'] + )); + exit; + + } else { + wp_die( esc_html__( "Incorrect or out of date login key", 'wp-domain-mapping' ) ); + } + } else { + wp_die( esc_html__( "Unknown login key", 'wp-domain-mapping' ) ); + } + + } elseif ( $_GET['action'] == 'logout' ) { + $details = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM {$this->tables['logins']} WHERE id = %s AND blog_id = %d", + $_GET['k'], $_GET['blogid'] + )); + + if ( $details ) { + $wpdb->delete( + $this->tables['logins'], + array( 'id' => $_GET['k'] ), + array( '%s' ) + ); + + $blog = get_blog_details( $_GET['blogid'] ); + wp_clear_auth_cookie(); + wp_redirect( trailingslashit( $blog->siteurl ) . "wp-login.php?loggedout=true" ); + exit; + } else { + wp_die( esc_html__( "Unknown logout key", 'wp-domain-mapping' ) ); + } + } + } + } + + /** + * Add JS loader for remote login + */ + public function remote_login_js_loader() { + global $current_site, $current_blog; + + if ( 0 == get_site_option( 'dm_remote_login' ) || is_user_logged_in() ) { + return; + } + + $protocol = is_ssl() ? 'https://' : 'http://'; + $hash = $this->get_hash(); + + echo ""; + } +} diff --git a/includes/class-tools.php b/includes/class-tools.php index 31efabe..449bed5 100644 --- a/includes/class-tools.php +++ b/includes/class-tools.php @@ -1,111 +1,162 @@ db = WP_Domain_Mapping_DB::get_instance(); - $this->core = WP_Domain_Mapping_Core::get_instance(); - + $this->tables = dm_get_table_names(); + $this->init(); + } + + /** + * Initialize tools functionality + */ + private function init() { // Setup hooks $this->setup_hooks(); + + // Initialize cron for health checks + $this->initialize_cron(); } - + + /** + * 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')); - + // Add Site ID to toolbar (only in multisite) + if ( is_multisite() ) { + 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')); - + 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')); - + 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')); + 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' - ); + + /** + * Initialize cron for health checks + */ + private function initialize_cron() { + // Register activation/deactivation hooks + register_activation_hook( WP_DOMAIN_MAPPING_BASENAME, array( $this, 'schedule_health_check' ) ); + register_deactivation_hook( WP_DOMAIN_MAPPING_BASENAME, array( $this, 'unschedule_health_check' ) ); } - + + // ======================================== // Site ID Display Functions - public function add_site_id_to_toolbar($admin_bar) { + // ======================================== + + /** + * Add site ID to WordPress toolbar + * + * @param WP_Admin_Bar $admin_bar WordPress toolbar object + */ + 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()) { + if ( ! current_user_can( 'manage_options' ) && ! is_super_admin() ) { return; } $blog_id = get_current_blog_id(); - $admin_bar->add_menu(array( + $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)) : '#', + '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'), + '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 to sites list + * + * @param array $columns Current columns + * @return array Modified columns + */ + 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); + $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) . ''; + + /** + * Display site ID column content + * + * @param string $column Column name + * @param int $blog_id Site ID + */ + public function display_site_id_column( $column, $blog_id ) { + if ( 'dm_site_id' == $column ) { + echo '' . esc_html( $blog_id ) . ''; } } - + + /** + * Add custom styles for site ID column + */ public function site_id_column_style() { - if ('sites-network' == get_current_screen()->id) { + if ( 'sites-network' == get_current_screen()->id ) { ?>