*/ class Mlsimport_Admin { /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; public $main; public $theme_importer; public $env_data; public $mls_env_data; protected $process_all; public $field_import; public $themes; /** * Initialize the class and set its properties. * * @since 1.0.0 * @param string $plugin_name The name of this plugin. * @param string $version The version of this plugin. */ public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; $this->field_import = array( 'City', 'CountyOrParish', 'MlsStatus', 'PropertySubType', 'PropertyType', 'StandardStatus', 'InternetEntireListingDisplayYN', 'InternetAddressDisplayYN', ); $this->themes = array( 991 => 'WpResidence', 992 => 'Houzez', 993 => 'RealHomes', 994 => 'Wpestate', ); } /** * * * * Admin Setup * * @since 1.0.0 */ public function admin_setup( $plugin_name, $mls_enviroment, $theme_enviroment ) { $options = get_option( $this->plugin_name . '_admin_options' ); $theme_id = 0; if ( isset( $options['mlsimport_theme_used'] ) ) { $theme_id = intval( $options['mlsimport_theme_used'] ); } $themes = $this->themes; $theme_enviroment = ''; if ( isset( $themes[ $theme_id ] ) ) { $theme_enviroment = $themes[ $theme_id ]; } $this->theme_importer = new ThemeImport( $plugin_name ); $options_api = get_option( $this->plugin_name . '_admin_options' ); if ( '' !== $theme_enviroment ) { $classname = str_replace('Wp','',$theme_enviroment ). 'Class'; $this->env_data = new $classname(); } else { $this->env_data = new stdClass(); } if ( '' !== $mls_enviroment ) { $mls_classname = $mls_enviroment . 'Class'; $this->mls_env_data = new $mls_classname( $this->theme_importer ); } else { $this->mls_env_data = new stdClass(); } } /** * * * * Register the stylesheets for the admin area. * * @since 1.0.0 */ public function enqueue_styles() { wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/mlsimport-admin.css', array(), MLSIMPORT_VERSION, 'all' ); wp_enqueue_style( 'mlsimport-onboarding', plugin_dir_url( __FILE__ ) . 'css/mlsimport-onboarding.css', array(), MLSIMPORT_VERSION, 'all' ); wp_enqueue_style( 'mlsimport-field-selector', plugin_dir_url( __FILE__ ) . 'css/mlsimport-field-selector.css', array(), MLSIMPORT_VERSION, 'all' ); } /** * * * * Register the JavaScript for the admin area. * * @since 1.0.0 */ public function enqueue_scripts($hook_suffix) { wp_enqueue_script( 'jquery-ui-autocomplete' ); $mls_import_list = mlsimport_saas_request_list(); wp_enqueue_script( 'mlsimport-admin', plugin_dir_url( __FILE__ ) . 'js/mlsimport-admin.js', array( 'jquery' ), $this->version, true ); wp_localize_script( 'mlsimport-admin', 'mlsimport_vars', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) ); wp_enqueue_script( 'mlsimport-field-selector', plugin_dir_url( ( __FILE__ ) ) . 'js/mlsimport-field-selector.js', array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-tooltip' ), '1.0.0', true ); // Pass AJAX parameters to script wp_localize_script( 'mlsimport-field-selector', 'mlsimport_params', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'mlsimport_field_selector_nonce' ) )); wp_enqueue_script( 'mlsimport-progressive-save', plugin_dir_url( ( __FILE__ ) ) . 'js/progressive-save.js', array('mlsimport-field-selector' ), '1.0.0', true ); if ('toplevel_page_mlsimport_plugin_options' === $hook_suffix && isset($_GET['page']) && $_GET['page'] === 'mlsimport_plugin_options' && isset($_GET['tab']) && $_GET['tab'] === 'field_options') { $mlsimport_mls_metadata_populated = get_option( 'mlsimport_mls_metadata_populated', '' ); if ( 'yes' !== $mlsimport_mls_metadata_populated ) { $inline_script = 'jQuery(document).ready(function($){ mlsimport_saas_get_metadata(); });'; wp_add_inline_script('mlsimport-admin', $inline_script); } } if ( 'admin_page_mlsimport-onboarding' === $hook_suffix && isset($_GET['page']) && $_GET['page'] === 'mlsimport-onboarding' ) { $mlsimport_mls_metadata_populated = get_option('mlsimport_mls_metadata_populated', ''); if ('yes' !== $mlsimport_mls_metadata_populated) { $inline_script = 'jQuery(document).ready(function($){ mlsimport_saas_get_metadata(); });'; wp_add_inline_script('mlsimport-admin', $inline_script); } } if ('toplevel_page_mlsimport_plugin_options' === $hook_suffix && ( isset($_GET['page']) && $_GET['page'] === 'mlsimport_plugin_options' && isset($_GET['tab']) && $_GET['tab'] === 'display_options') || (isset($_GET['page']) && $_GET['page'] === 'mlsimport_plugin_options' && !isset($_GET['tab']) ) ) { $mls_import_list = mlsimport_saas_request_list(); if(!is_array($mls_import_list)){ $inline_script = 'jQuery(document).ready(function($){ var autofill='.wp_kses_post($mls_import_list).';mlsimport_autocomplte_mls_selection(autofill); });'; wp_add_inline_script('mlsimport-admin', $inline_script); } } // Searchable City/County multi-select — only on the Import Task edit screen. $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; $post_type = $screen ? (string) $screen->post_type : ''; if ( $this->mlsimport_is_import_task_edit_screen( (string) $hook_suffix, $post_type ) ) { wp_enqueue_script( 'mlsimport-searchable-select', plugin_dir_url( __FILE__ ) . 'js/mlsimport-searchable-select.js', array(), MLSIMPORT_VERSION, true ); } // Deactivation exit survey — only needed on the Plugins screen. if ( 'plugins.php' === $hook_suffix ) { wp_enqueue_script( 'mlsimport-deactivation-survey', plugin_dir_url( __FILE__ ) . 'js/mlsimport-deactivation-survey.js', array( 'jquery' ), MLSIMPORT_VERSION, true ); wp_localize_script( 'mlsimport-deactivation-survey', 'mlsimport_deact_survey', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'mlsimport_exit_survey' ), 'plugin_basename' => plugin_basename( MLSIMPORT_PLUGIN_PATH . 'mlsimport.php' ), 'options' => $this->get_exit_survey_options(), 'i18n' => array( 'title' => esc_html__( 'Before you go — quick question', 'mlsimport' ), 'intro' => esc_html__( 'Why are you deactivating MLS Import? Your answer helps us improve.', 'mlsimport' ), 'other_placeholder' => esc_html__( 'Tell us more (optional)', 'mlsimport' ), 'submit' => esc_html__( 'Submit & Deactivate', 'mlsimport' ), 'skip' => esc_html__( 'Skip & Deactivate', 'mlsimport' ), ), ) ); } } /** * * * * Register the administration menu for this plugin into the WordPress Dashboard menu. * * @since 1.0.0 */ public function add_plugin_admin_menu() { add_menu_page( esc_html__( 'MLS Import Settings', 'mlsimport'), esc_html__( 'MLS Import Settings', 'mlsimport' ), 'administrator', 'mlsimport_plugin_options', array( $this, 'display_plugin_setup_page' ), MLSIMPORT_PLUGIN_URL . '/img/mlsimport_menu.png', 22 ); add_submenu_page( 'mlsimport_plugin_options', esc_html__( 'Import History', 'mlsimport' ), esc_html__( 'Import History', 'mlsimport' ), 'administrator', 'mlsimport_history', array( $this, 'display_history_page' ) ); } /** * Renders the Import History admin page. * * @return void */ public function display_history_page() { include_once plugin_dir_path( __FILE__ ) . 'partials/mlsimport-history.php'; } /** * * * * Add settings action link to the plugins page. * * @since 1.0.0 */ public function add_action_links( $links ) { $settings_link = array( '' . esc_html__( 'Settings', 'mlsimport') . '', ); return array_merge( $settings_link, $links ); } /** * * * Render the settings page for this plugin. * * @since 1.0.0 */ public function display_plugin_setup_page() { include_once 'partials/' . $this->plugin_name . '-admin-display.php'; } /** * * * Validate plugin options fields * * @since 1.0.0 */ public function validate_admin_options( $input ) { $valid = array(); $settings_list = array( 'auth_username' => array( 'name' => esc_html__( 'Api auth_username ', 'mlsimport' ), 'details' => 'to be added', ), 'auth_password' => array( 'name' => esc_html__( 'Api auth_password', 'mlsimport' ), 'details' => 'to be added', ), 'client_id' => array( 'name' => esc_html__( 'Api client_id', 'mlsimport' ), 'details' => 'to be added', ), 'client_secret' => array( 'name' => esc_html__( 'client_secret', 'mlsimport' ), 'details' => 'to be added', ), 'redirect_uri' => array( 'name' => esc_html__( 'redirect_uri', 'mlsimport' ), 'details' => 'to be added', ), 'title_format' => array( 'name' => esc_html__( 'title_format', 'mlsimport' ), 'details' => 'to be added', ), 'force_rand' => array( 'name' => esc_html__( 'title_format', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_username' => array( 'name' => esc_html__( 'MLSImport.com Username (not your email)', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_password' => array( 'name' => esc_html__( 'MLSImport.com Password', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_mls_name' => array( 'name' => esc_html__( 'MLSImport Name', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_mls_token' => array( 'name' => esc_html__( 'MLSImport Token', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_tresle_client_id' => array( 'name' => esc_html__( 'MLSImport Tresle Client id', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_tresle_client_secret' => array( 'name' => esc_html__( 'MLSImport Client Secret', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_connectmls_username' => array( 'name' => esc_html__( 'MLSImport ConnectMLS Username', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_connectmls_password' => array( 'name' => esc_html__( 'MLSImport ConnectMLS Password', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_rapattoni_client_id' => array( 'name' => esc_html__( 'MLSImport Rapattoni Client id','mlsimport'), 'details' => 'to be added', ), 'mlsimport_rapattoni_client_secret' => array( 'name' => esc_html__( 'MLSImport Rapattoni Secret', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_rapattoni_username' => array( 'name' => esc_html__( 'MLSImport Rapattoni Username', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_rapattoni_password' => array( 'name' => esc_html__( 'MLSImport Rapattoni Password', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_paragon_client_id' => array( 'name' => esc_html__( 'MLSImport Paragon Client id','mlsimport' ), 'details' => 'to be added', ), 'mlsimport_paragon_client_secret' => array( 'name' => esc_html__( 'MLSImport Paragon Secret', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_realtorca_client_id' => array( 'name' => esc_html__( 'MLSImport Realtor.ca Client id','mlsimport' ), 'details' => 'to be added', ), 'mlsimport_realtorca_client_secret' => array( 'name' => esc_html__( 'MLSImport Realtor.ca Secret', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_brightmls_client_id' => array( 'name' => esc_html__( 'MLSImport BrightMLS Client id', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_brightmls_client_secret' => array( 'name' => esc_html__( 'MLSImport BrightMLS Secret', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_theme_used' => array( 'name' => esc_html__( 'Your Wordpress Theme', 'mlsimport' ), 'details' => 'to be added', ), 'mlsimport_mls_name_front' => array( 'name' => '', 'details' => 'to be added', ), 'mlsimport-disable-logs' => array( 'name' => '', 'details' => 'to be added', ), ); foreach ( $settings_list as $key => $setting ) { $valid[ $key ] = ( isset( $input[ $key ] ) && ! empty( $input[ $key ] ) ) ? esc_attr( $input[ $key ] ) : ''; } delete_option( 'mlsimport_connection_test' ); delete_option( 'mlsimport_mls_metadata_populated' ); update_option( 'mlsimport_encoding_array', '' ); delete_transient( 'mlsimport_token_request' ); delete_transient( 'mlsimport_schema' ); delete_transient( 'mlsimport_plugin_data_schema' ); delete_transient( 'mlsimport_saas_token' ); return $valid; } /** * * * Validate admin fields * * @since 1.0.0 */ public function validate_admin_fields_select( $input ) { $valid = array(); $mlsimport_mls_metadata_mls_data = get_option( 'mlsimport_mls_metadata_mls_data', '' ); $metadata_api_call = json_decode( $mlsimport_mls_metadata_mls_data, true ); foreach ( $metadata_api_call as $key => $value ) { if ( isset( $input['mls-fields'][ $key ] ) ) { $valid['mls-fields'][ $key ] = esc_attr( $input['mls-fields'][ $key ] ); } if ( isset( $input['mls-fields-admin'][ $key ] ) ) { $valid['mls-fields-admin'][ $key ] = esc_attr( $input['mls-fields-admin'][ $key ] ); $valid['mls-fields-label'][ $key ] = esc_attr( $input['mls-fields-label'][ $key ] ); $valid['mls-fields-map-postmeta'][ $key ] = esc_attr( $input['mls-fields-map-postmeta'][ $key ] ); $valid['mls-fields-map-taxonomy'][ $key ] = esc_attr( $input['mls-fields-map-taxonomy'][ $key ] ); $valid['field_order'][ $key ] = esc_attr( $input['field_order'][ $key ] ); } } //$valid['mls-fields-admin']['force_rand'] = esc_attr( $input['mls-fields-admin']['force_rand'] ); return $valid; } /** * * * Validate Mls Sync fields * * @since 1.0.0 */ public function validate_admin_mls_sync( $input ) { $valid = array(); $field_import = array( 'force_rand', 'min_price', 'max_price', 'title_format', 'property_agent', 'property_user', 'City', 'City_check', 'CountyOrParish', 'CountyOrParish_check', 'MlsStatus', 'MlsStatus_check', 'PropertySubType', 'PropertySubType_check', 'PropertyType', 'PropertyType_check', 'StandardStatus_delete', 'StandardStatus_delete_check', 'InternetEntireListingDisplayYN', 'InternetAddressDisplayYN' ); foreach ( $field_import as $key ) { $valid[ $key ] = $input[ $key ]; } return $valid; } /** * * * Validate Administrative options * * @since 1.0.0 */ public function validate_administrative_options( $input ) { $valid = array(); $field_import = array( 'import' ); foreach ( $field_import as $key ) { $valid[ $key ] = $input[ $key ]; } return $valid; } /** * * * * Validate Import Options fields * * @since 1.0.0 */ public function validate_admin_import_options( $input ) { $valid = array(); $field_import = array( 'import_number' ); foreach ( $field_import as $key ) { $valid[ $key ] = intval( $input[ $key ] ); } if ( isset( $input['import'] ) && '' !== $input['import'] ) { $decode = json_decode( $input['import'] ); update_option( 'mlsimport_admin_fields_select', $decode['mlsimport_admin_fields_select'] ); update_option( 'mlsimport_admin_mls_sync', $decode['mlsimport_admin_mls_sync'] ); update_option( 'mlsimport_admin_import_options', $decode['mlsimport_admin_import_options'] ); update_option( 'mlsimport_admin_use_transients', $decode['mlsimport_admin_use_transients'] ); } return $valid; } /** * * * plugin options update */ public function options_update() { register_setting( $this->plugin_name . '_admin_options', $this->plugin_name . '_admin_options', array( $this, 'validate_admin_options' ) ); register_setting( $this->plugin_name . '_admin_fields_select', $this->plugin_name . '_admin_fields_select', array( $this, 'validate_admin_fields_select' ) ); register_setting( $this->plugin_name . '_admin_mls_sync', $this->plugin_name . '_admin_mls_sync', array( $this, 'validate_admin_mls_sync' ) ); register_setting( $this->plugin_name . '_admin_import_options', $this->plugin_name . '_admin_import_options', array( $this, 'validate_admin_import_options' ) ); register_setting( $this->plugin_name . '_administrative_options', $this->plugin_name . '_administrative_options', array( $this, 'validate_administrative_options' ) ); } /** * * * * * * * */ public function update_option_mlsimport_administrative_options() { $import = get_option( 'mlsimport_administrative_options' ); if ( '' !== $import ) { $decode = json_decode( $import['import'], true ); update_option( 'mlsimport_admin_fields_select', $decode['mlsimport_admin_fields_select'] ); update_option( 'mlsimport_admin_mls_sync', $decode['mlsimport_admin_mls_sync'] ); update_option( 'mlsimport_admin_import_options', $decode['mlsimport_admin_import_options'] ); } } /** * * * plugin options update */ public function update_option_mlsimport_admin_fields_select() { $this->env_data->enviroment_custom_fields( $this->plugin_name ); } /** * * * plugin options update */ public function mlsimport_meta_options() { if ( method_exists( $this->env_data, 'get_property_post_type' ) ) { add_meta_box( 'mlsimport_hidden_fields', esc_html__( 'Mls Import Hidden Fields', 'mlsimport' ), array( $this, 'mlsimport_hidden_fields' ), $this->env_data->get_property_post_type(), 'normal', 'low' ); } } /** * * * plugin options update */ public function mlsimport_hidden_fields() { global $post; $options = get_option( $this->plugin_name . '_admin_fields_select' ); $MLSimport_item_inserted = get_post_meta( $post->ID, 'MLSimport_item_inserted', true ); $MLSimport_item_updated = get_post_meta( $post->ID, 'MLSimport_item_updated', true ); $listing_key = get_post_meta( $post->ID, 'ListingKey', true ); // Get the import task ID to retrieve protected statuses $import_task_id = !empty( $MLSimport_item_inserted ) ? $MLSimport_item_inserted : ( !empty( $MLSimport_item_updated ) ? $MLSimport_item_updated : null ); $mlsImportItemStatusProtect = $import_task_id ? get_post_meta( $import_task_id, 'mlsimport_item_standardstatusprotect', true ) : null; // Check if the ListingKey exists if ( !empty( $listing_key ) ) { echo 'ListingKey: ' . esc_html( $listing_key ) . '
'; } // Check if MLSimport_item_inserted exists if ( !empty( $MLSimport_item_inserted ) ) { echo 'Added via MLS item id: ' . esc_html( $MLSimport_item_inserted ) . ' - ' . esc_html( get_the_title( $MLSimport_item_inserted ) ) . '
'; } // Check if MLSimport_item_updated exists if ( !empty( $MLSimport_item_updated ) ) { echo 'Updated via MLS item id: ' . esc_html( $MLSimport_item_updated ) . ' - ' . esc_html( get_the_title( $MLSimport_item_updated ) ) . '
'; } if(!empty($mlsImportItemStatusProtect)) { if(is_array($mlsImportItemStatusProtect)) { echo 'Protected statuses: ' . esc_html( implode(', ', $mlsImportItemStatusProtect) ) . '
'; } else { echo 'Protected statuses: ' . esc_html($mlsImportItemStatusProtect) . '
'; } } foreach ( $options['mls-fields-admin'] as $key => $value ) { if ( 1 === intval($options['mls-fields-admin'][ $key ] ) ) { $display_label = $key; if ( isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ) { $display_label = $options['mls-fields-label'][ $key ]; } if ( 'ListingKey' !== $key ) { $meta_key = strtolower( $key ); } else { $meta_key = $key; } ?> : ID, $meta_key, true ) ); ?>

