6556479417
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>
326 lines
8.2 KiB
PHP
326 lines
8.2 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: MLS by HansonXyz
|
|
* Plugin URI: https://hanson.xyz
|
|
* Description: Syncs MLS Grid API data (NorthStar MLS) into local database with CLI tools and public API for themes/plugins.
|
|
* Version: 1.0.0
|
|
* Author: HansonXyz
|
|
* Author URI: https://hanson.xyz
|
|
* License: GPL-2.0+
|
|
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
|
* Text Domain: mls-by-hansonxyz
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Plugin constants
|
|
define('MLS_PLUGIN_VERSION', '1.0.0');
|
|
define('MLS_PLUGIN_FILE', __FILE__);
|
|
define('MLS_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
|
define('MLS_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
define('MLS_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
|
define('MLS_DB_VERSION', '1.0.0');
|
|
|
|
// Database table names (without prefix)
|
|
define('MLS_TABLE_PROPERTIES', 'mls_properties');
|
|
define('MLS_TABLE_MEDIA', 'mls_media');
|
|
define('MLS_TABLE_SYNC_STATE', 'mls_sync_state');
|
|
define('MLS_TABLE_RATE_LIMITS', 'mls_rate_limits');
|
|
define('MLS_TABLE_SYNC_LOG', 'mls_sync_log');
|
|
|
|
/**
|
|
* Main plugin class
|
|
*/
|
|
final class MLS_Plugin {
|
|
|
|
/**
|
|
* Single instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Plugin components
|
|
*/
|
|
private $db;
|
|
private $options;
|
|
private $logger;
|
|
private $rate_limiter;
|
|
private $api_client;
|
|
private $sync_engine;
|
|
private $media_handler;
|
|
private $query;
|
|
|
|
/**
|
|
* Get single instance
|
|
*/
|
|
public static function get_instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->load_dependencies();
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Load required files
|
|
*/
|
|
private function load_dependencies() {
|
|
// Core classes
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-db.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-options.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-logger.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-rate-limiter.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-api-client.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-sync-engine.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-media-handler.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-query.php';
|
|
|
|
// Activation/Deactivation
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-activator.php';
|
|
require_once MLS_PLUGIN_DIR . 'includes/class-mls-deactivator.php';
|
|
|
|
// Admin
|
|
if (is_admin()) {
|
|
require_once MLS_PLUGIN_DIR . 'admin/class-mls-admin.php';
|
|
}
|
|
|
|
// WP-CLI
|
|
if (defined('WP_CLI') && WP_CLI) {
|
|
require_once MLS_PLUGIN_DIR . 'cli/class-mls-cli.php';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize hooks
|
|
*/
|
|
private function init_hooks() {
|
|
// Activation/Deactivation hooks
|
|
register_activation_hook(MLS_PLUGIN_FILE, array('MLS_Activator', 'activate'));
|
|
register_deactivation_hook(MLS_PLUGIN_FILE, array('MLS_Deactivator', 'deactivate'));
|
|
|
|
// Initialize components after plugins loaded
|
|
add_action('plugins_loaded', array($this, 'init_components'));
|
|
|
|
// Check for database updates
|
|
add_action('plugins_loaded', array($this, 'check_db_updates'));
|
|
}
|
|
|
|
/**
|
|
* Initialize plugin components
|
|
*/
|
|
public function init_components() {
|
|
$this->db = new MLS_DB();
|
|
$this->options = new MLS_Options();
|
|
$this->logger = new MLS_Logger($this->db);
|
|
$this->rate_limiter = new MLS_Rate_Limiter($this->db);
|
|
$this->api_client = new MLS_API_Client($this->options, $this->rate_limiter, $this->logger);
|
|
$this->media_handler = new MLS_Media_Handler($this->db, $this->logger);
|
|
$this->sync_engine = new MLS_Sync_Engine(
|
|
$this->db,
|
|
$this->api_client,
|
|
$this->media_handler,
|
|
$this->logger
|
|
);
|
|
$this->query = new MLS_Query($this->db);
|
|
|
|
// Initialize admin
|
|
if (is_admin()) {
|
|
new MLS_Admin($this);
|
|
}
|
|
|
|
// Initialize CLI
|
|
if (defined('WP_CLI') && WP_CLI) {
|
|
MLS_CLI::register($this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check and run database updates
|
|
*/
|
|
public function check_db_updates() {
|
|
$current_version = get_option('mls_db_version', '0');
|
|
|
|
if (version_compare($current_version, MLS_DB_VERSION, '<')) {
|
|
MLS_Activator::create_tables();
|
|
update_option('mls_db_version', MLS_DB_VERSION);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get DB instance
|
|
*/
|
|
public function get_db() {
|
|
return $this->db;
|
|
}
|
|
|
|
/**
|
|
* Get Options instance
|
|
*/
|
|
public function get_options() {
|
|
return $this->options;
|
|
}
|
|
|
|
/**
|
|
* Get Logger instance
|
|
*/
|
|
public function get_logger() {
|
|
return $this->logger;
|
|
}
|
|
|
|
/**
|
|
* Get Rate Limiter instance
|
|
*/
|
|
public function get_rate_limiter() {
|
|
return $this->rate_limiter;
|
|
}
|
|
|
|
/**
|
|
* Get API Client instance
|
|
*/
|
|
public function get_api_client() {
|
|
return $this->api_client;
|
|
}
|
|
|
|
/**
|
|
* Get Sync Engine instance
|
|
*/
|
|
public function get_sync_engine() {
|
|
return $this->sync_engine;
|
|
}
|
|
|
|
/**
|
|
* Get Media Handler instance
|
|
*/
|
|
public function get_media_handler() {
|
|
return $this->media_handler;
|
|
}
|
|
|
|
/**
|
|
* Get Query instance
|
|
*/
|
|
public function get_query() {
|
|
return $this->query;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the plugin
|
|
*/
|
|
function mls_plugin() {
|
|
return MLS_Plugin::get_instance();
|
|
}
|
|
|
|
// Start the plugin
|
|
add_action('plugins_loaded', 'mls_plugin', 0);
|
|
|
|
/**
|
|
* Global helper functions for themes/plugins
|
|
*/
|
|
|
|
/**
|
|
* Get MLS properties
|
|
*
|
|
* @param array $args Query arguments
|
|
* @return array Array of property objects
|
|
*/
|
|
function mls_get_properties($args = array()) {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return array();
|
|
}
|
|
return $plugin->get_query()->get_properties($args);
|
|
}
|
|
|
|
/**
|
|
* Get a single MLS property
|
|
*
|
|
* @param string $identifier Listing key or MLS ID
|
|
* @return object|null Property object or null
|
|
*/
|
|
function mls_get_property($identifier) {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return null;
|
|
}
|
|
return $plugin->get_query()->get_property($identifier);
|
|
}
|
|
|
|
/**
|
|
* Get media for a listing
|
|
*
|
|
* @param string $listing_key The listing key
|
|
* @return array Array of media objects
|
|
*/
|
|
function mls_get_property_media($listing_key) {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return array();
|
|
}
|
|
return $plugin->get_query()->get_property_media($listing_key);
|
|
}
|
|
|
|
/**
|
|
* Get primary image URL for a listing
|
|
*
|
|
* @param string $listing_key The listing key
|
|
* @return string|null Image URL or null
|
|
*/
|
|
function mls_get_property_image($listing_key) {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return null;
|
|
}
|
|
return $plugin->get_query()->get_primary_image($listing_key);
|
|
}
|
|
|
|
/**
|
|
* Get distinct cities with listings
|
|
*
|
|
* @param string|null $status Optional status filter
|
|
* @return array Array of city names
|
|
*/
|
|
function mls_get_cities($status = null) {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return array();
|
|
}
|
|
return $plugin->get_query()->get_distinct_cities($status);
|
|
}
|
|
|
|
/**
|
|
* Check if MLS data is available
|
|
*
|
|
* @return bool True if synced data exists
|
|
*/
|
|
function mls_is_available() {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return false;
|
|
}
|
|
return $plugin->get_query()->has_data();
|
|
}
|
|
|
|
/**
|
|
* Get property count
|
|
*
|
|
* @param array $args Optional filter arguments
|
|
* @return int Property count
|
|
*/
|
|
function mls_get_property_count($args = array()) {
|
|
$plugin = mls_plugin();
|
|
if (!$plugin->get_query()) {
|
|
return 0;
|
|
}
|
|
return $plugin->get_query()->get_count($args);
|
|
}
|