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') . '
';
}
$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'];
}
}
}
?>
1. Set the import parameters.
2. Hit Publish or Update, otherwise import will not work correctly.
3. Click the Start Import button. Most MLS limit the import number to 1000. If you need to import more create additional import items.
4. Press the Update button after you make any change in the import settings.
= 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!
"/>
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 );
}
}