Mls Import History

ID, 'mlsimport_property_history', true ); if ( '' === trim( $meta ) ) { ?> Property history is blank - you can enable it in Settings/ Tools page $taxonomy, 'hide_empty' => false, 'orderby' => 'name' ) ); $result = array(); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { $result[] = array( 'slug' => $term->slug, 'name' => $term->name, 'count' => $term->count, ); } } wp_send_json_success( $result ); } function mlsimport_delete_properties() { global $mlsimport; check_ajax_referer( 'mlsimport_tool_actions', 'security' ); if ( ! current_user_can( 'administrator' ) ) { wp_send_json_error( 'Unauthorized' ); } $taxonomy = sanitize_text_field( wp_unslash( $_POST['mlsimport_delete_category'] ) ); $terms = array(); if ( isset( $_POST['mlsimport_delete_category_term'] ) && is_array( $_POST['mlsimport_delete_category_term'] ) ) { foreach ( $_POST['mlsimport_delete_category_term'] as $term ) { $terms[] = sanitize_text_field( wp_unslash( $term ) ); } } if ( '' === $taxonomy ) { wp_send_json_error( esc_html__( 'Please select a taxonomy', 'mlsimport' ) ); } if ( empty( $terms ) ) { wp_send_json_error( esc_html__( 'Please select at least one term', 'mlsimport' ) ); } $post_type = $mlsimport->admin->env_data->get_property_post_type(); $args = array( 'post_type' => $post_type, 'post_status' => 'any', 'posts_per_page' => 20, 'tax_query' => array( array( 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => $terms, ), ), 'fields' => 'ids', ); $prop_selection = new WP_Query( $args ); $deleted = 0; foreach ( $prop_selection->posts as $delete_id ) { $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql( $delete_id, ' delete from tools ' ); ++$deleted; } $remaining = $prop_selection->found_posts - $deleted; $done = ( $remaining <= 0 ); // Update term counts only when all deletions are complete if ( $done ) { $all_taxonomies = get_object_taxonomies( $post_type ); foreach ( $all_taxonomies as $tax_name ) { $all_terms = get_terms( array( 'taxonomy' => $tax_name, 'hide_empty' => false, 'fields' => 'ids' ) ); if ( ! is_wp_error( $all_terms ) && ! empty( $all_terms ) ) { wp_update_term_count_now( $all_terms, $tax_name ); } } } wp_send_json_success( array( 'deleted' => $deleted, 'remaining' => max( 0, $remaining ), 'total' => $prop_selection->found_posts, 'done' => $done, ) ); } /** * * * Testing enviroment variables */ public function mlsimport_saas_setting_up() { // Do not output warnings during AJAX requests if ( ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { return; } $is_onboarding = isset( $_GET['page'] ) && 'mlsimport-onboarding' === $_GET['page']; if ( ! $is_onboarding && intval( WP_MEMORY_LIMIT ) < 256 ) : if (intval(WP_MEMORY_LIMIT) < 256){ ?>
WordPress Memory Limit is set to . Allocated Memory should be at least 256MB. Please refer to: Increasing memory allocated to PHP
Your max_execution_time setting in php is set to . Importing hundreds of listings requires extra time. Please set max_execution_time to 0 (unlimited). If that is not possible, set it to a minimum of 600 (10 minutes).
plugin_name . '_admin_options' ); $mls_id = ''; if ( isset( $options['mlsimport_mls_name'] ) ) { $mls_id = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) ); } $mls_token = ''; if ( isset( $options['mlsimport_mls_name'] ) ) { $mls_token = sanitize_text_field( trim( $options['mlsimport_mls_token'] ) ); } $mls_id_int = intval( $mls_id ); $mlsimport_tresle_client_id = ''; if ( isset( $options['mlsimport_tresle_client_id'] ) ) { $mlsimport_tresle_client_id = sanitize_text_field( trim( $options['mlsimport_tresle_client_id'] ) ); } $mlsimport_tresle_client_secret = ''; if ( isset( $options['mlsimport_tresle_client_secret'] ) ) { $mlsimport_tresle_client_secret = sanitize_text_field( trim( $options['mlsimport_tresle_client_secret'] ) ); } $mlsimport_connectmls_username = ''; if ( isset( $options['mlsimport_connectmls_username'] ) ) { $mlsimport_connectmls_username = sanitize_text_field( trim( $options['mlsimport_connectmls_username'] ) ); } $mlsimport_connectmls_password = ''; if ( isset( $options['mlsimport_connectmls_password'] ) ) { $mlsimport_connectmls_password = sanitize_text_field( trim( $options['mlsimport_connectmls_password'] ) ); } // rapattoni data $mlsimport_rapattoni_client_id = ''; if ( isset( $options['mlsimport_rapattoni_client_id'] ) ) { $mlsimport_rapattoni_client_id = sanitize_text_field( trim( $options['mlsimport_rapattoni_client_id'] ) ); } $mlsimport_rapattoni_client_secret = ''; if ( isset( $options['mlsimport_rapattoni_client_secret'] ) ) { $mlsimport_rapattoni_client_secret = sanitize_text_field( trim( $options['mlsimport_rapattoni_client_secret'] ) ); } $mlsimport_rapattoni_username = ''; if ( isset( $options['mlsimport_rapattoni_username'] ) ) { $mlsimport_rapattoni_username = sanitize_text_field( trim( $options['mlsimport_rapattoni_username'] ) ); } $mlsimport_rapattoni_password = ''; if ( isset( $options['mlsimport_rapattoni_password'] ) ) { $mlsimport_rapattoni_password = sanitize_text_field( trim( $options['mlsimport_rapattoni_password'] ) ); } // paragon data $mlsimport_paragon_client_id = ''; if ( isset( $options['mlsimport_paragon_client_id'] ) ) { $mlsimport_paragon_client_id = sanitize_text_field( trim( $options['mlsimport_paragon_client_id'] ) ); } $mlsimport_paragon_client_secret = ''; if ( isset( $options['mlsimport_paragon_client_secret'] ) ) { $mlsimport_paragon_client_secret = sanitize_text_field( trim( $options['mlsimport_paragon_client_secret'] ) ); } // realtor.ca data $mlsimport_realtorca_client_id = ''; if ( isset( $options['mlsimport_realtorca_client_id'] ) ) { $mlsimport_realtorca_client_id = sanitize_text_field( trim( $options['mlsimport_realtorca_client_id'] ) ); } $mlsimport_realtorca_client_secret = ''; if ( isset( $options['mlsimport_realtorca_client_secret'] ) ) { $mlsimport_realtorca_client_secret = sanitize_text_field( trim( $options['mlsimport_realtorca_client_secret'] ) ); } // brightmls data $mlsimport_brightmls_client_id = ''; if ( isset( $options['mlsimport_brightmls_client_id'] ) ) { $mlsimport_brightmls_client_id = sanitize_text_field( trim( $options['mlsimport_brightmls_client_id'] ) ); } $mlsimport_brightmls_client_secret = ''; if ( isset( $options['mlsimport_brightmls_client_secret'] ) ) { $mlsimport_brightmls_client_secret = sanitize_text_field( trim( $options['mlsimport_brightmls_client_secret'] ) ); } if ( trim( $mls_token ) === '' ) { if ( $this->mlsimport_is_brightmls_provider( $mls_id_int ) ) { // BrightMLS if ( trim( $mlsimport_brightmls_client_id ) === '' || trim( $mlsimport_brightmls_client_secret ) === '' ) { return; } } elseif ( $mls_id_int > 900 && $mls_id_int < 3000 ) { // Trestle if ( trim( $mlsimport_tresle_client_id ) === '' || trim( $mlsimport_tresle_client_secret ) === '' ) { return; } } elseif ( $this->mlsimport_is_connectmls_provider( $mls_id_int ) ) { // ConnectMLS if ( trim( $mlsimport_connectmls_username ) === '' || trim( $mlsimport_connectmls_password ) === '' ) { return; } } elseif ( $mls_id_int >= 5000 && $mls_id_int < 6000 ) { // Rapattoni if ( trim( $mlsimport_rapattoni_client_id ) === '' || trim( $mlsimport_rapattoni_client_secret ) === '' || trim( $mlsimport_rapattoni_username ) === '' || trim( $mlsimport_rapattoni_password ) === '' ) { return; } } elseif ( $mls_id_int >= 6000 && $mls_id_int < 7000 ) { // Paragon if ( trim( $mlsimport_paragon_client_id ) === '' || trim( $mlsimport_paragon_client_secret ) === '' ) { return; } } elseif ( $mls_id_int >= 7000 && $mls_id_int < 8000 ) { // Realtor.ca if ( trim( $mlsimport_realtorca_client_id ) === '' || trim( $mlsimport_realtorca_client_secret ) === '' ) { return; } } } $values['mls_token'] = $mls_token; $values['mls_id'] = $mls_id; $values['mlsimport_tresle_client_id'] = $mlsimport_tresle_client_id; $values['mlsimport_tresle_client_secret'] = $mlsimport_tresle_client_secret; $values['mlsimport_connectmls_username'] = $mlsimport_connectmls_username; $values['mlsimport_connectmls_password'] = $mlsimport_connectmls_password; $values['mlsimport_rapattoni_client_id'] = $mlsimport_rapattoni_client_id; $values['mlsimport_rapattoni_client_secret'] = $mlsimport_rapattoni_client_secret; $values['mlsimport_rapattoni_username'] = $mlsimport_rapattoni_username; $values['mlsimport_rapattoni_password'] = $mlsimport_rapattoni_password; $values['mlsimport_paragon_client_id'] = $mlsimport_paragon_client_id; $values['mlsimport_paragon_client_secret'] = $mlsimport_paragon_client_secret; $values['mlsimport_realtorca_client_id'] = $mlsimport_realtorca_client_id; $values['mlsimport_realtorca_client_secret'] = $mlsimport_realtorca_client_secret; $values['mlsimport_brightmls_client_id'] = $mlsimport_brightmls_client_id; $values['mlsimport_brightmls_client_secret'] = $mlsimport_brightmls_client_secret; $answer = $this->theme_importer->globalApiRequestSaas( 'clients', $values, 'PATCH' ); if ( isset( $answer['success'] ) && true === $answer['success'] ) { if ( isset( $answer['tested'] ) && true === $answer['tested'] ) { update_option( 'mlsimport_connection_test', 'yes' ); mlsimport_telemetry_set_once( 'mls_connected_at', time() ); } else { delete_option( 'mlsimport_connection_test' ); delete_option( 'mlsimport_mls_metadata_populated' ); } } else { delete_option( 'mlsimport_connection_test' ); delete_option( 'mlsimport_mls_metadata_populated' ); } return $answer; } private function mlsimport_is_connectmls_provider( $mls_id_int ) { return $mls_id_int >= 8000 && $mls_id_int < 9000 && 8001 !== (int) $mls_id_int; } private function mlsimport_is_brightmls_provider( int $mls_id_int ): bool { return 8001 === $mls_id_int; } /** * AJAX handler for the plugin-deactivation exit survey. * * Thin wrapper: it verifies the nonce and capability, sanitizes input, * delegates the real work to mlsimport_exit_survey_record(), and POSTs * the result to the SaaS API. The POST is fire-and-forget — a failed or * not-yet-deployed endpoint must never stop the admin from deactivating. */ public function mlsimport_exit_survey_submit() { check_ajax_referer( 'mlsimport_exit_survey', 'security' ); if ( ! current_user_can( 'administrator' ) ) { wp_send_json_error( 'Unauthorized' ); } $input = array( 'reason' => sanitize_text_field( wp_unslash( $_POST['reason'] ?? '' ) ), 'details' => sanitize_textarea_field( wp_unslash( $_POST['details'] ?? '' ) ), ); // Only record a recognized reason; an unknown value is dropped // silently rather than blocking the user or storing junk. if ( $this->mlsimport_exit_survey_is_valid_reason( $input['reason'] ) ) { $payload = $this->mlsimport_exit_survey_record( $input ); try { ThemeImport::globalApiRequestSaas( 'user-activity', $payload, 'POST' ); } catch ( \Throwable $e ) { // Swallow: deactivation proceeds regardless of transport failure. } } wp_send_json_success(); } /** * Whether a submitted exit-survey reason is one of the known options. * * Pure predicate — no WordPress functions, no translation. */ private function mlsimport_exit_survey_is_valid_reason( string $reason ): bool { return in_array( $reason, $this->mlsimport_exit_survey_reasons(), true ); } /** * Whether the current admin request is the Import Task (mlsimport_item) * post edit screen. Used to scope the Select2 asset enqueue so the * searchable-select library does not load across all of wp-admin. * * @param string $hook_suffix Current admin page hook suffix. * @param string $post_type Post type of the screen being rendered. */ private function mlsimport_is_import_task_edit_screen( string $hook_suffix, string $post_type ): bool { return 'mlsimport_item' === $post_type && in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true ); } /** * Extra CSS class for an Import Task select field. City and County lists * can hold 300+ entries, so they are upgraded to a searchable multi-select * (Select2) via this marker class; every other field keeps the plain select. * * @param string $field_key Field key from the $field_import definition. * @return string Leading-space class string, or '' when not searchable. */ private function mlsimport_searchable_select_class( string $field_key ): string { return in_array( $field_key, array( 'City', 'CountyOrParish' ), true ) ? ' mlsimport-searchable-select' : ''; } /** * Exit-survey testable core: resolve identity and count, build payload. * * Calls no dying functions — the AJAX wrapper handles nonce/capability * and wp_send_json_*. Always returns the payload to transmit. * * @param array $input Sanitized survey input (reason, details). */ private function mlsimport_exit_survey_record( array $input ): array { $opts = get_option( 'mlsimport_admin_options', array() ); if ( empty( $opts['mlsimport_install_uuid'] ) ) { $opts['mlsimport_install_uuid'] = wp_generate_uuid4(); update_option( 'mlsimport_admin_options', $opts ); } $count = (int) get_option( 'mlsimport_deactivation_count', 0 ) + 1; update_option( 'mlsimport_deactivation_count', $count ); $reason = (string) ( $input['reason'] ?? '' ); $options = $this->get_exit_survey_options(); return array( 'event_type' => 'exit_survey', 'reason' => $reason, 'reason_label' => $options[ $reason ] ?? '', 'details' => (string) ( $input['details'] ?? '' ), 'account' => (string) ( $opts['mlsimport_username'] ?? '' ), 'install_uuid' => $opts['mlsimport_install_uuid'], 'deactivation_count' => $count, 'environment' => wp_get_environment_type(), 'site_url' => home_url(), 'admin_email' => (string) get_option( 'admin_email' ), 'timestamp' => time(), ); } /** * The known exit-survey reason keys — the single source of truth. * * Pure: keys only, no labels, no translation. The label map * (get_exit_survey_options) builds on top of this for the modal. * * @return string[] */ private function mlsimport_exit_survey_reasons(): array { return array( 'built_website', 'no_leads', 'technical_issues', 'too_expensive', 'switched_tool', 'other', ); } /** * Exit-survey reason key => display label, for the modal and payload. * * Uses translation, so it is not exercised by the pure unit suite — * is_valid_reason() relies on mlsimport_exit_survey_reasons() instead. * * @return array */ private function get_exit_survey_options(): array { return array( 'built_website' => esc_html__( "Built the website, don't need ongoing sync", 'mlsimport' ), 'no_leads' => esc_html__( 'Not getting leads from my site', 'mlsimport' ), 'technical_issues' => esc_html__( "Technical issues I couldn't fix", 'mlsimport' ), 'too_expensive' => esc_html__( 'Too expensive', 'mlsimport' ), 'switched_tool' => esc_html__( 'Switched to another tool', 'mlsimport' ), 'other' => esc_html__( 'Other', 'mlsimport' ), ); } /** * Request auth token from mlsimport.net * * @since 4.0.1 * returns token fron mlsimport */ public function mlsimport_saas_get_mls_api_token_from_transient() { $token = get_transient( 'mlsimport_saas_token' ); if ( false === $token || '' === $token ) { $token_json_answer = $this->mlsimport_saas_get_mls_api_token(); if ( isset( $token_json_answer['success'] ) && true === $token_json_answer['success'] ) { $token = $token_json_answer['token']; set_transient( 'mlsimport_saas_token', $token, 3500 ); } } return $token; } /** * call for token * * @since 4.0.1 * returns token fron mlsimport */ protected function mlsimport_saas_get_mls_api_token() { $values = array(); $options = get_option( $this->plugin_name . '_admin_options' ); // Check if the MLS provider has changed since the last run $prev_mls = get_option( 'mlsimport_prev_mls_name', '' ); $username = ''; if ( isset( $options['mlsimport_username'] ) ) { $username = sanitize_text_field( trim( $options['mlsimport_username'] ) ); } $password = ''; if ( isset( $options['mlsimport_password'] ) ) { $password = sanitize_text_field( trim( $options['mlsimport_password'] ) ); } $mls_name = ''; if ( isset( $options['mlsimport_mls_name'] ) ) { $mls_name = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) ); } $mls_token = ''; if ( isset( $options['mlsimport_mls_token'] ) ) { $mls_token = sanitize_text_field( trim( $options['mlsimport_mls_token'] ) ); } if ( $prev_mls !== '' && $prev_mls !== $mls_name ) { delete_transient( 'mlsimport_token_request' ); delete_transient( 'mlsimport_metadata_api_call_data_service_property' ); delete_transient( 'mls_import_meta_enums' ); delete_transient( 'mls_import_meta' ); delete_transient( 'mlsimport_plugin_data_schema' ); delete_transient( 'mlsimport_ready_to_go_mlsimport_data' ); delete_transient( 'mlsimport_saas_token' ); delete_option( 'mlsimport_mls_metadata_populated' ); delete_option( 'mlsimport_admin_fields_select' ); } update_option( 'mlsimport_prev_mls_name', $mls_name ); $values['username'] = $username; $values['password'] = $password; if ( '' === $username || '' === $password ) { return ''; } $theme_Start = new ThemeImport(); $answer = $theme_Start::globalApiRequestSaas( 'token', $values, 'POST' ); return $answer; } /** * save meta options * * @since 3.0.1 */ public function mlsimport_item_product_metaboxes() { add_meta_box( 'mlsimport_item_metaboxes-sectionid', __( 'Set Import data', 'mlsimport' ), array( $this, 'mlsimport_saas_display_meta_options' ), 'mlsimport_item', 'normal', 'default' ); } /** * * * * save meta options * * @since 3.0.1 */ public function mlsimport_item_product_save_metaboxes( $post_id, $post ) { if ( ! is_object( $post ) || ! isset( $post->post_type ) ) { return; } if ( 'mlsimport_item' !== $post->post_type ) { return; } $allowed_keys = array( 'mlsimport_item_how_many', 'mlsimport_item_title_format', 'mlsimport_item_agent', 'mlsimport_item_property_status', 'mlsimport_item_property_user', 'mlsimport_item_min_price', 'mlsimport_item_max_price', 'mlsimport_item_city_check', 'mlsimport_item_city', 'mlsimport_item_city[]', 'mlsimport_item_countyorparish_check', 'mlsimport_item_countyorparish', 'mlsimport_item_mlsstatus_check', 'mlsimport_item_mlsstatus', 'mlsimport_item_propertysubtype_check', 'mlsimport_item_propertysubtype', 'mlsimport_item_propertytype_check', 'mlsimport_item_propertytype', 'mlsimport_item_standardstatus_check', 'mlsimport_item_standardstatus', 'mlsimport_item_standardstatusprotect_check', 'mlsimport_item_standardstatusprotect', 'mlsimport_item_internetentirelistingdisplayyn', 'mlsimport_item_internetaddressdisplayyn', 'mlsimport_item_stat_cron', 'mlsimport_item_listagentkey', 'mlsimport_item_listagentmlsid', 'mlsimport_item_buyeragentmlsid', 'mlsimport_item_listofficekey', 'mlsimport_item_postalcode', 'mlsimport_item_listofficemlsid', 'mlsimport_item_listingid', 'mlsimport_item_extracity', 'mlsimport_item_extracounty', 'mlsimport_item_exclude_listofficemlsid', 'mlsimport_item_exclude_listofficekey', 'mlsimport_item_exclude_listagentmlsid', 'mlsimport_item_exclude_listagentkey', 'mlsimport_item_customparameters', 'mlsimport_item_mlsareamajor', 'mlsimport_item_subdivisionname', ); foreach ( $allowed_keys as $key => $key_value ) { if( isset($_POST[$key_value]) ){ $postmeta = mlsimport_sanitize_multi_dimensional_array ( $_POST[$key_value] ) ; update_post_meta( $post_id, sanitize_key( $key_value ), $postmeta ); } } $blank_keys = array( 'mlsimport_item_standardstatus', 'mlsimport_item_standardstatusprotect', 'mlsimport_item_city', 'mlsimport_item_countyorparish', 'mlsimport_item_propertysubtype', 'mlsimport_item_propertytype', 'mlsimport_item_standardstatus', 'mlsimport_item_listingid', 'mlsimport_item_customparameters', 'mlsimport_item_mlsareamajor', 'mlsimport_item_subdivisionname', ); foreach ( $blank_keys as $key ) { if ( ! isset( $_POST[ $key ] ) ) { update_post_meta( $post_id, $key, '' ); } } } /** * Display Meta Options * * @param WP_Post $post The post object. */ public function mlsimport_saas_display_meta_options($post) { wp_nonce_field(plugin_basename(__FILE__), 'estate_agent_noncename'); global $mlsimport; $token = $mlsimport->admin->mlsimport_saas_get_mls_api_token_from_transient(); $is_mls_connected = get_option('mlsimport_connection_test', ''); $mlsimport->admin->mlsimport_saas_setting_up(); if ('yes' !== $is_mls_connected) { $mlsimport->admin->mlsimport_saas_check_mls_connection(); $is_mls_connected = get_option('mlsimport_connection_test', ''); } if (trim($token) === '') { echo '
' . esc_html__('You are not connected to MlsImport - Please check your Username and Password.', 'mlsimport') . '
'; return; } if ('yes' !== $is_mls_connected) { echo '
' . esc_html__('The connection to your MLS was NOT succesful. Please check the authentication token is correct and check your MLS Data Access Application is approved.', 'mlsimport') . '
'; return; } $postId = $post->ID; $mlsimportItemHowMany = esc_html(get_post_meta($postId, 'mlsimport_item_how_many', true)); $mlsimportItemStatCron = esc_html(get_post_meta($postId, 'mlsimport_item_stat_cron', true)); $lastDate = get_post_meta($postId, 'mlsimport_last_date', true); $status = get_option('mlsimport_force_stop_' . $postId); $fieldImport = $this->mlsimport_saas_return_mls_fields(); $options = get_option('mlsimport_admin_options'); $mlsimportMlsId = isset($options['mlsimport_mls_name']) && $options['mlsimport_mls_name'] !== '' ? intval($options['mlsimport_mls_name']) : 0; $mlsRequest = $this->mlsimport_make_listing_requests($postId); // print_r($mlsRequest); $hasError = isset($mlsRequest['success']) && !$mlsRequest['success']; if ($hasError) { echo '
' . esc_html($mlsRequest['message']) . '
'; } $foundItems = isset($mlsRequest['results']) ? intval($mlsRequest['results']) : 'none'; if ($foundItems === 'none') { $mlsimport->admin->mlsimport_saas_check_mls_connection(); esc_html_e('Your Token was expired. Please refresh the page to renew it wait while we renew it.', 'mlsimport'); } echo $this->generateMetaOptionsHtml($postId, $foundItems, $lastDate, $mlsimportItemHowMany, $mlsimportItemStatCron, $mlsimportMlsId, $fieldImport, $hasError); } /** * Generate Meta Options HTML * * @param int $postId The post ID. * @param int $foundItems The number of found items. * @param string $lastDate The last date checked. * @param string $mlsimportItemHowMany How many items to import. * @param string $mlsimportItemStatCron The status of the cron job. * @param int $mlsimportMlsId The MLS import ID. * @param array $fieldImport The fields to import. * @return string The generated HTML. */ private function generateMetaOptionsHtml($postId, $foundItems, $lastDate, $mlsimportItemHowMany, $mlsimportItemStatCron, $mlsimportMlsId, $fieldImport, $hasError = false) { ob_start(); $metadata_api_call_city = array(); $metadata_api_call_county = array(); $metadata_api_call_property_type = array(); $mlsimport_mls_metadata_mls_enums = get_option('mlsimport_mls_metadata_mls_enums', ''); if ('' !== $mlsimport_mls_metadata_mls_enums) { $metadata_api_call_full = json_decode($mlsimport_mls_metadata_mls_enums, true); if (isset($metadata_api_call_full['global_array']['PropertyEnums'])) { $property_enums = $metadata_api_call_full['global_array']['PropertyEnums']; if (isset($property_enums['City']) && is_array($property_enums['City'])) { $metadata_api_call_city = $property_enums['City']; } if (isset($property_enums['CountyOrParish']) && is_array($property_enums['CountyOrParish'])) { $metadata_api_call_county = $property_enums['CountyOrParish']; } if (isset($property_enums['PropertyType']) && is_array($property_enums['PropertyType'])) { $metadata_api_call_property_type = $property_enums['PropertyType']; } } } ?> = 500): ?>
listings. If you decide to import all of them make sure your server database can handle the load. Please do a database backup before initial import.
Ready to import!
"/>

