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>
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
<?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);
|
||||
}
|
||||
Reference in New Issue
Block a user