Files
homeproz/wp-content/plugins/mls-by-hansonxyz/mls-by-hansonxyz.php
T
Hanson.xyz Dev 6eadf3d266 Add queue-based media download system with rate limiting
- Add download_status, retry_after, queued_at columns to mls_media table
- Add mls_media_log table for download attempt tracking
- Rewrite media handler to queue downloads instead of immediate download
- Add 700ms delay between downloads (25% buffer over 2/sec limit)
- Add 3-hour backoff for rate-limited (429) responses
- Add max 5 attempts before marking as permanently failed
- Add wp mls media command: status, process, reset, logs
- Deprecate wp mls sync media in favor of wp mls media process
- Update documentation with queue system details and cron examples

Media downloads are now separate from property sync:
1. wp mls sync full/incremental - syncs properties, queues media
2. wp mls media process - downloads queued media with rate limiting

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 22:52:58 -06:00

327 lines
8.3 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.1.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');
define('MLS_TABLE_MEDIA_LOG', 'mls_media_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);
}