and
plugin_name . '_admin_options'); $mlsId = ''; if (isset($options['mlsimport_mls_name'])) { $mlsId = sanitize_text_field(trim($options['mlsimport_mls_name'])); } if ($mlsId > 5000) { $fieldImport['PropertyType']['multiple'] = 'no'; } if ($mlsId >= 7000) { // there is no such thing for realtor.ca unset($fieldImport['PropertyType']); } foreach ($fieldImport as $key => $field): if (!empty($field['hidden'])) { continue; } $nameCheck = strtolower('mlsimport_item_' . $key . '_check'); $name = strtolower('mlsimport_item_' . $key); $value = get_post_meta($postId, $name, true); $valueCheck = get_post_meta($postId, $nameCheck, true); $extraClass = ''; if ('extraCity' === $key || 'extraCounty' === $key) { $extraClass = ' mlsimport_hidden_field_button button mlsimport_button'; } ?>
mlsimport_saas_update_last_date( $item_id ); } return $last_date; } /** * * * Save Last date */ public function mlsimport_saas_update_last_date( $item_id ) { $unix_time = current_time( 'timestamp', 0 ) - ( 2 * 60 * 60 ); print $last_date_to_save = date( 'Y-m-d\TH:i', $unix_time ); update_post_meta( $item_id, 'mlsimport_last_date', $last_date_to_save ); return $last_date_to_save; } /** * Check and process MLSimport item for modified listings in the last 2 hours. * Optimized for memory: logs memory, unsets large arrays, and triggers garbage collection. * * @param int $item_id * @return int Number of listings found in the MLS feed, or 0 on failure. */ public function mlsimport_saas_start_cron_links_per_item( int $item_id ): int { // Log memory before start $found_items = 0; // Auto-sync only maintains tasks the realtor has imported at least once // by hand. A task created and forgotten has no 'mlsimport_spawn_status' // meta, so the cron skips it instead of pulling the whole MLS feed. if ( '' === get_post_meta( $item_id, 'mlsimport_spawn_status', true ) ) { return 0; } $last_date = $this->mlsimport_saas_get_last_date( $item_id ); print 'MLSitem id: ' . $item_id . ' - '; esc_html_e('date to consider: ','mlsimport'); print esc_html($last_date) . '. '; // Make request to MLS API $mlsrequest = $this->mlsimport_make_listing_requests( $item_id, $last_date, '', '', true ); if ( isset( $mlsrequest['results'] ) ) { $found_items = intval( $mlsrequest['results'] ); } else { delete_transient( 'mlsimport_saas_token' ); mlsimport_telemetry_set( 'last_sync_failed', time() ); mlsimport_telemetry_set( 'last_sync_failed_code', (string) ( $mlsrequest['error_code'] ?? 'unknown' ) ); } print esc_html__('We found ','mlsimport') . esc_html( $found_items ) . ' listings.
' . PHP_EOL; // Only process if items found if ( $found_items > 0 ) { $item_id_array = array( 'item_id' => $item_id, 'how_many' => 0, 'max_number' => $found_items, 'batch_counter' => 1, ); // Potentially large array, log memory before/after $attachments_to_move = (array) $this->mlsimport_saas_generate_import_requests_per_item( $item_id_array, $last_date, true ); // Store in post meta (beware if array is huge) update_post_meta( $item_id, 'mlsimport_spawn_status_cron_job', 'started' ); update_post_meta( $item_id, 'mlsimport_cron_attach_to_move_' . $item_id, $attachments_to_move ); // Save last date for next run $this->mlsimport_saas_update_last_date( $item_id ); // Prepare and pass only necessary arguments to background process $attachments_to_send = array( 'args' => array( 'attachments_to_move' => $item_id, 'item_id_array' => $item_id_array, ), ); $this->mlsimport_background_process_per_item_cron_function( $attachments_to_send['args'] ); // Unset large arrays/objects after use unset($attachments_to_move, $attachments_to_send, $mlsrequest, $item_id_array); gc_collect_cycles(); } return $found_items; } /** * Reconciliation log (optimized, batched, memory logged) */ public function mlsimport_saas_start_doing_reconciliation() { global $mlsimport, $wpdb; // Get all MLS keys in memory (we assume this is necessary for lookup) $mls_data = $this->mlsimport_saas_get_mls_reconciliation_data(); $listingKey_in_MLS = $mls_data['all_data'] ?? []; unset($mls_data); gc_collect_cycles(); if (empty($listingKey_in_MLS)) { return; } // Flip for fast lookup $listingKey_in_MLS = array_flip($listingKey_in_MLS); // Batch fetch local listings $batch = 1000; $offset = 0; $to_delete = 0; $counter = 0; $mlsimport_preload_all_mls_item_status_meta = $this->mlsimport_preload_all_mls_item_status_meta(); //print_r($mlsimport_preload_all_mls_item_status_meta); do { $local = $wpdb->get_results( $wpdb->prepare( "SELECT p.ID, listingkey_meta.meta_value AS listingkey, inserted_meta.meta_value AS mlsimport_item_inserted FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} listingkey_meta ON p.ID = listingkey_meta.post_id AND listingkey_meta.meta_key = %s LEFT JOIN {$wpdb->postmeta} inserted_meta ON p.ID = inserted_meta.post_id AND inserted_meta.meta_key = %s WHERE p.post_status NOT IN ('draft', 'trash') LIMIT %d OFFSET %d", 'ListingKey', 'MLSimport_item_inserted', $batch, $offset ), ARRAY_A ); $count = count($local); foreach ($local as $item) { $listingkey = $item['listingkey']; // not 'meta_value' anymore $property_id = $item['ID']; $mlsimportItemId = $item['mlsimport_item_inserted']; ++$counter; // IN MLS if (isset($listingKey_in_MLS[$listingkey])) { if (!empty($mlsimportItemId) && isset($mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId])) { $mlsimport_item_standardstatus = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatus'] ?? null; $mlsimport_item_standardstatusprotect = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatusprotect'] ?? null; } else { $mlsimport_item_standardstatus = null; $mlsimport_item_standardstatusprotect = null; } $keep_when_in_mls = $mlsimport->admin->theme_importer->check_if_delete_when_status_when_in_mls($property_id, $mlsimport_item_standardstatus, $mlsimport_item_standardstatusprotect); if (!$keep_when_in_mls) { ++$to_delete; $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql($property_id, $listingkey); } } else { // NOT IN MLS if (!empty($mlsimportItemId) && isset($mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId])) { $mlsimport_item_standardstatus = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatus'] ?? null; $mlsimport_item_standardstatusprotect = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatusprotect'] ?? null; } else { $mlsimport_item_standardstatus = null; $mlsimport_item_standardstatusprotect = null; } $keep = $mlsimport->admin->theme_importer->check_if_delete_when_status($property_id, $mlsimport_item_standardstatus, null, $mlsimport_item_standardstatusprotect); if (!$keep) { ++$to_delete; $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql($property_id, $listingkey); } } // Memory housekeeping unset($listingkey, $property_id, $mlsimportItemId, $mlsImportItemStatus, $mlsimport_item_standardstatusprotect, $keep_when_in_mls, $keep); if ($counter % 250 == 0) { gc_collect_cycles(); } } unset($local); gc_collect_cycles(); $offset += $batch; } while ($count === $batch); print esc_html(' to delete:' . $to_delete); // Final cleanup unset($listingKey_in_MLS); gc_collect_cycles(); return; } /** * Preload all status meta for ALL mlsimport_item posts in ONE QUERY. * Returns: [mlsimport_item_id => ['mlsimport_item_standardstatus' => ..., 'mlsimport_item_standardstatusprotect' => ...], ...] */ function mlsimport_preload_all_mls_item_status_meta() { global $wpdb; $sql = " SELECT p.ID as post_id, pm.meta_key, pm.meta_value FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = 'mlsimport_item' AND pm.meta_key IN ('mlsimport_item_standardstatus', 'mlsimport_item_standardstatusprotect') "; $rows = $wpdb->get_results($sql); $meta = []; foreach ($rows as $row) { if (!isset($meta[$row->post_id])) { $meta[$row->post_id] = [ 'mlsimport_item_standardstatus' => null, 'mlsimport_item_standardstatusprotect' => null ]; } $meta[$row->post_id][$row->meta_key] = maybe_unserialize($row->meta_value); } return $meta; } /** * * * Requestq Reconciliation log */ public function mlsimport_saas_get_mls_reconciliation_data() { $arguments = array(); $answer = $this->theme_importer->globalApiRequestCurlSaas( 'reconciliation', $arguments, 'GET' ); return $answer; } /** * * * Reconciliation get local data */ public function mlsimport_saas_get_all_meta_values($key) { global $wpdb; $result = $wpdb->get_results( $wpdb->prepare( " SELECT pm.meta_value, p.ID FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_status = 'publish' ", $key ), ARRAY_A // Lighter than OBJECT, unless you need objects ); return $result; } /* * Do api Listing Requests * * * * * */ public function mlsimport_make_listing_requests( $item_id, $last_date = '', $skip = '', $top = '', $is_hourly_sync = false ) { $options = get_option( $this->plugin_name . '_admin_options' ); $mls_id = ''; if ( isset( $options['mlsimport_mls_name'] ) ) { $mls_id = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) ); } $arguments = $this->mlsimport_saas_make_listing_requests_arguments( $item_id, $last_date, $skip, $top, $is_hourly_sync ); if ( $mls_id > 5000 && $mls_id < 6000 && ( ! isset( $arguments['property_type'] ) or ( isset( $arguments['property_type'] ) && '' === $arguments['property_type'] ) or ( isset( $arguments['property_type'][0] ) && '' === $arguments['property_type'][0] ) ) ) { return array( 'success' => false, 'type' => 'rapattoni', 'message' => esc_html__( 'This MLS requires to have one item selected from "Property Action Category" dropdown', 'mlsimport' ), ); } $potential_leght = strlen( wp_json_encode( $arguments ) ); if ( $potential_leght > 1750 ) { return array( 'success' => false, 'potential_leght' => $potential_leght, 'message' => esc_html__( 'You have too many parameters selected. Split the import beween multiple MLS Import Tasks: For ex : Import per County instead of selecting 10 cities or import listing between certain price range.', 'mlsimport' ), ); } //print_r($arguments); //print '----------------------------'.PHP_EOL; $answer = $this->theme_importer->globalApiRequestCurlSaas( 'listings', $arguments, 'POST' ); $answer['potential_leght'] = $potential_leght; // Record the pre-filter MLS feed count for telemetry. Every import path // — manual, hourly cron, and onboarding — routes through this method, so // recording here is the single rule that keeps the metric complete. if ( isset( $answer['results'] ) ) { mlsimport_telemetry_set( 'last_feed_found', (int) $answer['results'] ); } return ( $answer ); } /* * Create Api query arguments * * * * * * */ public function mlsimport_saas_make_listing_requests_arguments( $item_id, $last_date = '', $skip = '', $top = '', $is_hourly_sync = false ) { $options = get_option( $this->plugin_name . '_admin_options' ); if ( isset( $options['mlsimport_mls_name'] ) ) { $mls_id = intval( $options['mlsimport_mls_name'] ); } else { return ''; } if ( isset( $options['mlsimport_theme_used'] ) ) { $theme_id = intval( $options['mlsimport_theme_used'] ); } else { return ''; } $values = array(); $values['mls_id'] = $mls_id; $values['theme_id'] = $theme_id; if ( $is_hourly_sync ) { $values['hourly_sync'] = 1; } if ( '' !== $top ) { $values['top'] = $top; $values['skip'] = intval( $skip ); } // // add price $mlsimport_item_min_price = get_post_meta( $item_id, 'mlsimport_item_min_price', true ); $mlsimport_item_max_price = get_post_meta( $item_id, 'mlsimport_item_max_price', true ); if ( '' !== $mlsimport_item_min_price && '' !== $mlsimport_item_max_price ) { $values['list_price_min'] = floatval( $mlsimport_item_min_price ); $values['list_price_max'] = floatval( $mlsimport_item_max_price ); } // add city $values = $this->mls_import_return_multiple_param_value( 'city', $item_id, 'city', $values ); // add county $values = $this->mls_import_return_multiple_param_value( 'countyorparish', $item_id, 'county_or_parish', $values ); // add MLSAreaMajor $values = $this->mls_import_saas_add_to_parms_input( 'MLSAreaMajor', $item_id, 'mls_area_major', $values ); // add SubdivisionName $values = $this->mls_import_saas_add_to_parms_input( 'SubdivisionName', $item_id, 'subdivision_name', $values ); // add postal code $values = $this->mls_import_saas_add_to_parms_input( 'PostalCode', $item_id, 'postal_code', $values ); // add status if ( 111 !== $mls_id ) { // edmonton check $values = $this->mls_import_return_multiple_param_value( 'StandardStatus', $item_id, 'status', $values ); } // add property_subtype $values = $this->mls_import_return_multiple_param_value( 'PropertySubType', $item_id, 'property_subtype', $values ); // add property_type $values = $this->mls_import_return_multiple_param_value( 'PropertyType', $item_id, 'property_type', $values ); // rapattoni exception if ( $mls_id > 5000 && ( isset($values['property_type']) && $values['property_type'] !='' ) ) { $values = $this->mls_import_saas_add_to_parms_input( 'PropertyType', $item_id, 'property_type', $values ); $temp = $values['property_type']; $temp = str_replace( ' ', '', $temp ); $values['property_type'] = array(); $values['property_type'][] = $temp; } // add internet_entirelisting_displayyn $values = $this->mls_import_saas_add_to_parms_input( 'InternetEntireListingDisplayYN', $item_id, 'internet_entirelisting_displayyn', $values ); // add internet_address_displayyn $values = $this->mls_import_saas_add_to_parms_input( 'InternetAddressDisplayYN', $item_id, 'internet_address_displayyn', $values ); // add ListAgentKey $values = $this->mls_import_saas_add_to_parms_input( 'ListAgentKey', $item_id, 'list_agentkey', $values ); // add ListAgentKey $values = $this->mls_import_saas_add_to_parms_input( 'ListAgentMlsId', $item_id, 'list_agentmlsid', $values ); // add BuyerAgentMlsId $values = $this->mls_import_saas_add_to_parms_input( 'BuyerAgentMlsId', $item_id, 'buyer_agentmlsid', $values ); // add ListOfficeKey $values = $this->mls_import_saas_add_to_parms_input( 'ListOfficeKey', $item_id, 'list_officekey', $values ); // add ListOfficeMlsId $values = $this->mls_import_saas_add_to_parms_input( 'ListOfficeMlsId', $item_id, 'list_officemlsid', $values ); // add ListingId $values = $this->mls_import_saas_add_to_parms_input( 'ListingId', $item_id, 'listingid', $values ); //add Exclude_ListOfficeKey $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListOfficeKey', $item_id, 'exclude_list_officekey', $values ); // add Exclude_ListOfficeMlsId $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListOfficeMlsId', $item_id, 'exclude_list_officemlsid', $values ); //add Exclude_ListAgentKey $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListAgentKey', $item_id, 'exclude_list_agentkey', $values ); // add Exclude_ListAgentMlsId $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListAgentMlsId', $item_id, 'exclude_list_agentmlsid', $values ); // add CustomParameters $values = $this->mls_import_saas_add_to_parms_input( 'CustomParameters', $item_id, 'custom_parameters', $values ); // if we have realtorca if ($mls_id >= 7000 && $mls_id < 8000 && $last_date!=='') { $dateTime_realtorca = new DateTime($last_date, new DateTimeZone('UTC')); // Format with seconds and UTC timezone marker $last_date = $dateTime_realtorca->format('Y-m-d\TH:i:s.000\Z'); } if ( '' !== $last_date ) { $values['modification_time'] = $last_date; } return( $values ); } /* * * add input items to parameters array * */ public function mls_import_saas_add_to_parms_input( $key, $post_id, $new_name, $all_values ) { $name = strtolower( 'mlsimport_item_' . $key ); $value = get_post_meta( $post_id, $name, true ); if ( '' !== $value ) { $all_values[ $new_name ] = $value; } return $all_values; } /* * * add list items to parameters array * */ public function mls_import_return_multiple_param_value( $key, $post_id, $new_name, $all_values ) { $name_check = strtolower( 'mlsimport_item_' . $key . '_check' ); $name = strtolower( 'mlsimport_item_' . $key ); $value = get_post_meta( $post_id, $name, true ); // add extra county - should be moved into function if pass tests if ( 'countyorparish' === $key ) { $extracounty_values = get_post_meta( $post_id, 'mlsimport_item_extracounty', true ); if ( '' !== $extracounty_values ) { $extracounty_array = explode( ',', $extracounty_values ); if ( ! is_array( $value ) ) { if ( '' === $value ) { $value = array(); } else { $value = array( $value ); } } foreach ( $extracounty_array as $extra ) { $value[] = $extra; } } } // add extra city - should be moved into function if pass tests if ( 'city' === $key ) { $extracity_values = get_post_meta( $post_id, 'mlsimport_item_extracity', true ); if ( '' !== $extracity_values ) { $extracity_array = explode( ',', $extracity_values ); if ( ! is_array( $value ) ) { if ('' === $value ) { $value = array(); } else { $value = array( $value ); } } foreach ( $extracity_array as $extra ) { $value[] = $extra; } } } $value_check = get_post_meta( $post_id, $name_check, true ); if ( 0 === intval($value_check) && '' !== $value ) { $all_values[ $new_name ] = $value; } // status exception if ( 'status' === $new_name ) { $all_values[ $new_name ] = $value; } return $all_values; } /* * * All Enums fiels to be used on MLS import Taaks * * * * * * * */ public function mlsimport_saas_return_mls_fields() { $mlsimport_mls_metadata_mls_enums = get_option( 'mlsimport_mls_metadata_mls_enums', '' ); if ( '' === $mlsimport_mls_metadata_mls_enums ) { ?>
Please select the import fields(from MLS Import Settings) before starting a MLS import process.
array( 'label' => esc_html__( 'Select cities', 'mlsimport' ), 'description' => esc_html__( 'Select the cities from where we will import data.', 'mlsimport' ), 'type' => 'select', 'multiple' => 'yes', 'values' => $city_array, ), 'extraCity' => array( 'label' => esc_html__( 'Add extra Cities', 'mlsimport' ), 'description' => esc_html__( 'Add extra cities, separated by comma. They need to be written exactly like they are stored in MLS (for example all caps)', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', 'values' => $extracity_values, ), 'CountyOrParish' => array( 'label' => esc_html__( 'Select Counties', 'mlsimport' ), 'description' => esc_html__( 'Select the counties from where we will import data.', 'mlsimport' ), 'type' => 'select', 'multiple' => 'yes', 'values' => $county_array, 'show_extra_field' => true, ), 'extraCounty' => array( 'label' => esc_html__( 'Add extra Counties', 'mlsimport' ), 'description' => esc_html__( 'Add extra counties, separated by comma. They need to be written exactly like they are stored in MLS (for example all caps)', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', 'values' => $extracounty_values, ), 'MLSAreaMajor' => array( 'label' => esc_html__( 'MLS Area Major', 'mlsimport' ), 'description' => esc_html__( 'Filter listings by MLSAreaMajor.', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'SubdivisionName' => array( 'label' => esc_html__( 'Subdivision Name', 'mlsimport' ), 'description' => esc_html__( 'Filter listings by SubDivisionName.', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'PostalCode' => array( 'label' => esc_html__( 'Select Postal Code', 'mlsimport' ), 'description' => esc_html__( 'Enter one or more postal codes to import listings from, separated by commas (e.g. 12345, 23456).', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'PropertySubType' => array( 'label' => esc_html__( 'Select Property Category', 'mlsimport' ), 'description' => esc_html__( 'Property Category', 'mlsimport' ), 'type' => 'select', 'multiple' => 'yes', 'values' => $propertysubtype_array, ), 'PropertyType' => array( 'label' => esc_html__( 'Select Property Action Category', 'mlsimport' ), 'description' => esc_html__( 'Property Action Category', 'mlsimport' ), 'type' => 'select', 'multiple' => 'yes', 'values' => $propertytype_array, ), 'StandardStatus' => array( 'label' => esc_html__( 'Select Status', 'mlsimport' ), 'description' => __( 'The list is auto-populated with MLS available statuses. To select multiple statuses, use Ctrl (Windows) or Command (Mac).', 'mlsimport' ), 'type' => 'select', 'multiple' => 'yes', 'values' => $standardstatus_array, ), 'StandardStatusProtect' => array( 'label' => esc_html__( 'Protected Statuses', 'mlsimport' ), 'description' => __( 'Properties with these statuses will NEVER be deleted from your website during reconciliation, even if they are no longer found in the MLS. Use this to protect Closed, Expired, or other non-active listings from automatic deletion.', 'mlsimport' ), 'type' => 'select', 'multiple' => 'yes', 'values' => $standardstatus_array, ), 'InternetEntireListingDisplayYN' => array( 'label' => esc_html__( 'Internet Entire Listing Display ', 'mlsimport'), 'description' => esc_html__( 'A yes/no field that states the seller has allowed the listing to be displayed on Internet sites.', 'mlsimport' ), 'type' => 'select', 'multiple' => 'no', 'values' => array( 'yes', 'no', ), ), 'InternetAddressDisplayYN' => array( 'label' => esc_html__( 'Internet Address display', 'mlsimport' ), 'description' => esc_html__( 'A yes/no field that states the seller has allowed the listing address to be displayed on Internet sites.', 'mlsimport' ), 'type' => 'select', 'multiple' => 'no', 'values' => array( 'yes', 'no', ), ), 'ListAgentKey' => array( 'label' => esc_html__( 'ListAgentKey', 'mlsimport' ), 'description' => esc_html__( 'Import listings from a specific Agent (contact your MLS for this information)', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'ListAgentMlsId' => array( 'label' => esc_html__( 'ListAgentMlsId', 'mlsimport' ), 'description' => esc_html__( 'Import listings from a specific Agent (contact your MLS for this information)', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'BuyerAgentMlsId' => array( 'label' => esc_html__( 'BuyerAgentMlsId', 'mlsimport' ), 'description' => esc_html__( 'Import listings from a specific Buyer Agent (contact your MLS for this information)', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'ListOfficeKey' => array( 'label' => esc_html__( 'ListOfficeKey', 'mlsimport' ), 'description' => esc_html__( 'Import listings from a specific Office (contact your MLS for this information)', 'mlsimport'), 'type' => 'input', 'multiple' => 'no', ), 'ListOfficeMlsId' => array( 'label' => esc_html__( 'ListOfficeMlsId', 'mlsimport' ), 'description' => esc_html__( 'Import listings from a specific Office (contact your MLS for this information)', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'ListingId' => array( 'label' => esc_html__( 'ListingId', 'mlsimport' ), 'description' => esc_html__( 'Import One Property Only via parameter ListingID. If this does not work for you, please contact us to check if the field exists in your MLS.', 'mlsimport'), 'type' => 'input', 'multiple' => 'no', ), 'Exclude_ListOfficeMlsId' => array( 'label' => esc_html__( 'Exclude listings with ListOfficeMlsId', 'mlsimport' ), 'description' => esc_html__( 'Exclude listings that belong to one or more ListOfficeMlsId.', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'Exclude_ListOfficeKey' => array( 'label' => esc_html__( 'Exclude listings with ListOfficeKey', 'mlsimport' ), 'description' => esc_html__( 'Exclude listings that belong to one or more ListOfficeKey', 'mlsimport'), 'type' => 'input', 'multiple' => 'no', ), 'Exclude_ListAgentMlsId' => array( 'label' => esc_html__( 'Exclude listings with ListAgentMlsId', 'mlsimport' ), 'description' => esc_html__( 'Exclude listings that belong to one or more ListAgentMlsId.', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), 'Exclude_ListAgentKey' => array( 'label' => esc_html__( 'Exclude listings with ListAgentKey ', 'mlsimport' ), 'description' => esc_html__( 'Exclude listings that belong to one or more ListAgentKey ', 'mlsimport'), 'type' => 'input', 'multiple' => 'no', ), 'CustomParameters' => array( 'label' => esc_html__( 'Custom parameters', 'mlsimport' ), 'description' => esc_html__( 'Add raw query fragment parameters (for example: $filter=WaterfrontYN eq true). They will be forwarded to the RESO API request.', 'mlsimport' ), 'type' => 'input', 'multiple' => 'no', ), ); return $field_import; } /** * * * AYsnc Test */ public function mlsimport_move_files_per_item() { check_ajax_referer( 'mlsimport_item_actions', 'security' ); $post_id = 0; $how_many = 0; $max_number = 0; if(isset( $_POST['post_id'] )){ $post_id = intval( $_POST['post_id'] ); } if(isset( $_POST['how_many'] )){ $how_many = intval( $_POST['how_many'] ); } if(isset( $_POST['post_number'] )){ $max_number = intval( $_POST['post_number'] ); } $is_onboard=intval($_POST['is_onboard']); update_option( 'mlsimport_force_stop_' . $post_id, 'no', false ); $item_id_array = array( 'item_id' => $post_id, 'how_many' => $how_many, 'max_number' => $max_number, 'batch_counter' => 1, ); update_post_meta( $post_id, 'mlsimport_attach_to_move_' . $post_id, '' ); $attachments_to_move = (array) $this->mlsimport_saas_generate_import_requests_per_item( $item_id_array ); // If an error was returned from the API, sanitize and send it back to the client and stop further processing. if ( isset( $attachments_to_move['success'] ) && false === $attachments_to_move['success'] ) { if ( isset( $attachments_to_move['message'] ) ) { $attachments_to_move['message'] = wp_strip_all_tags( $attachments_to_move['message'] ); } wp_send_json( $attachments_to_move ); wp_die(); } update_post_meta( $post_id, 'mlsimport_attach_to_move_' . $post_id, $attachments_to_move ); // net stat data update_post_meta( $post_id, 'mlsimport_progress_properties', 0 ); update_post_meta( $post_id, 'mlsimport_progress_batches', 0 ); update_post_meta( $post_id, 'mlsimport_progress_memory', 0 ); $attachments_to_send = array( 'args' => array( 'attachments_to_move' => $post_id, 'item_id_array' => $item_id_array, 'is_onboard' =>$is_onboard, ), ); mlsimport_saas_single_write_import_custom_logs( 'Preparing the import. Please hold on.' . PHP_EOL ); mlsimport_debuglogs_per_plugin( 'Preparing the import. Please hold on.' . PHP_EOL ); update_post_meta( $post_id, 'mlsimport_spawn_status', 'started' ); // old as_enqueue_async_action( 'mlsimport_background_process_per_item', $attachments_to_send ); // Remove any pending async jobs for this item and enqueue a unique one //bad ideea // as_unschedule_all_actions( 'mlsimport_background_process_per_item', $attachments_to_send ); //as_enqueue_async_action( 'mlsimport_background_process_per_item', $attachments_to_send, '', true ); spawn_cron(); unset( $attachments_to_send ); // Return success response to the AJAX caller. wp_send_json( array( 'success' => true ) ); } /** * Process MLS Import attachments via background cron. * Memory-optimized with detailed memory usage logging. * * @param array $input_arg * @return void */ public function mlsimport_background_process_per_item_cron_function( $input_arg ) { global $mlsimport; $log = 'In cron processing function ->' . wp_json_encode( $input_arg['item_id_array'] ) . PHP_EOL; mlsimport_saas_single_write_import_custom_logs( $log, 'cron' ); mlsimport_saas_single_write_import_custom_logs( '[Memory] Start: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB', 'cron' ); // Load attachments to move from post meta $attachments_to_move = get_post_meta( $input_arg['item_id_array']['item_id'], 'mlsimport_cron_attach_to_move_' . $input_arg['item_id_array']['item_id'], true ); $log = '[Memory] After loading attachments: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL; mlsimport_saas_single_write_import_custom_logs( $log, 'cron' ); if (!empty($attachments_to_move) && is_array($attachments_to_move)) { foreach ($attachments_to_move as $key => $import_arguments) { // Optionally clear any cache for this batch if ( isset($GLOBALS['wp_object_cache']) ) { $GLOBALS['wp_object_cache']->delete('mlsimport_force_stop_' . $input_arg['item_id_array']['item_id'], 'options'); } $log = '[Memory] Before API batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL; mlsimport_saas_single_write_import_custom_logs( $log, 'cron' ); // API call $api_call_array = $this->theme_importer->globalApiRequestCurlSaas('listings', $import_arguments, 'POST'); $log = '[Memory] After API batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL; mlsimport_saas_single_write_import_custom_logs( $log, 'cron' ); // Parse/process response $mlsimport->admin->theme_importer->mlsimportSaasCronParseSearchArrayPerItem( $api_call_array, $input_arg['item_id_array'], $key ); // Free per-iteration memory unset($api_call_array, $import_arguments); gc_collect_cycles(); $log = '[Memory] After cleanup batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL; mlsimport_saas_single_write_import_custom_logs( $log, 'cron' ); } } mlsimport_saas_single_write_import_custom_logs('[Memory] End: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB', 'cron' ); mlsimport_saas_single_write_import_custom_logs('CRON JOB Import Completed ' . PHP_EOL, 'cron'); mlsimport_debuglogs_per_plugin('CRON JOB Import Completed ' . PHP_EOL); update_post_meta($input_arg['item_id_array']['item_id'], 'mlsimport_spawn_status', 'completed'); unset($attachments_to_move, $input_arg, $log); gc_collect_cycles(); } /** * * * Generate import Requests per item */ public function mlsimport_saas_generate_import_requests_per_item( $item_id_array, $last_date = '', $is_hourly_sync = false ) { $import_step = 25; $prop_id = $item_id_array['item_id']; $max_found = $item_id_array['max_number']; $how_many = $item_id_array['how_many']; if ( 0=== intval($how_many) ) { $how_many = $max_found; } if ( $how_many > $max_found ) { $how_many = $max_found; } $search_url_step = ''; $urls_array = array(); $skip = 0; if ( $how_many > 10000 ) { $how_many = 10000; } update_post_meta($prop_id,'mlsimport_task_to_import', intval($how_many) ); if ( $how_many < $import_step ) { $import_step = $how_many; } while ( $skip < $how_many ) { // Determine how many items to request for this batch. $batch_step = min( $import_step, $how_many - $skip ); // Build the request arguments using the remaining count. $search_url_step = $this->mlsimport_saas_make_listing_requests_arguments( $prop_id, $last_date, $skip, $batch_step, $is_hourly_sync ); // If the API returned an error, propagate it immediately. if ( isset( $search_url_step['success'] ) && false === $search_url_step['success'] ) { return $search_url_step; } $skip += $batch_step; $urls_array[] = $search_url_step; } return $urls_array; } /** * Process Async function */ public function mlsimport_background_process_per_item_function( $input_arg ) { $mlsimportItemId = $input_arg['item_id_array']['item_id']; $log_prefix = 'In processing function - Item ID: ' . $mlsimportItemId . ' -> '; mlsimport_saas_single_write_import_custom_logs( $log_prefix . wp_json_encode( $input_arg['item_id_array'] ) . PHP_EOL ); // Get from MLS Import the big argument array only once $attachments_to_move = get_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId, true ); // Retrieve all meta data in one go to reduce database queries $mlsimport_item_option_data = array( 'mlsimport_item_standardstatus' => get_post_meta( $mlsimportItemId, 'mlsimport_item_standardstatus', true ), 'mlsimport_item_standardstatusprotect' => get_post_meta( $mlsimportItemId, 'mlsimport_item_standardstatusprotect', true ), 'mlsimport_item_property_user' => get_post_meta( $mlsimportItemId, 'mlsimport_item_property_user', true ), 'mlsimport_item_agent' => get_post_meta( $mlsimportItemId, 'mlsimport_item_agent', true ), 'mlsimport_item_property_status' => get_post_meta( $mlsimportItemId, 'mlsimport_item_property_status', true ), ); $total_batches = count( $attachments_to_move ); // removed because $this global $mlsimport; $log = 'In processing function $attachments_to_move ->' . wp_json_encode( $attachments_to_move ) . PHP_EOL; mlsimport_saas_single_write_import_custom_logs( $log ); foreach ( $attachments_to_move as $key => $import_arguments ) { // reconsider use // $GLOBALS['wp_object_cache']->delete('mlsimport_force_stop_' . $input_arg['item_id_array']['item_id'], 'options'); $status = get_option( 'mlsimport_force_stop_' . $mlsimportItemId ); if ( 'no' === $status ) { // Clear memory before processing each batch wp_cache_flush(); gc_collect_cycles(); // wp_cache_flush(); $mem_usage = memory_get_usage( true ); $mem_usage_show = round( $mem_usage / 1048576, 2 ); update_post_meta( $mlsimportItemId, 'mlsimport_progress_batches', $key + 1 ); update_post_meta( $mlsimportItemId, 'mlsimport_progress_memory', $mem_usage_show ); mlsimport_saas_single_write_import_custom_logs( $log ); $log = 'Parsing import batch: ' . ( $key + 1 ) . ' of ' . $total_batches . '. Memory used: ' . $mem_usage_show . ' MB.' . PHP_EOL; // Combine logs and reduce function calls mlsimport_saas_single_write_import_custom_logs( $log ); mlsimport_debuglogs_per_plugin( $log ); print esc_html($log); $api_call_array = $this->theme_importer->globalApiRequestCurlSaas( 'listings', $import_arguments, 'POST' ); $mlsimport->admin->theme_importer->mlsimportSaasParseSearchArrayPerItem( $api_call_array, $input_arg['item_id_array'], $key, $mlsimport_item_option_data ); // Explicitly unset large variables after each batch unset($api_call_array); // Force garbage collection again after processing wp_cache_flush(); gc_collect_cycles(); // Add a small delay to allow memory to be freed if (($key + 1) < $total_batches) { usleep(100000); // 100ms pause between batches } } else { $final_mem_usage = memory_get_usage( true ); $final_mem_usage_show = round( $final_mem_usage / 1048576, 2 ); update_post_meta( $mlsimportItemId, 'mlsimport_spawn_status', 'completed' ); delete_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId ); //new stats update_post_meta( $mlsimportItemId, 'mlsimport_progress_batches', $total_batches ); update_post_meta( $mlsimportItemId, 'mlsimport_progress_memory', $final_mem_usage_show ); mlsimport_saas_single_write_import_custom_logs( PHP_EOL . 'Parsing importing link FORCE STOP : ' ); mlsimport_debuglogs_per_plugin( 'Parsing importing link FORCE STOP : ' ); break; // Exit the loop if forced to stop } } mlsimport_saas_single_write_import_custom_logs( 'Import Completed ' . PHP_EOL ); mlsimport_debuglogs_per_plugin( 'Import Completed ' . PHP_EOL ); update_post_meta( $mlsimportItemId, 'mlsimport_spawn_status', 'completed' ); delete_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId ); // Final cleanup unset($attachments_to_move); unset($mlsimport_item_option_data); unset($input_arg); unset($log); // One final garbage collection wp_cache_flush(); gc_collect_cycles(); } /** * * * * update log function */ public function mlsimport_logger_per_item() { //check_ajax_referer( 'mlsimport_item_actions', 'security' ); $post_id=0; if(isset($_POST['post_id'] )){ $post_id = intval( $_POST['post_id'] ); } $status = get_post_meta( $post_id, 'mlsimport_spawn_status', true ); $path = WP_PLUGIN_DIR . '/mlsimport/logs/status_logs.log'; $logs = file_get_contents( $path ); //get neww status data $current = intval( get_post_meta( $post_id, 'mlsimport_progress_properties', true ) ); $total = intval( get_post_meta( $post_id, 'mlsimport_progress_batches', true ) ); $memory = get_post_meta( $post_id, 'mlsimport_progress_memory', true ); $mlsimport_task_to_import = intval( get_post_meta($post_id, 'mlsimport_task_to_import',true)); $force_status = intval( get_post_meta( $post_id, 'mlsimport_force_stop', true ) ); $force_status = get_option( 'mlsimport_force_stop_' . $post_id ); if ( 'no' !== $force_status ) { echo wp_json_encode( array( 'is_done' => 'done', 'status' => $status, 'logs' => $logs, ) ); die(); } if ( '' === $status || 'completed' === $status ) { echo wp_json_encode( array( 'is_done' => 'done', 'status' => $status, 'logs' => $logs, 'mlsimport_progress_properties' => $current, 'mlsimport_task_to_import' => $total, ) ); } else { // return from log echo wp_json_encode( array( 'is_done' => 'wip', 'status' => $status, 'logs' => $logs, 'mlsimport_progress_properties' => $current, 'mlsimport_progress_batches' => $total, 'memory' => $memory, 'mlsimport_task_to_import'=>$mlsimport_task_to_import, 'post_id'=>$post_id ) ); } die(); } /** * * * * * Force Stop Import */ public function mlsimport_stop_import_per_item() { check_ajax_referer( 'mlsimport_item_actions', 'security' ); $post_id=0; if(isset($_POST['post_id'] )){ $post_id = intval( $_POST['post_id'] ); } update_option( 'mlsimport_force_stop_' . $post_id, 'yes', false ); // ensure caches are cleared so running processes see the update immediately if ( function_exists( 'wp_cache_delete' ) ) { wp_cache_delete( 'mlsimport_force_stop_' . $post_id, 'options' ); } mlsimport_saas_single_write_import_custom_logs( 'Stopped for ' . $post_id . PHP_EOL ); mlsimport_debuglogs_per_plugin( 'Stopped for ' . $post_id . PHP_EOL ); wp_send_json_success(); } /* * * Get MLS Metadata * * * * **/ public function mlsimport_saas_get_metadata_function() { check_ajax_referer( 'mlsimport_saas_get_metadata', 'security' ); $theme_Start = new ThemeImport(); $values = array(); $options = get_option( $this->plugin_name . '_admin_options' ); $url = 'clients?theme_id=' . intval( $options['mlsimport_theme_used'] ); $answer = $theme_Start::globalApiRequestSaas( $url, $values, 'GET' ); update_option( 'mlsimport_mls_metadata_populated', 'yes' ); update_option( 'mlsimport_mls_metadata_theme_schema', $answer['theme_schema'] ); update_option( 'mlsimport_mls_metadata_mls_data', $answer['mls_data']['mls_meta_data'] ); update_option( 'mlsimport_mls_metadata_mls_enums', $answer['mls_data']['mls_meta_enums'] ); } /** * * * write debug logs */ public function mlsimport_debuglog_cron( $message ) { if ( is_array( $message ) ) { $message = wp_json_encode( $message ); } $message = date( 'F j, Y, g:i a' ) . ' -> ' . $message; global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); } $path = WP_PLUGIN_DIR . '/mlsimport/logs/cron_logs.log'; file_put_contents( $path, $message, FILE_APPEND | LOCK_EX ); } }