Files
homeproz/wp-content/plugins/mls-by-hansonxyz/admin/class-mls-admin.php
T
Hanson.xyz Dev 6556479417 Add MLS by HansonXyz plugin for MLS Grid API integration
Features:
- Full sync of NorthStar MLS properties via MLS Grid API v2
- Incremental sync using ModificationTimestamp
- Local media download and storage
- Rate limit compliance (2 req/sec, 7200/hr, 40000/day)
- Sync state tracking with resume capability
- WP-CLI commands: test, sync, status, stats, cache
- Admin settings page with manual sync triggers
- Public API functions: mls_get_properties, mls_get_property, etc.

Database tables:
- mls_properties: Listing data with full field mapping
- mls_media: Downloaded images
- mls_sync_state: Sync progress tracking
- mls_rate_limits: API usage tracking
- mls_sync_log: Debug logging

Documentation:
- docs/CLAUDE.md: AI development guide
- docs/API.md: MLS Grid API reference
- docs/USAGE.md: User documentation

Tested: Connection, auth, sync 10 records, media download verified

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 21:24:38 -06:00

387 lines
17 KiB
PHP

<?php
/**
* MLS Admin Settings Page
*/
if (!defined('ABSPATH')) {
exit;
}
class MLS_Admin {
/**
* Plugin instance
*/
private $plugin;
/**
* Constructor
*/
public function __construct($plugin) {
$this->plugin = $plugin;
add_action('admin_menu', array($this, 'add_menu'));
add_action('admin_init', array($this, 'register_settings'));
add_action('wp_ajax_mls_test_connection', array($this, 'ajax_test_connection'));
add_action('wp_ajax_mls_run_sync', array($this, 'ajax_run_sync'));
}
/**
* Add admin menu
*/
public function add_menu() {
add_options_page(
'MLS Settings',
'MLS Settings',
'manage_options',
'mls-settings',
array($this, 'render_settings_page')
);
}
/**
* Register settings
*/
public function register_settings() {
register_setting('mls_settings', MLS_Options::OPTION_KEY, array(
'sanitize_callback' => array($this, 'sanitize_options'),
));
}
/**
* Sanitize options
*/
public function sanitize_options($input) {
$sanitized = array();
if (isset($input['api_url'])) {
$sanitized['api_url'] = esc_url_raw($input['api_url']);
}
if (isset($input['api_token'])) {
$sanitized['api_token'] = sanitize_text_field($input['api_token']);
}
if (isset($input['originating_system'])) {
$sanitized['originating_system'] = sanitize_text_field($input['originating_system']);
}
$sanitized['auto_sync_enabled'] = !empty($input['auto_sync_enabled']);
$sanitized['sync_media'] = !empty($input['sync_media']);
if (isset($input['sync_interval'])) {
$allowed = array('every_30_minutes', 'hourly', 'every_2_hours', 'every_6_hours', 'every_12_hours', 'daily');
$sanitized['sync_interval'] = in_array($input['sync_interval'], $allowed)
? $input['sync_interval']
: 'hourly';
}
// Preserve timestamps
$existing = get_option(MLS_Options::OPTION_KEY, array());
if (isset($existing['last_full_sync'])) {
$sanitized['last_full_sync'] = $existing['last_full_sync'];
}
if (isset($existing['last_incremental_sync'])) {
$sanitized['last_incremental_sync'] = $existing['last_incremental_sync'];
}
return $sanitized;
}
/**
* Render settings page
*/
public function render_settings_page() {
if (!current_user_can('manage_options')) {
return;
}
$options = $this->plugin->get_options();
$db = $this->plugin->get_db();
$stats = $db->get_stats();
$sync_engine = $this->plugin->get_sync_engine();
$sync_status = $sync_engine->get_status();
$rate_limiter = $this->plugin->get_rate_limiter();
$rate_status = $rate_limiter->get_status();
// Check if using wp-config constants
$using_config_url = defined('MLSGRID_API_URL') && MLSGRID_API_URL;
$using_config_token = defined('MLSGRID_ACCESS_TOKEN') && MLSGRID_ACCESS_TOKEN;
?>
<div class="wrap">
<h1>MLS Settings</h1>
<div class="mls-admin-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
<!-- Settings Form -->
<div class="mls-settings-form">
<form method="post" action="options.php">
<?php settings_fields('mls_settings'); ?>
<table class="form-table">
<tr>
<th scope="row">API URL</th>
<td>
<?php if ($using_config_url): ?>
<input type="text" value="<?php echo esc_attr($options->get_api_url()); ?>" class="regular-text" disabled />
<p class="description">Set via MLSGRID_API_URL in wp-config.php</p>
<?php else: ?>
<input type="url" name="<?php echo MLS_Options::OPTION_KEY; ?>[api_url]"
value="<?php echo esc_attr($options->get('api_url')); ?>"
class="regular-text" />
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row">API Token</th>
<td>
<?php if ($using_config_token): ?>
<input type="password" value="••••••••••••" class="regular-text" disabled />
<p class="description">Set via MLSGRID_ACCESS_TOKEN in wp-config.php</p>
<?php else: ?>
<input type="password" name="<?php echo MLS_Options::OPTION_KEY; ?>[api_token]"
value="<?php echo esc_attr($options->get('api_token')); ?>"
class="regular-text" />
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row">Originating System</th>
<td>
<input type="text" name="<?php echo MLS_Options::OPTION_KEY; ?>[originating_system]"
value="<?php echo esc_attr($options->get('originating_system', 'northstar')); ?>"
class="regular-text" />
<p class="description">MLS system identifier (e.g., northstar)</p>
</td>
</tr>
<tr>
<th scope="row">Auto Sync</th>
<td>
<label>
<input type="checkbox" name="<?php echo MLS_Options::OPTION_KEY; ?>[auto_sync_enabled]"
value="1" <?php checked($options->get('auto_sync_enabled')); ?> />
Enable automatic sync
</label>
</td>
</tr>
<tr>
<th scope="row">Sync Interval</th>
<td>
<select name="<?php echo MLS_Options::OPTION_KEY; ?>[sync_interval]">
<option value="every_30_minutes" <?php selected($options->get('sync_interval'), 'every_30_minutes'); ?>>Every 30 minutes</option>
<option value="hourly" <?php selected($options->get('sync_interval'), 'hourly'); ?>>Hourly</option>
<option value="every_2_hours" <?php selected($options->get('sync_interval'), 'every_2_hours'); ?>>Every 2 hours</option>
<option value="every_6_hours" <?php selected($options->get('sync_interval'), 'every_6_hours'); ?>>Every 6 hours</option>
<option value="every_12_hours" <?php selected($options->get('sync_interval'), 'every_12_hours'); ?>>Every 12 hours</option>
<option value="daily" <?php selected($options->get('sync_interval'), 'daily'); ?>>Daily</option>
</select>
</td>
</tr>
<tr>
<th scope="row">Sync Media</th>
<td>
<label>
<input type="checkbox" name="<?php echo MLS_Options::OPTION_KEY; ?>[sync_media]"
value="1" <?php checked($options->get('sync_media', true)); ?> />
Download listing photos
</label>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<hr />
<h2>Manual Actions</h2>
<p>
<button type="button" class="button" id="mls-test-connection">Test Connection</button>
<span id="mls-test-result"></span>
</p>
<p>
<button type="button" class="button button-primary" id="mls-run-sync">Run Incremental Sync</button>
<button type="button" class="button" id="mls-run-full-sync">Run Full Sync</button>
</p>
<div id="mls-sync-progress" style="display:none; margin-top: 10px;">
<span class="spinner is-active" style="float: none;"></span>
<span id="mls-sync-status">Syncing...</span>
</div>
</div>
<!-- Status Panel -->
<div class="mls-status-panel">
<div class="card" style="max-width: none; padding: 15px;">
<h2 style="margin-top: 0;">Database Statistics</h2>
<table class="widefat">
<tr>
<td>Total Properties</td>
<td><strong><?php echo number_format($stats['total_properties']); ?></strong></td>
</tr>
<tr>
<td>Active</td>
<td><?php echo number_format($stats['active_properties']); ?></td>
</tr>
<tr>
<td>Pending</td>
<td><?php echo number_format($stats['pending_properties']); ?></td>
</tr>
<tr>
<td>Sold/Closed</td>
<td><?php echo number_format($stats['sold_properties']); ?></td>
</tr>
<tr>
<td>Media Files</td>
<td><?php echo number_format($stats['downloaded_media']); ?> / <?php echo number_format($stats['total_media']); ?></td>
</tr>
</table>
</div>
<div class="card" style="max-width: none; padding: 15px; margin-top: 15px;">
<h2 style="margin-top: 0;">Last Sync</h2>
<?php if ($sync_status['last_sync']): ?>
<p>
<strong>Type:</strong> <?php echo esc_html($sync_status['last_sync']->sync_type); ?><br />
<strong>Completed:</strong> <?php echo esc_html($sync_status['last_sync']->completed_at); ?><br />
<strong>Records:</strong> <?php echo number_format($sync_status['last_sync']->records_processed); ?> processed
</p>
<?php else: ?>
<p>No sync completed yet.</p>
<?php endif; ?>
</div>
<div class="card" style="max-width: none; padding: 15px; margin-top: 15px;">
<h2 style="margin-top: 0;">Rate Limits</h2>
<table class="widefat">
<tr>
<td>Hourly</td>
<td><?php echo number_format($rate_status['hourly']['used']); ?> / <?php echo number_format($rate_status['hourly']['limit']); ?></td>
</tr>
<tr>
<td>Daily</td>
<td><?php echo number_format($rate_status['daily']['used']); ?> / <?php echo number_format($rate_status['daily']['limit']); ?></td>
</tr>
<tr>
<td>Data This Hour</td>
<td><?php echo size_format($rate_status['bytes_this_hour']); ?></td>
</tr>
</table>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('#mls-test-connection').on('click', function() {
var $btn = $(this);
var $result = $('#mls-test-result');
$btn.prop('disabled', true);
$result.text('Testing...');
$.post(ajaxurl, {
action: 'mls_test_connection',
_wpnonce: '<?php echo wp_create_nonce('mls_admin'); ?>'
}, function(response) {
$btn.prop('disabled', false);
if (response.success) {
$result.html('<span style="color:green;">Connected! (' + response.data.response_time + 'ms)</span>');
} else {
$result.html('<span style="color:red;">Failed: ' + response.data + '</span>');
}
});
});
$('#mls-run-sync, #mls-run-full-sync').on('click', function() {
var syncType = $(this).attr('id') === 'mls-run-full-sync' ? 'full' : 'incremental';
if (syncType === 'full' && !confirm('Run full sync? This may take a while.')) {
return;
}
$('#mls-sync-progress').show();
$('#mls-sync-status').text('Starting ' + syncType + ' sync...');
$.post(ajaxurl, {
action: 'mls_run_sync',
sync_type: syncType,
_wpnonce: '<?php echo wp_create_nonce('mls_admin'); ?>'
}, function(response) {
$('#mls-sync-progress').hide();
if (response.success) {
alert('Sync completed! ' + response.data.message);
location.reload();
} else {
alert('Sync failed: ' + response.data);
}
});
});
});
</script>
<?php
}
/**
* AJAX: Test connection
*/
public function ajax_test_connection() {
check_ajax_referer('mls_admin', '_wpnonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$api_client = $this->plugin->get_api_client();
$result = $api_client->test_connection();
if ($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result['error']);
}
}
/**
* AJAX: Run sync
*/
public function ajax_run_sync() {
check_ajax_referer('mls_admin', '_wpnonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$sync_type = isset($_POST['sync_type']) ? sanitize_text_field($_POST['sync_type']) : 'incremental';
$sync_engine = $this->plugin->get_sync_engine();
// Run sync with a reasonable limit for AJAX
if ($sync_type === 'full') {
$result = $sync_engine->run_full_sync(false, 500);
} else {
$result = $sync_engine->run_incremental_sync(false);
}
if ($result['success']) {
wp_send_json_success(array(
'message' => sprintf(
'%d processed, %d created, %d updated',
$result['stats']['processed'],
$result['stats']['created'],
$result['stats']['updated']
),
'stats' => $result['stats'],
));
} else {
wp_send_json_error($result['error']);
}
}
}