PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 6.3.8
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v6.3.8
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
mlsimport / admin / class-mlsimport-admin.php

class-mlsimport-admin.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 6.3.8, at admin/class-mlsimport-admin.php

3,205 lines 118.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly
4 }
5
6
7 /**
8 * The admin-specific functionality of the plugin.
9 *
10 * @link http://mlsimport.com/
11 * @since 1.0.0
12 *
13 * @package Mlsimport
14 * @subpackage Mlsimport/admin
15 */
16
17
18 /**
19 * The admin-specific functionality of the plugin.
20 *
21 * Defines the plugin name, version, and two examples hooks for how to
22 * enqueue the admin-specific stylesheet and JavaScript.
23 *
24 * @package Mlsimport
25 * @subpackage Mlsimport/admin
26 * @author MlsImport <office@mlsimport.com>
27 */
28 class Mlsimport_Admin {
29
30 /**
31 * The ID of this plugin.
32 *
33 * @since 1.0.0
34 * @access private
35 * @var string $plugin_name The ID of this plugin.
36 */
37 private $plugin_name;
38
39 /**
40 * The version of this plugin.
41 *
42 * @since 1.0.0
43 * @access private
44 * @var string $version The current version of this plugin.
45 */
46 private $version;
47 public $main;
48 public $theme_importer;
49 public $env_data;
50 public $mls_env_data;
51 protected $process_all;
52 public $field_import;
53 public $themes;
54 /**
55 * Initialize the class and set its properties.
56 *
57 * @since 1.0.0
58 * @param string $plugin_name The name of this plugin.
59 * @param string $version The version of this plugin.
60 */
61 public function __construct( $plugin_name, $version ) {
62
63 $this->plugin_name = $plugin_name;
64 $this->version = $version;
65
66 $this->field_import = array(
67 'City',
68 'CountyOrParish',
69 'MlsStatus',
70 'PropertySubType',
71 'PropertyType',
72 'StandardStatus',
73 'InternetEntireListingDisplayYN',
74 'InternetAddressDisplayYN',
75 );
76
77 $this->themes = array(
78 991 => 'WpResidence',
79 992 => 'Houzez',
80 993 => 'RealHomes',
81 994 => 'Wpestate',
82 );
83 }
84 /**
85 *
86 *
87 *
88 * Admin Setup
89 *
90 * @since 1.0.0
91 */
92 public function admin_setup( $plugin_name, $mls_enviroment, $theme_enviroment ) {
93
94 $options = get_option( $this->plugin_name . '_admin_options' );
95 $theme_id = 0;
96 if ( isset( $options['mlsimport_theme_used'] ) ) {
97 $theme_id = intval( $options['mlsimport_theme_used'] );
98 }
99 $themes = $this->themes;
100
101 $theme_enviroment = '';
102 if ( isset( $themes[ $theme_id ] ) ) {
103 $theme_enviroment = $themes[ $theme_id ];
104 }
105
106 $this->theme_importer = new ThemeImport( $plugin_name );
107
108 $options_api = get_option( $this->plugin_name . '_admin_options' );
109
110 if ( '' !== $theme_enviroment ) {
111 $classname = str_replace('Wp','',$theme_enviroment ). 'Class';
112 $this->env_data = new $classname();
113 } else {
114 $this->env_data = new stdClass();
115 }
116
117 if ( '' !== $mls_enviroment ) {
118 $mls_classname = $mls_enviroment . 'Class';
119
120 $this->mls_env_data = new $mls_classname( $this->theme_importer );
121 } else {
122 $this->mls_env_data = new stdClass();
123 }
124 }
125
126 /**
127 *
128 *
129 *
130 * Register the stylesheets for the admin area.
131 *
132 * @since 1.0.0
133 */
134 public function enqueue_styles() {
135 wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/mlsimport-admin.css', array(), MLSIMPORT_VERSION, 'all' );
136 wp_enqueue_style( 'mlsimport-onboarding', plugin_dir_url( __FILE__ ) . 'css/mlsimport-onboarding.css', array(), MLSIMPORT_VERSION, 'all' );
137 wp_enqueue_style( 'mlsimport-field-selector', plugin_dir_url( __FILE__ ) . 'css/mlsimport-field-selector.css', array(), MLSIMPORT_VERSION, 'all' );
138 }
139
140
141
142
143 /**
144 *
145 *
146 *
147 * Register the JavaScript for the admin area.
148 *
149 * @since 1.0.0
150 */
151 public function enqueue_scripts($hook_suffix) {
152 wp_enqueue_script( 'jquery-ui-autocomplete' );
153 $mls_import_list = mlsimport_saas_request_list();
154 wp_enqueue_script( 'mlsimport-admin', plugin_dir_url( __FILE__ ) . 'js/mlsimport-admin.js', array( 'jquery' ), $this->version, true );
155 wp_localize_script(
156 'mlsimport-admin',
157 'mlsimport_vars',
158 array(
159 'ajax_url' => admin_url( 'admin-ajax.php' )
160 )
161 );
162
163 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 );
164
165 // Pass AJAX parameters to script
166 wp_localize_script( 'mlsimport-field-selector', 'mlsimport_params', array(
167 'ajax_url' => admin_url( 'admin-ajax.php' ),
168 'nonce' => wp_create_nonce( 'mlsimport_field_selector_nonce' )
169 ));
170
171
172 wp_enqueue_script( 'mlsimport-progressive-save', plugin_dir_url( ( __FILE__ ) ) . 'js/progressive-save.js', array('mlsimport-field-selector' ), '1.0.0', true );
173
174
175
176 if ('toplevel_page_mlsimport_plugin_options' === $hook_suffix &&
177 isset($_GET['page']) && $_GET['page'] === 'mlsimport_plugin_options' &&
178 isset($_GET['tab']) && $_GET['tab'] === 'field_options') {
179 $mlsimport_mls_metadata_populated = get_option( 'mlsimport_mls_metadata_populated', '' );
180 if ( 'yes' !== $mlsimport_mls_metadata_populated ) {
181 $inline_script = 'jQuery(document).ready(function($){ mlsimport_saas_get_metadata(); });';
182 wp_add_inline_script('mlsimport-admin', $inline_script);
183 }
184 }
185
186 if (
187 'admin_page_mlsimport-onboarding' === $hook_suffix &&
188 isset($_GET['page']) && $_GET['page'] === 'mlsimport-onboarding'
189 ) {
190 $mlsimport_mls_metadata_populated = get_option('mlsimport_mls_metadata_populated', '');
191 if ('yes' !== $mlsimport_mls_metadata_populated) {
192 $inline_script = 'jQuery(document).ready(function($){ mlsimport_saas_get_metadata(); });';
193 wp_add_inline_script('mlsimport-admin', $inline_script);
194 }
195 }
196
197
198
199
200 if ('toplevel_page_mlsimport_plugin_options' === $hook_suffix &&
201 ( isset($_GET['page']) && $_GET['page'] === 'mlsimport_plugin_options' && isset($_GET['tab']) && $_GET['tab'] === 'display_options') ||
202 (isset($_GET['page']) && $_GET['page'] === 'mlsimport_plugin_options' && !isset($_GET['tab']) ) ) {
203
204 $mls_import_list = mlsimport_saas_request_list();
205 if(!is_array($mls_import_list)){
206 $inline_script = 'jQuery(document).ready(function($){ var autofill='.wp_kses_post($mls_import_list).';mlsimport_autocomplte_mls_selection(autofill); });';
207 wp_add_inline_script('mlsimport-admin', $inline_script);
208 }
209 }
210
211 // Searchable City/County multi-select — only on the Import Task edit screen.
212 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
213 $post_type = $screen ? (string) $screen->post_type : '';
214 if ( $this->mlsimport_is_import_task_edit_screen( (string) $hook_suffix, $post_type ) ) {
215 wp_enqueue_script( 'mlsimport-searchable-select', plugin_dir_url( __FILE__ ) . 'js/mlsimport-searchable-select.js', array(), MLSIMPORT_VERSION, true );
216 }
217
218 // Deactivation exit survey — only needed on the Plugins screen.
219 if ( 'plugins.php' === $hook_suffix ) {
220 wp_enqueue_script( 'mlsimport-deactivation-survey', plugin_dir_url( __FILE__ ) . 'js/mlsimport-deactivation-survey.js', array( 'jquery' ), MLSIMPORT_VERSION, true );
221 wp_localize_script( 'mlsimport-deactivation-survey', 'mlsimport_deact_survey', array(
222 'ajax_url' => admin_url( 'admin-ajax.php' ),
223 'nonce' => wp_create_nonce( 'mlsimport_exit_survey' ),
224 'plugin_basename' => plugin_basename( MLSIMPORT_PLUGIN_PATH . 'mlsimport.php' ),
225 'options' => $this->get_exit_survey_options(),
226 'i18n' => array(
227 'title' => esc_html__( 'Before you go — quick question', 'mlsimport' ),
228 'intro' => esc_html__( 'Why are you deactivating MLS Import? Your answer helps us improve.', 'mlsimport' ),
229 'other_placeholder' => esc_html__( 'Tell us more (optional)', 'mlsimport' ),
230 'submit' => esc_html__( 'Submit & Deactivate', 'mlsimport' ),
231 'skip' => esc_html__( 'Skip & Deactivate', 'mlsimport' ),
232 ),
233 ) );
234 }
235
236 }
237
238
239
240
241
242 /**
243 *
244 *
245 *
246 * Register the administration menu for this plugin into the WordPress Dashboard menu.
247 *
248 * @since 1.0.0
249 */
250 public function add_plugin_admin_menu() {
251 add_menu_page(
252 esc_html__( 'MLS Import Settings', 'mlsimport'),
253 esc_html__( 'MLS Import Settings', 'mlsimport' ),
254 'administrator',
255 'mlsimport_plugin_options',
256 array( $this, 'display_plugin_setup_page' ),
257 MLSIMPORT_PLUGIN_URL . '/img/mlsimport_menu.png',
258 22
259 );
260
261 add_submenu_page(
262 'mlsimport_plugin_options',
263 esc_html__( 'Import History', 'mlsimport' ),
264 esc_html__( 'Import History', 'mlsimport' ),
265 'administrator',
266 'mlsimport_history',
267 array( $this, 'display_history_page' )
268 );
269 }
270
271 /**
272 * Renders the Import History admin page.
273 *
274 * @return void
275 */
276 public function display_history_page() {
277 include_once plugin_dir_path( __FILE__ ) . 'partials/mlsimport-history.php';
278 }
279
280
281
282
283
284
285
286
287 /**
288 *
289 *
290 *
291 * Add settings action link to the plugins page.
292 *
293 * @since 1.0.0
294 */
295 public function add_action_links( $links ) {
296 $settings_link = array(
297 '<a href="' . admin_url( 'admin.php?page=mlsimport_plugin_options' ) . '">' . esc_html__( 'Settings', 'mlsimport') . '</a>',
298 );
299 return array_merge( $settings_link, $links );
300 }
301
302
303
304
305
306
307
308
309 /**
310 *
311 *
312 * Render the settings page for this plugin.
313 *
314 * @since 1.0.0
315 */
316 public function display_plugin_setup_page() {
317 include_once 'partials/' . $this->plugin_name . '-admin-display.php';
318 }
319
320
321
322
323
324
325 /**
326 *
327 *
328 * Validate plugin options fields
329 *
330 * @since 1.0.0
331 */
332 public function validate_admin_options( $input ) {
333
334 $valid = array();
335 $settings_list = array(
336 'auth_username' => array(
337 'name' => esc_html__( 'Api auth_username ', 'mlsimport' ),
338 'details' => 'to be added',
339 ),
340 'auth_password' => array(
341 'name' => esc_html__( 'Api auth_password', 'mlsimport' ),
342 'details' => 'to be added',
343 ),
344 'client_id' => array(
345 'name' => esc_html__( 'Api client_id', 'mlsimport' ),
346 'details' => 'to be added',
347 ),
348 'client_secret' => array(
349 'name' => esc_html__( 'client_secret', 'mlsimport' ),
350 'details' => 'to be added',
351 ),
352 'redirect_uri' => array(
353 'name' => esc_html__( 'redirect_uri', 'mlsimport' ),
354 'details' => 'to be added',
355 ),
356 'title_format' => array(
357 'name' => esc_html__( 'title_format', 'mlsimport' ),
358 'details' => 'to be added',
359 ),
360 'force_rand' => array(
361 'name' => esc_html__( 'title_format', 'mlsimport' ),
362 'details' => 'to be added',
363 ),
364 'mlsimport_username' => array(
365 'name' => esc_html__( 'MLSImport.com Username (not your email)', 'mlsimport' ),
366 'details' => 'to be added',
367 ),
368 'mlsimport_password' => array(
369 'name' => esc_html__( 'MLSImport.com Password', 'mlsimport' ),
370 'details' => 'to be added',
371 ),
372 'mlsimport_mls_name' => array(
373 'name' => esc_html__( 'MLSImport Name', 'mlsimport' ),
374 'details' => 'to be added',
375 ),
376 'mlsimport_mls_token' => array(
377 'name' => esc_html__( 'MLSImport Token', 'mlsimport' ),
378 'details' => 'to be added',
379 ),
380
381 'mlsimport_tresle_client_id' => array(
382 'name' => esc_html__( 'MLSImport Tresle Client id', 'mlsimport' ),
383 'details' => 'to be added',
384 ),
385
386 'mlsimport_tresle_client_secret' => array(
387 'name' => esc_html__( 'MLSImport Client Secret', 'mlsimport' ),
388 'details' => 'to be added',
389 ),
390
391 'mlsimport_connectmls_username' => array(
392 'name' => esc_html__( 'MLSImport ConnectMLS Username', 'mlsimport' ),
393 'details' => 'to be added',
394 ),
395
396 'mlsimport_connectmls_password' => array(
397 'name' => esc_html__( 'MLSImport ConnectMLS Password', 'mlsimport' ),
398 'details' => 'to be added',
399 ),
400
401 'mlsimport_rapattoni_client_id' => array(
402 'name' => esc_html__( 'MLSImport Rapattoni Client id','mlsimport'),
403 'details' => 'to be added',
404 ),
405
406 'mlsimport_rapattoni_client_secret' => array(
407 'name' => esc_html__( 'MLSImport Rapattoni Secret', 'mlsimport' ),
408 'details' => 'to be added',
409 ),
410
411 'mlsimport_rapattoni_username' => array(
412 'name' => esc_html__( 'MLSImport Rapattoni Username', 'mlsimport' ),
413 'details' => 'to be added',
414 ),
415
416 'mlsimport_rapattoni_password' => array(
417 'name' => esc_html__( 'MLSImport Rapattoni Password', 'mlsimport' ),
418 'details' => 'to be added',
419 ),
420
421 'mlsimport_paragon_client_id' => array(
422 'name' => esc_html__( 'MLSImport Paragon Client id','mlsimport' ),
423 'details' => 'to be added',
424 ),
425
426 'mlsimport_paragon_client_secret' => array(
427 'name' => esc_html__( 'MLSImport Paragon Secret', 'mlsimport' ),
428 'details' => 'to be added',
429 ),
430 'mlsimport_realtorca_client_id' => array(
431 'name' => esc_html__( 'MLSImport Realtor.ca Client id','mlsimport' ),
432 'details' => 'to be added',
433 ),
434
435 'mlsimport_realtorca_client_secret' => array(
436 'name' => esc_html__( 'MLSImport Realtor.ca Secret', 'mlsimport' ),
437 'details' => 'to be added',
438 ),
439 'mlsimport_brightmls_client_id' => array(
440 'name' => esc_html__( 'MLSImport BrightMLS Client id', 'mlsimport' ),
441 'details' => 'to be added',
442 ),
443
444 'mlsimport_brightmls_client_secret' => array(
445 'name' => esc_html__( 'MLSImport BrightMLS Secret', 'mlsimport' ),
446 'details' => 'to be added',
447 ),
448
449 'mlsimport_theme_used' => array(
450 'name' => esc_html__( 'Your Wordpress Theme', 'mlsimport' ),
451 'details' => 'to be added',
452 ),
453 'mlsimport_mls_name_front' => array(
454 'name' => '',
455 'details' => 'to be added',
456 ),
457 'mlsimport-disable-logs' => array(
458 'name' => '',
459 'details' => 'to be added',
460 ),
461 );
462
463 foreach ( $settings_list as $key => $setting ) {
464 $valid[ $key ] = ( isset( $input[ $key ] ) && ! empty( $input[ $key ] ) ) ? esc_attr( $input[ $key ] ) : '';
465 }
466
467 delete_option( 'mlsimport_connection_test' );
468 delete_option( 'mlsimport_mls_metadata_populated' );
469
470 update_option( 'mlsimport_encoding_array', '' );
471 delete_transient( 'mlsimport_token_request' );
472 delete_transient( 'mlsimport_schema' );
473 delete_transient( 'mlsimport_plugin_data_schema' );
474
475 delete_transient( 'mlsimport_saas_token' );
476 return $valid;
477 }
478
479
480
481
482
483 /**
484 *
485 *
486 * Validate admin fields
487 *
488 * @since 1.0.0
489 */
490 public function validate_admin_fields_select( $input ) {
491 $valid = array();
492
493 $mlsimport_mls_metadata_mls_data = get_option( 'mlsimport_mls_metadata_mls_data', '' );
494 $metadata_api_call = json_decode( $mlsimport_mls_metadata_mls_data, true );
495
496 foreach ( $metadata_api_call as $key => $value ) {
497 if ( isset( $input['mls-fields'][ $key ] ) ) {
498 $valid['mls-fields'][ $key ] = esc_attr( $input['mls-fields'][ $key ] );
499 }
500
501 if ( isset( $input['mls-fields-admin'][ $key ] ) ) {
502 $valid['mls-fields-admin'][ $key ] = esc_attr( $input['mls-fields-admin'][ $key ] );
503 $valid['mls-fields-label'][ $key ] = esc_attr( $input['mls-fields-label'][ $key ] );
504 $valid['mls-fields-map-postmeta'][ $key ] = esc_attr( $input['mls-fields-map-postmeta'][ $key ] );
505 $valid['mls-fields-map-taxonomy'][ $key ] = esc_attr( $input['mls-fields-map-taxonomy'][ $key ] );
506 $valid['field_order'][ $key ] = esc_attr( $input['field_order'][ $key ] );
507 }
508 }
509 //$valid['mls-fields-admin']['force_rand'] = esc_attr( $input['mls-fields-admin']['force_rand'] );
510 return $valid;
511 }
512
513 /**
514 *
515 *
516 * Validate Mls Sync fields
517 *
518 * @since 1.0.0
519 */
520 public function validate_admin_mls_sync( $input ) {
521 $valid = array();
522
523 $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',
524 'StandardStatus_delete', 'StandardStatus_delete_check', 'InternetEntireListingDisplayYN', 'InternetAddressDisplayYN' );
525 foreach ( $field_import as $key ) {
526 $valid[ $key ] = $input[ $key ];
527 }
528
529 return $valid;
530 }
531
532
533 /**
534 *
535 *
536 * Validate Administrative options
537 *
538 * @since 1.0.0
539 */
540 public function validate_administrative_options( $input ) {
541
542 $valid = array();
543
544 $field_import = array( 'import' );
545 foreach ( $field_import as $key ) {
546 $valid[ $key ] = $input[ $key ];
547 }
548
549 return $valid;
550 }
551
552 /**
553 *
554 *
555 *
556 * Validate Import Options fields
557 *
558 * @since 1.0.0
559 */
560 public function validate_admin_import_options( $input ) {
561 $valid = array();
562
563 $field_import = array( 'import_number' );
564 foreach ( $field_import as $key ) {
565 $valid[ $key ] = intval( $input[ $key ] );
566 }
567
568 if ( isset( $input['import'] ) && '' !== $input['import'] ) {
569 $decode = json_decode( $input['import'] );
570 update_option( 'mlsimport_admin_fields_select', $decode['mlsimport_admin_fields_select'] );
571 update_option( 'mlsimport_admin_mls_sync', $decode['mlsimport_admin_mls_sync'] );
572 update_option( 'mlsimport_admin_import_options', $decode['mlsimport_admin_import_options'] );
573 update_option( 'mlsimport_admin_use_transients', $decode['mlsimport_admin_use_transients'] );
574 }
575
576 return $valid;
577 }
578
579
580
581
582
583
584 /**
585 *
586 *
587 * plugin options update
588 */
589 public function options_update() {
590 register_setting( $this->plugin_name . '_admin_options', $this->plugin_name . '_admin_options', array( $this, 'validate_admin_options' ) );
591 register_setting( $this->plugin_name . '_admin_fields_select', $this->plugin_name . '_admin_fields_select', array( $this, 'validate_admin_fields_select' ) );
592 register_setting( $this->plugin_name . '_admin_mls_sync', $this->plugin_name . '_admin_mls_sync', array( $this, 'validate_admin_mls_sync' ) );
593 register_setting( $this->plugin_name . '_admin_import_options', $this->plugin_name . '_admin_import_options', array( $this, 'validate_admin_import_options' ) );
594 register_setting( $this->plugin_name . '_administrative_options', $this->plugin_name . '_administrative_options', array( $this, 'validate_administrative_options' ) );
595 }
596
597
598
599 /**
600 *
601 *
602 *
603 *
604 *
605 *
606 *
607 */
608 public function update_option_mlsimport_administrative_options() {
609 $import = get_option( 'mlsimport_administrative_options' );
610 if ( '' !== $import ) {
611 $decode = json_decode( $import['import'], true );
612 update_option( 'mlsimport_admin_fields_select', $decode['mlsimport_admin_fields_select'] );
613 update_option( 'mlsimport_admin_mls_sync', $decode['mlsimport_admin_mls_sync'] );
614 update_option( 'mlsimport_admin_import_options', $decode['mlsimport_admin_import_options'] );
615 }
616 }
617
618 /**
619 *
620 *
621 * plugin options update
622 */
623 public function update_option_mlsimport_admin_fields_select() {
624
625 $this->env_data->enviroment_custom_fields( $this->plugin_name );
626 }
627
628
629 /**
630 *
631 *
632 * plugin options update
633 */
634 public function mlsimport_meta_options() {
635 if ( method_exists( $this->env_data, 'get_property_post_type' ) ) {
636 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' );
637 }
638 }
639
640 /**
641 *
642 *
643 * plugin options update
644 */
645 public function mlsimport_hidden_fields() {
646 global $post;
647
648 $options = get_option( $this->plugin_name . '_admin_fields_select' );
649
650 $MLSimport_item_inserted = get_post_meta( $post->ID, 'MLSimport_item_inserted', true );
651 $MLSimport_item_updated = get_post_meta( $post->ID, 'MLSimport_item_updated', true );
652 $listing_key = get_post_meta( $post->ID, 'ListingKey', true );
653
654 // Get the import task ID to retrieve protected statuses
655 $import_task_id = !empty( $MLSimport_item_inserted ) ? $MLSimport_item_inserted : ( !empty( $MLSimport_item_updated ) ? $MLSimport_item_updated : null );
656 $mlsImportItemStatusProtect = $import_task_id ? get_post_meta( $import_task_id, 'mlsimport_item_standardstatusprotect', true ) : null;
657
658 // Check if the ListingKey exists
659 if ( !empty( $listing_key ) ) {
660 echo 'ListingKey: ' . esc_html( $listing_key ) . '<br>';
661 }
662
663 // Check if MLSimport_item_inserted exists
664 if ( !empty( $MLSimport_item_inserted ) ) {
665 echo 'Added via MLS item id: ' . esc_html( $MLSimport_item_inserted ) . ' - ' . esc_html( get_the_title( $MLSimport_item_inserted ) ) . '<br>';
666 }
667
668 // Check if MLSimport_item_updated exists
669 if ( !empty( $MLSimport_item_updated ) ) {
670 echo 'Updated via MLS item id: ' . esc_html( $MLSimport_item_updated ) . ' - ' . esc_html( get_the_title( $MLSimport_item_updated ) ) . '<br>';
671 }
672
673 if(!empty($mlsImportItemStatusProtect)) {
674 if(is_array($mlsImportItemStatusProtect)) {
675 echo 'Protected statuses: ' . esc_html( implode(', ', $mlsImportItemStatusProtect) ) . '<br>';
676 } else {
677 echo 'Protected statuses: ' . esc_html($mlsImportItemStatusProtect) . '<br>';
678 }
679 }
680
681 foreach ( $options['mls-fields-admin'] as $key => $value ) {
682 if ( 1 === intval($options['mls-fields-admin'][ $key ] ) ) {
683 $display_label = $key;
684 if ( isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ) {
685 $display_label = $options['mls-fields-label'][ $key ];
686 }
687
688 if ( 'ListingKey' !== $key ) {
689 $meta_key = strtolower( $key );
690 } else {
691 $meta_key = $key;
692 }
693 ?>
694
695 <strong><?php echo esc_html($display_label);?>:</strong>
696 <?php echo esc_html( get_post_meta( $post->ID, $meta_key, true ) ); ?> </br>
697 <?php
698 }
699 }
700 ?>
701
702 <h2 style="font-weight:bold;padding-left:0px;">Mls Import History</h2>
703 <?php
704 $meta = get_post_meta( $post->ID, 'mlsimport_property_history', true );
705 if ( '' === trim( $meta ) ) { ?>
706 <strong>Property history is blank - you can enable it in Settings/ Tools page </strong>
707 <?php
708 } else {
709 print wp_kses_post($meta);
710 }
711 }
712
713
714
715
716 /**
717 * delete cache
718 */
719 function mlsimport_delete_cache() {
720
721 check_ajax_referer( 'mlsimport_tool_actions', 'security' );
722
723 delete_transient( 'mlsimport_token_request' );
724 delete_transient( 'mlsimport_metadata_api_call_data_service_property' );
725 delete_transient( 'mls_import_meta_enums' );
726 delete_transient( 'mls_import_meta' );
727 delete_transient( 'mlsimport_plugin_data_schema' );
728 delete_transient( 'mlsimport_ready_to_go_mlsimport_data' );
729 delete_transient( 'mlsimport_saas_token' );
730
731 delete_option( 'mlsimport_mls_metadata_populated' );
732
733 die( 'deleted' );
734 }
735
736 /**
737 * clear fields data
738 */
739 function mlsimport_clear_fields_data() {
740
741 check_ajax_referer( 'mlsimport_tool_actions', 'security' );
742
743 delete_option( 'mlsimport_mls_metadata_populated' );
744 delete_option( 'mlsimport_admin_fields_select' );
745
746 die( 'deleted' );
747 }
748
749 /**
750 *
751 *
752 *
753 *
754 * delete properties
755 */
756 function mlsimport_get_taxonomy_terms() {
757 check_ajax_referer( 'mlsimport_tool_actions', 'security' );
758 if ( ! current_user_can( 'administrator' ) ) {
759 wp_send_json_error( 'Unauthorized' );
760 }
761
762 $taxonomy = sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) );
763 if ( ! taxonomy_exists( $taxonomy ) ) {
764 wp_send_json_error( 'Invalid taxonomy' );
765 }
766
767 $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'orderby' => 'name' ) );
768 $result = array();
769 if ( ! is_wp_error( $terms ) ) {
770 foreach ( $terms as $term ) {
771 $result[] = array(
772 'slug' => $term->slug,
773 'name' => $term->name,
774 'count' => $term->count,
775 );
776 }
777 }
778 wp_send_json_success( $result );
779 }
780
781 function mlsimport_delete_properties() {
782 global $mlsimport;
783
784 check_ajax_referer( 'mlsimport_tool_actions', 'security' );
785
786 if ( ! current_user_can( 'administrator' ) ) {
787 wp_send_json_error( 'Unauthorized' );
788 }
789
790 $taxonomy = sanitize_text_field( wp_unslash( $_POST['mlsimport_delete_category'] ) );
791 $terms = array();
792
793 if ( isset( $_POST['mlsimport_delete_category_term'] ) && is_array( $_POST['mlsimport_delete_category_term'] ) ) {
794 foreach ( $_POST['mlsimport_delete_category_term'] as $term ) {
795 $terms[] = sanitize_text_field( wp_unslash( $term ) );
796 }
797 }
798
799 if ( '' === $taxonomy ) {
800 wp_send_json_error( esc_html__( 'Please select a taxonomy', 'mlsimport' ) );
801 }
802
803 if ( empty( $terms ) ) {
804 wp_send_json_error( esc_html__( 'Please select at least one term', 'mlsimport' ) );
805 }
806
807 $post_type = $mlsimport->admin->env_data->get_property_post_type();
808
809 $args = array(
810 'post_type' => $post_type,
811 'post_status' => 'any',
812 'posts_per_page' => 20,
813 'tax_query' => array(
814 array(
815 'taxonomy' => $taxonomy,
816 'field' => 'slug',
817 'terms' => $terms,
818 ),
819 ),
820 'fields' => 'ids',
821 );
822
823 $prop_selection = new WP_Query( $args );
824 $deleted = 0;
825
826 foreach ( $prop_selection->posts as $delete_id ) {
827 $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql( $delete_id, ' delete from tools ' );
828 ++$deleted;
829 }
830
831 $remaining = $prop_selection->found_posts - $deleted;
832 $done = ( $remaining <= 0 );
833
834 // Update term counts only when all deletions are complete
835 if ( $done ) {
836 $all_taxonomies = get_object_taxonomies( $post_type );
837 foreach ( $all_taxonomies as $tax_name ) {
838 $all_terms = get_terms( array( 'taxonomy' => $tax_name, 'hide_empty' => false, 'fields' => 'ids' ) );
839 if ( ! is_wp_error( $all_terms ) && ! empty( $all_terms ) ) {
840 wp_update_term_count_now( $all_terms, $tax_name );
841 }
842 }
843 }
844
845 wp_send_json_success( array(
846 'deleted' => $deleted,
847 'remaining' => max( 0, $remaining ),
848 'total' => $prop_selection->found_posts,
849 'done' => $done,
850 ) );
851 }
852
853
854
855
856
857
858
859
860 /**
861 *
862 *
863 * Testing enviroment variables
864 */
865 public function mlsimport_saas_setting_up() {
866 // Do not output warnings during AJAX requests
867 if ( ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) ||
868 ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
869 return;
870 }
871
872 $is_onboarding = isset( $_GET['page'] ) && 'mlsimport-onboarding' === $_GET['page'];
873 if ( ! $is_onboarding && intval( WP_MEMORY_LIMIT ) < 256 ) :
874 if (intval(WP_MEMORY_LIMIT) < 256){ ?>
875 <div class="mlsimport_warning long_warning">
876 <strong>WordPress Memory Limit</strong> is set to <strong><?php echo esc_html(WP_MEMORY_LIMIT); ?></strong>. Allocated Memory should be at least <strong>256MB</strong>. Please refer to: <a href="https://wordpress.org/support/article/editing-wp-config-php/#increasing-memory-allocated-to-php" target="_blank">Increasing memory allocated to PHP</a>
877 </div>
878 <?php
879 }
880
881 $max_time = ini_get('max_execution_time');
882 if ($max_time < 600 && 0 !== $max_time){
883 ?>
884 <div class="mlsimport_warning long_warning">Your <strong>max_execution_time</strong> setting in php is set to <strong><?php echo esc_html($max_time); ?></strong>. Importing hundreds of listings requires extra time. Please set max_execution_time to <strong>0 (unlimited)</strong>. If that is not possible, set it to a minimum of <strong>600 (10 minutes)</strong>.</div>
885
886 <?php
887 }
888
889
890 endif; // emd pn boardingin check
891
892 }
893
894 /**
895 * Check if token validates with MLS
896 *
897 * @since 4.0.1
898 * returns token fron mlsimport
899 */
900 public function mlsimport_saas_check_mls_connection() {
901
902 $values = array();
903 $options = get_option( $this->plugin_name . '_admin_options' );
904
905 $mls_id = '';
906 if ( isset( $options['mlsimport_mls_name'] ) ) {
907 $mls_id = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) );
908 }
909
910 $mls_token = '';
911 if ( isset( $options['mlsimport_mls_name'] ) ) {
912 $mls_token = sanitize_text_field( trim( $options['mlsimport_mls_token'] ) );
913 }
914
915 $mls_id_int = intval( $mls_id );
916
917 $mlsimport_tresle_client_id = '';
918 if ( isset( $options['mlsimport_tresle_client_id'] ) ) {
919 $mlsimport_tresle_client_id = sanitize_text_field( trim( $options['mlsimport_tresle_client_id'] ) );
920 }
921
922 $mlsimport_tresle_client_secret = '';
923 if ( isset( $options['mlsimport_tresle_client_secret'] ) ) {
924 $mlsimport_tresle_client_secret = sanitize_text_field( trim( $options['mlsimport_tresle_client_secret'] ) );
925 }
926
927 $mlsimport_connectmls_username = '';
928 if ( isset( $options['mlsimport_connectmls_username'] ) ) {
929 $mlsimport_connectmls_username = sanitize_text_field( trim( $options['mlsimport_connectmls_username'] ) );
930 }
931
932 $mlsimport_connectmls_password = '';
933 if ( isset( $options['mlsimport_connectmls_password'] ) ) {
934 $mlsimport_connectmls_password = sanitize_text_field( trim( $options['mlsimport_connectmls_password'] ) );
935 }
936
937 // rapattoni data
938 $mlsimport_rapattoni_client_id = '';
939 if ( isset( $options['mlsimport_rapattoni_client_id'] ) ) {
940 $mlsimport_rapattoni_client_id = sanitize_text_field( trim( $options['mlsimport_rapattoni_client_id'] ) );
941 }
942 $mlsimport_rapattoni_client_secret = '';
943 if ( isset( $options['mlsimport_rapattoni_client_secret'] ) ) {
944 $mlsimport_rapattoni_client_secret = sanitize_text_field( trim( $options['mlsimport_rapattoni_client_secret'] ) );
945 }
946
947 $mlsimport_rapattoni_username = '';
948 if ( isset( $options['mlsimport_rapattoni_username'] ) ) {
949 $mlsimport_rapattoni_username = sanitize_text_field( trim( $options['mlsimport_rapattoni_username'] ) );
950 }
951
952 $mlsimport_rapattoni_password = '';
953 if ( isset( $options['mlsimport_rapattoni_password'] ) ) {
954 $mlsimport_rapattoni_password = sanitize_text_field( trim( $options['mlsimport_rapattoni_password'] ) );
955 }
956
957 // paragon data
958 $mlsimport_paragon_client_id = '';
959 if ( isset( $options['mlsimport_paragon_client_id'] ) ) {
960 $mlsimport_paragon_client_id = sanitize_text_field( trim( $options['mlsimport_paragon_client_id'] ) );
961 }
962 $mlsimport_paragon_client_secret = '';
963 if ( isset( $options['mlsimport_paragon_client_secret'] ) ) {
964 $mlsimport_paragon_client_secret = sanitize_text_field( trim( $options['mlsimport_paragon_client_secret'] ) );
965 }
966
967 // realtor.ca data
968 $mlsimport_realtorca_client_id = '';
969 if ( isset( $options['mlsimport_realtorca_client_id'] ) ) {
970 $mlsimport_realtorca_client_id = sanitize_text_field( trim( $options['mlsimport_realtorca_client_id'] ) );
971 }
972 $mlsimport_realtorca_client_secret = '';
973 if ( isset( $options['mlsimport_realtorca_client_secret'] ) ) {
974 $mlsimport_realtorca_client_secret = sanitize_text_field( trim( $options['mlsimport_realtorca_client_secret'] ) );
975 }
976
977 // brightmls data
978 $mlsimport_brightmls_client_id = '';
979 if ( isset( $options['mlsimport_brightmls_client_id'] ) ) {
980 $mlsimport_brightmls_client_id = sanitize_text_field( trim( $options['mlsimport_brightmls_client_id'] ) );
981 }
982 $mlsimport_brightmls_client_secret = '';
983 if ( isset( $options['mlsimport_brightmls_client_secret'] ) ) {
984 $mlsimport_brightmls_client_secret = sanitize_text_field( trim( $options['mlsimport_brightmls_client_secret'] ) );
985 }
986
987
988
989
990
991
992
993 if ( trim( $mls_token ) === '' ) {
994 if ( $this->mlsimport_is_brightmls_provider( $mls_id_int ) ) { // BrightMLS
995 if ( trim( $mlsimport_brightmls_client_id ) === '' || trim( $mlsimport_brightmls_client_secret ) === '' ) {
996 return;
997 }
998 } elseif ( $mls_id_int > 900 && $mls_id_int < 3000 ) { // Trestle
999 if ( trim( $mlsimport_tresle_client_id ) === '' || trim( $mlsimport_tresle_client_secret ) === '' ) {
1000 return;
1001 }
1002 } elseif ( $this->mlsimport_is_connectmls_provider( $mls_id_int ) ) { // ConnectMLS
1003 if (
1004 trim( $mlsimport_connectmls_username ) === '' ||
1005 trim( $mlsimport_connectmls_password ) === ''
1006 ) {
1007 return;
1008 }
1009 } elseif ( $mls_id_int >= 5000 && $mls_id_int < 6000 ) { // Rapattoni
1010 if (
1011 trim( $mlsimport_rapattoni_client_id ) === '' ||
1012 trim( $mlsimport_rapattoni_client_secret ) === '' ||
1013 trim( $mlsimport_rapattoni_username ) === '' ||
1014 trim( $mlsimport_rapattoni_password ) === ''
1015 ) {
1016 return;
1017 }
1018 } elseif ( $mls_id_int >= 6000 && $mls_id_int < 7000 ) { // Paragon
1019 if (
1020 trim( $mlsimport_paragon_client_id ) === '' ||
1021 trim( $mlsimport_paragon_client_secret ) === ''
1022 ) {
1023 return;
1024 }
1025 } elseif ( $mls_id_int >= 7000 && $mls_id_int < 8000 ) { // Realtor.ca
1026 if (
1027 trim( $mlsimport_realtorca_client_id ) === '' ||
1028 trim( $mlsimport_realtorca_client_secret ) === ''
1029 ) {
1030 return;
1031 }
1032 } elseif ( mlsimport_is_proptx_provider( $mls_id_int ) ) { // PropTx / AMPRE - static bearer token is the only credential
1033 return;
1034 }
1035 }
1036
1037 $values['mls_token'] = $mls_token;
1038 $values['mls_id'] = $mls_id;
1039 $values['mlsimport_tresle_client_id'] = $mlsimport_tresle_client_id;
1040 $values['mlsimport_tresle_client_secret'] = $mlsimport_tresle_client_secret;
1041 $values['mlsimport_connectmls_username'] = $mlsimport_connectmls_username;
1042 $values['mlsimport_connectmls_password'] = $mlsimport_connectmls_password;
1043
1044 $values['mlsimport_rapattoni_client_id'] = $mlsimport_rapattoni_client_id;
1045 $values['mlsimport_rapattoni_client_secret'] = $mlsimport_rapattoni_client_secret;
1046 $values['mlsimport_rapattoni_username'] = $mlsimport_rapattoni_username;
1047 $values['mlsimport_rapattoni_password'] = $mlsimport_rapattoni_password;
1048
1049 $values['mlsimport_paragon_client_id'] = $mlsimport_paragon_client_id;
1050 $values['mlsimport_paragon_client_secret'] = $mlsimport_paragon_client_secret;
1051
1052
1053 $values['mlsimport_realtorca_client_id'] = $mlsimport_realtorca_client_id;
1054 $values['mlsimport_realtorca_client_secret'] = $mlsimport_realtorca_client_secret;
1055
1056 $values['mlsimport_brightmls_client_id'] = $mlsimport_brightmls_client_id;
1057 $values['mlsimport_brightmls_client_secret'] = $mlsimport_brightmls_client_secret;
1058
1059
1060
1061
1062
1063
1064
1065 $answer = $this->theme_importer->globalApiRequestSaas( 'clients', $values, 'PATCH' );
1066
1067
1068
1069
1070 if ( isset( $answer['success'] ) && true === $answer['success'] ) {
1071 if ( isset( $answer['tested'] ) && true === $answer['tested'] ) {
1072 update_option( 'mlsimport_connection_test', 'yes' );
1073 mlsimport_telemetry_set_once( 'mls_connected_at', time() );
1074 } else {
1075 delete_option( 'mlsimport_connection_test' );
1076 delete_option( 'mlsimport_mls_metadata_populated' );
1077 }
1078 } else {
1079 delete_option( 'mlsimport_connection_test' );
1080 delete_option( 'mlsimport_mls_metadata_populated' );
1081 }
1082
1083 return $answer;
1084 }
1085
1086 private function mlsimport_is_connectmls_provider( $mls_id_int ) {
1087 return $mls_id_int >= 8000 && $mls_id_int < 9000 && 8001 !== (int) $mls_id_int;
1088 }
1089
1090 private function mlsimport_is_brightmls_provider( int $mls_id_int ): bool {
1091 return 8001 === $mls_id_int;
1092 }
1093
1094 /**
1095 * AJAX handler for the plugin-deactivation exit survey.
1096 *
1097 * Thin wrapper: it verifies the nonce and capability, sanitizes input,
1098 * delegates the real work to mlsimport_exit_survey_record(), and POSTs
1099 * the result to the SaaS API. The POST is fire-and-forget — a failed or
1100 * not-yet-deployed endpoint must never stop the admin from deactivating.
1101 */
1102 public function mlsimport_exit_survey_submit() {
1103 check_ajax_referer( 'mlsimport_exit_survey', 'security' );
1104 if ( ! current_user_can( 'administrator' ) ) {
1105 wp_send_json_error( 'Unauthorized' );
1106 }
1107
1108 $input = array(
1109 'reason' => sanitize_text_field( wp_unslash( $_POST['reason'] ?? '' ) ),
1110 'details' => sanitize_textarea_field( wp_unslash( $_POST['details'] ?? '' ) ),
1111 );
1112
1113 // Only record a recognized reason; an unknown value is dropped
1114 // silently rather than blocking the user or storing junk.
1115 if ( $this->mlsimport_exit_survey_is_valid_reason( $input['reason'] ) ) {
1116 $payload = $this->mlsimport_exit_survey_record( $input );
1117 try {
1118 ThemeImport::globalApiRequestSaas( 'user-activity', $payload, 'POST' );
1119 } catch ( \Throwable $e ) {
1120 // Swallow: deactivation proceeds regardless of transport failure.
1121 }
1122 }
1123
1124 wp_send_json_success();
1125 }
1126
1127 /**
1128 * Whether a submitted exit-survey reason is one of the known options.
1129 *
1130 * Pure predicate — no WordPress functions, no translation.
1131 */
1132 private function mlsimport_exit_survey_is_valid_reason( string $reason ): bool {
1133 return in_array( $reason, $this->mlsimport_exit_survey_reasons(), true );
1134 }
1135
1136 /**
1137 * Whether the current admin request is the Import Task (mlsimport_item)
1138 * post edit screen. Used to scope the Select2 asset enqueue so the
1139 * searchable-select library does not load across all of wp-admin.
1140 *
1141 * @param string $hook_suffix Current admin page hook suffix.
1142 * @param string $post_type Post type of the screen being rendered.
1143 */
1144 private function mlsimport_is_import_task_edit_screen( string $hook_suffix, string $post_type ): bool {
1145 return 'mlsimport_item' === $post_type
1146 && in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true );
1147 }
1148
1149 /**
1150 * Extra CSS class for an Import Task select field. City and County lists
1151 * can hold 300+ entries, so they are upgraded to a searchable multi-select
1152 * (Select2) via this marker class; every other field keeps the plain select.
1153 *
1154 * @param string $field_key Field key from the $field_import definition.
1155 * @return string Leading-space class string, or '' when not searchable.
1156 */
1157 private function mlsimport_searchable_select_class( string $field_key ): string {
1158 return in_array( $field_key, array( 'City', 'CountyOrParish' ), true )
1159 ? ' mlsimport-searchable-select'
1160 : '';
1161 }
1162
1163 /**
1164 * Exit-survey testable core: resolve identity and count, build payload.
1165 *
1166 * Calls no dying functions — the AJAX wrapper handles nonce/capability
1167 * and wp_send_json_*. Always returns the payload to transmit.
1168 *
1169 * @param array $input Sanitized survey input (reason, details).
1170 */
1171 private function mlsimport_exit_survey_record( array $input ): array {
1172 $opts = get_option( 'mlsimport_admin_options', array() );
1173 if ( empty( $opts['mlsimport_install_uuid'] ) ) {
1174 $opts['mlsimport_install_uuid'] = wp_generate_uuid4();
1175 update_option( 'mlsimport_admin_options', $opts );
1176 }
1177
1178 $count = (int) get_option( 'mlsimport_deactivation_count', 0 ) + 1;
1179 update_option( 'mlsimport_deactivation_count', $count );
1180
1181 $reason = (string) ( $input['reason'] ?? '' );
1182 $options = $this->get_exit_survey_options();
1183
1184 return array(
1185 'event_type' => 'exit_survey',
1186 'reason' => $reason,
1187 'reason_label' => $options[ $reason ] ?? '',
1188 'details' => (string) ( $input['details'] ?? '' ),
1189 'account' => (string) ( $opts['mlsimport_username'] ?? '' ),
1190 'install_uuid' => $opts['mlsimport_install_uuid'],
1191 'deactivation_count' => $count,
1192 'environment' => wp_get_environment_type(),
1193 'site_url' => home_url(),
1194 'admin_email' => (string) get_option( 'admin_email' ),
1195 'timestamp' => time(),
1196 );
1197 }
1198
1199 /**
1200 * The known exit-survey reason keys — the single source of truth.
1201 *
1202 * Pure: keys only, no labels, no translation. The label map
1203 * (get_exit_survey_options) builds on top of this for the modal.
1204 *
1205 * @return string[]
1206 */
1207 private function mlsimport_exit_survey_reasons(): array {
1208 return array(
1209 'built_website',
1210 'no_leads',
1211 'technical_issues',
1212 'too_expensive',
1213 'switched_tool',
1214 'other',
1215 );
1216 }
1217
1218 /**
1219 * Exit-survey reason key => display label, for the modal and payload.
1220 *
1221 * Uses translation, so it is not exercised by the pure unit suite —
1222 * is_valid_reason() relies on mlsimport_exit_survey_reasons() instead.
1223 *
1224 * @return array<string,string>
1225 */
1226 private function get_exit_survey_options(): array {
1227 return array(
1228 'built_website' => esc_html__( "Built the website, don't need ongoing sync", 'mlsimport' ),
1229 'no_leads' => esc_html__( 'Not getting leads from my site', 'mlsimport' ),
1230 'technical_issues' => esc_html__( "Technical issues I couldn't fix", 'mlsimport' ),
1231 'too_expensive' => esc_html__( 'Too expensive', 'mlsimport' ),
1232 'switched_tool' => esc_html__( 'Switched to another tool', 'mlsimport' ),
1233 'other' => esc_html__( 'Other', 'mlsimport' ),
1234 );
1235 }
1236
1237
1238
1239
1240
1241
1242 /**
1243 * Request auth token from mlsimport.net
1244 *
1245 * @since 4.0.1
1246 * returns token fron mlsimport
1247 */
1248 public function mlsimport_saas_get_mls_api_token_from_transient() {
1249
1250 $token = get_transient( 'mlsimport_saas_token' );
1251
1252 if ( false === $token || '' === $token ) {
1253 $token_json_answer = $this->mlsimport_saas_get_mls_api_token();
1254
1255 if ( isset( $token_json_answer['success'] ) && true === $token_json_answer['success'] ) {
1256 $token = $token_json_answer['token'];
1257
1258 set_transient( 'mlsimport_saas_token', $token, 3500 );
1259 }
1260 }
1261
1262 return $token;
1263 }
1264
1265
1266 /**
1267 * call for token
1268 *
1269 * @since 4.0.1
1270 * returns token fron mlsimport
1271 */
1272 protected function mlsimport_saas_get_mls_api_token() {
1273 $values = array();
1274 $options = get_option( $this->plugin_name . '_admin_options' );
1275
1276 // Check if the MLS provider has changed since the last run
1277 $prev_mls = get_option( 'mlsimport_prev_mls_name', '' );
1278
1279
1280 $username = '';
1281 if ( isset( $options['mlsimport_username'] ) ) {
1282 $username = sanitize_text_field( trim( $options['mlsimport_username'] ) );
1283 }
1284
1285 $password = '';
1286 if ( isset( $options['mlsimport_password'] ) ) {
1287 $password = sanitize_text_field( trim( $options['mlsimport_password'] ) );
1288 }
1289 $mls_name = '';
1290 if ( isset( $options['mlsimport_mls_name'] ) ) {
1291 $mls_name = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) );
1292 }
1293
1294 $mls_token = '';
1295 if ( isset( $options['mlsimport_mls_token'] ) ) {
1296 $mls_token = sanitize_text_field( trim( $options['mlsimport_mls_token'] ) );
1297 }
1298
1299 if ( $prev_mls !== '' && $prev_mls !== $mls_name ) {
1300 delete_transient( 'mlsimport_token_request' );
1301 delete_transient( 'mlsimport_metadata_api_call_data_service_property' );
1302 delete_transient( 'mls_import_meta_enums' );
1303 delete_transient( 'mls_import_meta' );
1304 delete_transient( 'mlsimport_plugin_data_schema' );
1305 delete_transient( 'mlsimport_ready_to_go_mlsimport_data' );
1306 delete_transient( 'mlsimport_saas_token' );
1307
1308 delete_option( 'mlsimport_mls_metadata_populated' );
1309
1310 delete_option( 'mlsimport_admin_fields_select' );
1311 }
1312
1313 update_option( 'mlsimport_prev_mls_name', $mls_name );
1314
1315
1316
1317 $values['username'] = $username;
1318 $values['password'] = $password;
1319
1320 if ( '' === $username || '' === $password ) {
1321 return '';
1322 }
1323
1324 $theme_Start = new ThemeImport();
1325 $answer = $theme_Start::globalApiRequestSaas( 'token', $values, 'POST' );
1326
1327
1328
1329 return $answer;
1330 }
1331
1332
1333
1334
1335
1336
1337 /**
1338 * save meta options
1339 *
1340 * @since 3.0.1
1341 */
1342 public function mlsimport_item_product_metaboxes() {
1343 add_meta_box( 'mlsimport_item_metaboxes-sectionid', __( 'Set Import data', 'mlsimport' ), array( $this, 'mlsimport_saas_display_meta_options' ), 'mlsimport_item', 'normal', 'default' );
1344 }
1345
1346
1347
1348 /**
1349 *
1350 *
1351 *
1352 * save meta options
1353 *
1354 * @since 3.0.1
1355 */
1356 public function mlsimport_item_product_save_metaboxes( $post_id, $post ) {
1357
1358 if ( ! is_object( $post ) || ! isset( $post->post_type ) ) {
1359 return;
1360 }
1361
1362 if ( 'mlsimport_item' !== $post->post_type ) {
1363 return;
1364 }
1365
1366 $allowed_keys = array(
1367 'mlsimport_item_how_many',
1368 'mlsimport_item_title_format',
1369 'mlsimport_item_agent',
1370 'mlsimport_item_property_status',
1371 'mlsimport_item_property_user',
1372 'mlsimport_item_min_price',
1373 'mlsimport_item_max_price',
1374 'mlsimport_item_city_check',
1375 'mlsimport_item_city',
1376 'mlsimport_item_city[]',
1377 'mlsimport_item_countyorparish_check',
1378 'mlsimport_item_countyorparish',
1379 'mlsimport_item_mlsstatus_check',
1380 'mlsimport_item_mlsstatus',
1381 'mlsimport_item_propertysubtype_check',
1382 'mlsimport_item_propertysubtype',
1383 'mlsimport_item_propertytype_check',
1384 'mlsimport_item_propertytype',
1385 'mlsimport_item_standardstatus_check',
1386 'mlsimport_item_standardstatus',
1387 'mlsimport_item_standardstatusprotect_check',
1388 'mlsimport_item_standardstatusprotect',
1389
1390 'mlsimport_item_internetentirelistingdisplayyn',
1391 'mlsimport_item_internetaddressdisplayyn',
1392 'mlsimport_item_stat_cron',
1393 'mlsimport_item_listagentkey',
1394 'mlsimport_item_listagentmlsid',
1395 'mlsimport_item_buyeragentmlsid',
1396 'mlsimport_item_listofficekey',
1397 'mlsimport_item_postalcode',
1398 'mlsimport_item_listofficemlsid',
1399 'mlsimport_item_listingid',
1400 'mlsimport_item_extracity',
1401 'mlsimport_item_extracounty',
1402 'mlsimport_item_exclude_listofficemlsid',
1403 'mlsimport_item_exclude_listofficekey',
1404 'mlsimport_item_exclude_listagentmlsid',
1405 'mlsimport_item_exclude_listagentkey',
1406 'mlsimport_item_customparameters',
1407 'mlsimport_item_mlsareamajor',
1408 'mlsimport_item_subdivisionname',
1409 );
1410
1411
1412
1413
1414 foreach ( $allowed_keys as $key => $key_value ) {
1415 if( isset($_POST[$key_value]) ){
1416 $postmeta = mlsimport_sanitize_multi_dimensional_array ( $_POST[$key_value] ) ;
1417 update_post_meta( $post_id, sanitize_key( $key_value ), $postmeta );
1418 }
1419
1420 }
1421
1422 $blank_keys = array(
1423 'mlsimport_item_standardstatus',
1424 'mlsimport_item_standardstatusprotect',
1425 'mlsimport_item_city',
1426 'mlsimport_item_countyorparish',
1427 'mlsimport_item_propertysubtype',
1428 'mlsimport_item_propertytype',
1429 'mlsimport_item_standardstatus',
1430 'mlsimport_item_listingid',
1431 'mlsimport_item_customparameters',
1432 'mlsimport_item_mlsareamajor',
1433 'mlsimport_item_subdivisionname',
1434
1435 );
1436
1437 foreach ( $blank_keys as $key ) {
1438 if ( ! isset( $_POST[ $key ] ) ) {
1439 update_post_meta( $post_id, $key, '' );
1440 }
1441 }
1442
1443
1444 }
1445
1446
1447 /**
1448 * Display Meta Options
1449 *
1450 * @param WP_Post $post The post object.
1451 */
1452 public function mlsimport_saas_display_meta_options($post) {
1453 wp_nonce_field(plugin_basename(__FILE__), 'estate_agent_noncename');
1454 global $mlsimport;
1455
1456 $token = $mlsimport->admin->mlsimport_saas_get_mls_api_token_from_transient();
1457 $is_mls_connected = get_option('mlsimport_connection_test', '');
1458 $mlsimport->admin->mlsimport_saas_setting_up();
1459
1460 if ('yes' !== $is_mls_connected) {
1461 $mlsimport->admin->mlsimport_saas_check_mls_connection();
1462 $is_mls_connected = get_option('mlsimport_connection_test', '');
1463 }
1464
1465 if (trim($token) === '') {
1466 echo '<div class="mlsimport_warning">' . esc_html__('You are not connected to MlsImport - Please check your Username and Password.', 'mlsimport') . '</div>';
1467 return;
1468 }
1469
1470 if ('yes' !== $is_mls_connected) {
1471 echo '<div class="mlsimport_warning">' . 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') . '</div>';
1472 return;
1473 }
1474
1475 $postId = $post->ID;
1476 $mlsimportItemHowMany = esc_html(get_post_meta($postId, 'mlsimport_item_how_many', true));
1477 $mlsimportItemStatCron = esc_html(get_post_meta($postId, 'mlsimport_item_stat_cron', true));
1478 $lastDate = get_post_meta($postId, 'mlsimport_last_date', true);
1479 $status = get_option('mlsimport_force_stop_' . $postId);
1480 $fieldImport = $this->mlsimport_saas_return_mls_fields();
1481 $options = get_option('mlsimport_admin_options');
1482 $mlsimportMlsId = isset($options['mlsimport_mls_name']) && $options['mlsimport_mls_name'] !== ''
1483
1484 ? intval($options['mlsimport_mls_name'])
1485 : 0;
1486
1487 $mlsRequest = $this->mlsimport_make_listing_requests($postId);
1488 // print_r($mlsRequest);
1489
1490 $hasError = isset($mlsRequest['success']) && !$mlsRequest['success'];
1491 if ($hasError) {
1492 echo '<div class="mlsimport_warning">' . esc_html($mlsRequest['message']) . '</div>';
1493 }
1494
1495 $foundItems = isset($mlsRequest['results']) ? intval($mlsRequest['results']) : 'none';
1496 if ($foundItems === 'none') {
1497 $mlsimport->admin->mlsimport_saas_check_mls_connection();
1498 esc_html_e('Your Token was expired. Please refresh the page to renew it wait while we renew it.', 'mlsimport');
1499 }
1500
1501 echo $this->generateMetaOptionsHtml($postId, $foundItems, $lastDate, $mlsimportItemHowMany, $mlsimportItemStatCron, $mlsimportMlsId, $fieldImport, $hasError);
1502 }
1503
1504
1505
1506
1507 /**
1508 * Generate Meta Options HTML
1509 *
1510 * @param int $postId The post ID.
1511 * @param int $foundItems The number of found items.
1512 * @param string $lastDate The last date checked.
1513 * @param string $mlsimportItemHowMany How many items to import.
1514 * @param string $mlsimportItemStatCron The status of the cron job.
1515 * @param int $mlsimportMlsId The MLS import ID.
1516 * @param array $fieldImport The fields to import.
1517 * @return string The generated HTML.
1518 */
1519 private function generateMetaOptionsHtml($postId, $foundItems, $lastDate, $mlsimportItemHowMany, $mlsimportItemStatCron, $mlsimportMlsId, $fieldImport, $hasError = false) {
1520
1521
1522 ob_start();
1523
1524 $metadata_api_call_city = array();
1525 $metadata_api_call_county = array();
1526 $metadata_api_call_property_type = array();
1527 $mlsimport_mls_metadata_mls_enums = get_option('mlsimport_mls_metadata_mls_enums', '');
1528 if ('' !== $mlsimport_mls_metadata_mls_enums) {
1529 $metadata_api_call_full = json_decode($mlsimport_mls_metadata_mls_enums, true);
1530 if (isset($metadata_api_call_full['global_array']['PropertyEnums'])) {
1531 $property_enums = $metadata_api_call_full['global_array']['PropertyEnums'];
1532 if (isset($property_enums['City']) && is_array($property_enums['City'])) {
1533 $metadata_api_call_city = $property_enums['City'];
1534 }
1535
1536 if (isset($property_enums['CountyOrParish']) && is_array($property_enums['CountyOrParish'])) {
1537 $metadata_api_call_county = $property_enums['CountyOrParish'];
1538 }
1539
1540 if (isset($property_enums['PropertyType']) && is_array($property_enums['PropertyType'])) {
1541 $metadata_api_call_property_type = $property_enums['PropertyType'];
1542 }
1543 }
1544 }
1545
1546 ?>
1547 <div class="mlsimport_item_search_url" style="display:none;"><?php echo esc_html__('Last date/time we check :', 'mlsimport') . ' ' . esc_html($lastDate); ?></div>
1548 <ul>
1549 <li>1. Set the import parameters.</li>
1550 <li>2. Hit Publish or Update, otherwise import will not work correctly.</li>
1551 <li>3. Click the Start Import button. Most MLS limit the import number to 1000. If you need to import more create additional import items.</li>
1552 <li>4. Press the Update button after you make any change in the import settings.</li>
1553 </ul>
1554
1555 <?php if (is_numeric($foundItems) && $foundItems >= 500): ?>
1556 <div class="mlsimport_notification">
1557 <?php esc_html_e('You found a large number of listings. While MlsImport import can handle such a large number, you need to make sure that your server can do this operation. This import will take some time. Make sure your server has the capacity, there are no time limits for a long-running process and consider splitting the import between multiple MLS Import Tasks.', 'mlsimport'); ?>
1558 </div>
1559 <?php endif; ?>
1560
1561 <div class="mlsimport_import_no">
1562 <?php esc_html_e('We found', 'mlsimport'); ?>
1563 <strong><?php echo esc_html($foundItems); ?></strong> 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.
1564 </div>
1565
1566 <fieldset class="mlsimport-fieldset">
1567 <label class="mlsimport-label" for="mlsimport_item_how_many">
1568 <?php esc_html_e('How Many to import. Use 0 if you want to import all listings found.', 'mlsimport'); ?>
1569 </label>
1570 <input type="text" id="mlsimport_item_how_many" name="mlsimport_item_how_many"
1571 class="mlsimport-input mlsimport-2025-input " value="<?php echo esc_attr($mlsimportItemHowMany); ?>"/>
1572 </fieldset>
1573
1574 <fieldset class="mlsimport-fieldset mlsimport_auto_switch">
1575 <?php esc_html_e('Enable Auto Update every hour?', 'mlsimport'); ?>
1576 <label class="mlsimport_switch">
1577 <input type="hidden" value="0" name="mlsimport_item_stat_cron">
1578 <input type="checkbox" class="mlsimport-import-checkbox" value="1" name="mlsimport_item_stat_cron"<?php if (intval($mlsimportItemStatCron) !== 0) echo esc_html(' checked'); ?>>
1579 <span class="slider round"></span>
1580 </label>
1581 </fieldset>
1582
1583 <?php if ($mlsimportItemStatCron !== '' && !$hasError): ?>
1584 <div id="mlsimport_item_status">Ready to import!</div>
1585 <div id="mlsimport_item_progress" class="mlsimport-progress-bar">
1586 <div class="mlsimport-progress-bar-inner" style="width:0%;"></div>
1587 </div>
1588 <input class="button mlsimport_button save_data " type="button" id="mlsimport-start_item"
1589 data-post-number="<?php echo intval($foundItems); ?>"
1590 data-post_id="<?php echo intval($postId); ?>" value="Start Import">
1591 <input class="button mlsimport_button error_action" type="button" id="mlsimport_stop_item"
1592 data-post-number="<?php echo intval($foundItems); ?>"
1593 data-post_id="<?php echo intval($postId); ?>" value="Stop Import">
1594 <?php endif; ?>
1595
1596 <input type="hidden" id="mlsimport_item_actions" value="<?php echo esc_attr(wp_create_nonce("mlsimport_item_actions")); ?>"/>
1597 <div class="mlsimport_param_wrapper"><h2><?php esc_html_e('Import Parameters', 'mlsimport'); ?></h2>
1598
1599 <?php
1600 $mlsimportItemTitleFormat = esc_html(get_post_meta($postId, 'mlsimport_item_title_format', true));
1601 ?>
1602
1603 <fieldset class="mlsimport-fieldset">
1604 <label class="mlsimport-label" for="mlsimport_item_title_format">
1605 <?php esc_html_e('Title Format', 'mlsimport'); ?>
1606 </label>
1607
1608 <p class="mlsimport-exp"><?php esc_html_e('You can use {Address}, {City}, {CountyOrParish}, {StateOrProvince}, {PostalCode}, {PropertyType}, {Bedrooms}, {Bathrooms}, {ListingKey}, {ListingId},{StreetNumberNumeric} or {StreetName}', 'mlsimport'); ?></p>
1609 <input type="text" id="mlsimport_item_title_format" name="mlsimport_item_title_format"
1610 class="mlsimport-input mlsimport-2025-input"
1611 value="<?php echo '' !== $mlsimportItemTitleFormat ? trim(esc_html($mlsimportItemTitleFormat)) : esc_html('{Address},{City},{CountyOrParish},{PropertyType}'); ?>"/>
1612 </fieldset>
1613
1614 <?php
1615 $mlsimportItemAgent = esc_html(get_post_meta($postId, 'mlsimport_item_agent', true));
1616 ?>
1617
1618 <fieldset class="mlsimport-fieldset">
1619 <label class="mlsimport-label" for="mlsimport_item_agent">
1620 <?php esc_html_e('Select Agent', 'mlsimport'); ?>
1621 </label>
1622 <select class="mlsimport-select mlsimport-2025-select" name="mlsimport_item_agent" id="mlsimport_item_agent">
1623 <?php
1624 $permitedTags = mlsimport_allowed_html_tags_content();
1625 $selectAgent =$this->theme_importer->mlsimportSaasThemeImportSelectAgent($mlsimportItemAgent);
1626 print wp_kses($selectAgent, $permitedTags);
1627 ?>
1628 </select>
1629 </fieldset>
1630
1631 <?php
1632 $mlsimportItemPropertyStatus = esc_html(get_post_meta($postId, 'mlsimport_item_property_status', true));
1633 if ('' === $mlsimportItemPropertyStatus) {
1634 $mlsimportItemPropertyStatus = 'publish';
1635 }
1636 $statusArray = array('publish', 'draft');
1637 ?>
1638 <fieldset class="mlsimport-fieldset">
1639 <label class="mlsimport-label" for="mlsimport_item_property_status">
1640 <?php esc_html_e('Select Property Status on import', 'mlsimport'); ?>
1641 </label>
1642 <select class="mlsimport-select mlsimport-2025-select" name="mlsimport_item_property_status" id="mlsimport_item_property_status">
1643 <?php foreach ($statusArray as $value): ?>
1644 <option value="<?php echo esc_attr($value); ?>" <?php if ($value === $mlsimportItemPropertyStatus) echo esc_html('selected'); ?>>
1645 <?php echo esc_html($value); ?>
1646 </option>
1647 <?php endforeach; ?>
1648 </select>
1649 </fieldset>
1650
1651 <?php
1652 $mlsimportItemPropertyUser = esc_html(get_post_meta($postId, 'mlsimport_item_property_user', true));
1653 ?>
1654 <fieldset class="mlsimport-fieldset">
1655 <label class="mlsimport-label" for="mlsimport_item_property_user">
1656 <?php esc_html_e('User', 'mlsimport'); ?>
1657 </label>
1658 <select class="mlsimport-select mlsimport-2025-select" id="mlsimport_item_property_user" name="mlsimport_item_property_user">
1659 <?php
1660 $selectUser = $this->theme_importer->mlsimportSaasThemeImportSelectUser($mlsimportItemPropertyUser);
1661 print wp_kses($selectUser, $permitedTags);
1662 ?>
1663 </select>
1664 </fieldset>
1665
1666 <?php
1667 $mlsimportItemMinPrice = floatval(get_post_meta($postId, 'mlsimport_item_min_price', true));
1668 $mlsimportItemMaxPrice = floatval(get_post_meta($postId, 'mlsimport_item_max_price', true));
1669 if (0 === intval($mlsimportItemMaxPrice)) {
1670 $mlsimportItemMaxPrice = 10000000;
1671 }
1672 ?>
1673 <fieldset class="mlsimport-fieldset">
1674 <label class="mlsimport-label">
1675 <?php esc_html_e('Price Between', 'mlsimport'); ?>
1676 </label>
1677 <input type="text" class="mlsimport-select mlsimport-input mlsimport-2025-input " id="mlsimport_item_min_price" name="mlsimport_item_min_price" value="<?php echo esc_attr($mlsimportItemMinPrice); ?>"> and
1678 <input type="text" class="mlsimport-select mlsimport-input mlsimport-2025-input " id="mlsimport_item_max_price" name="mlsimport_item_max_price" value="<?php echo esc_attr($mlsimportItemMaxPrice); ?>">
1679 </fieldset>
1680
1681 <?php
1682 $options = get_option($this->plugin_name . '_admin_options');
1683
1684 $mlsId = '';
1685 if (isset($options['mlsimport_mls_name'])) {
1686 $mlsId = sanitize_text_field(trim($options['mlsimport_mls_name']));
1687 }
1688
1689 if ($mlsId > 5000) {
1690 $fieldImport['PropertyType']['multiple'] = 'no';
1691 }
1692
1693
1694 if ($mlsId >= 7000) {
1695 // there is no such thing for realtor.ca
1696 unset($fieldImport['PropertyType']);
1697 }
1698
1699
1700
1701
1702 foreach ($fieldImport as $key => $field):
1703 if (!empty($field['hidden'])) {
1704 continue;
1705 }
1706 $nameCheck = strtolower('mlsimport_item_' . $key . '_check');
1707 $name = strtolower('mlsimport_item_' . $key);
1708
1709 $value = get_post_meta($postId, $name, true);
1710 $valueCheck = get_post_meta($postId, $nameCheck, true);
1711 $extraClass = '';
1712 if ('extraCity' === $key || 'extraCounty' === $key) {
1713 $extraClass = ' mlsimport_hidden_field_button button mlsimport_button';
1714 }
1715 ?>
1716 <fieldset class="mlsimport-fieldset">
1717 <label class="mlsimport-label <?php echo esc_attr($extraClass); ?>" for="<?php echo esc_attr($name); ?>">
1718 <?php echo esc_html($field['label']); ?>
1719 </label>
1720 <?php if ('extraCity' === $key || 'extraCounty' === $key): ?>
1721 <div class="mlsimport-input-wrapper" style="display:none">
1722 <?php endif; ?>
1723 <p class="mlsimport-exp"><?php echo wp_kses_post($this->mlsimport_notes_for_mls($mlsimportMlsId, $name, $field['description'])); ?>
1724 <?php
1725 $isCheckboxAdmin = 0;
1726 if (1 === intval($valueCheck)) {
1727 $isCheckboxAdmin = 1;
1728 }
1729
1730 $selectAllNone = [
1731 'InternetAddressDisplayYN',
1732 'InternetEntireListingDisplayYN',
1733 'PostalCode',
1734 'ListAgentKey',
1735 'ListAgentMlsId',
1736 'BuyerAgentMlsId',
1737 'ListOfficeKey',
1738 'ListOfficeMlsId',
1739 'StandardStatus',
1740 'ListingId',
1741 'extraCity',
1742 'extraCounty',
1743 'Exclude_ListOfficeKey',
1744 'Exclude_ListOfficeMlsId',
1745 'Exclude_ListAgentKey',
1746 'Exclude_ListAgentMlsId',
1747 'CustomParameters',
1748 'MLSAreaMajor',
1749 'SubdivisionName',
1750 ];
1751
1752 if ($mlsId > 5000) {
1753 $selectAllNone[] = 'PropertyType';
1754 }
1755
1756 if (!in_array($key, $selectAllNone)): ?>
1757 <?php
1758 esc_html_e('- Or Select All ', 'mlsimport');
1759
1760 ?>
1761 <input type="hidden" name="<?php echo esc_attr($nameCheck); ?>" value="0"/>
1762 <input type="checkbox" class="mlsimport-import-checkbox" name="<?php echo esc_attr($nameCheck); ?>" value="1" <?php print esc_attr(checked($isCheckboxAdmin, 1, 0)); ?>/>
1763 <?php endif; ?>
1764 </p>
1765
1766 <?php
1767 $permittedStatus = ['active', 'active under contract', 'coming soon', 'activeundercontract', 'comingsoon', 'pending'];
1768
1769 if ($field['type'] === 'select'): ?>
1770 <?php
1771 $multiple = '';
1772 if ('yes' === $field['multiple']) {
1773 $multiple = 'multiple';
1774 $name .= '[]';
1775 }
1776
1777 if ('StandardStatus' === $key && '' === $value) {
1778 $value = ['Active'];
1779 }
1780
1781
1782
1783 // City/County lists can hold 300+ entries — render a filter
1784 // input above the full native multi-select listbox.
1785 $searchableClass = $this->mlsimport_searchable_select_class($key);
1786 $isSearchable = '' !== $searchableClass;
1787 $searchPlaceholder = $isSearchable
1788 ? esc_html__('Type to search…', 'mlsimport')
1789 : '';
1790
1791 // Additional conditions can be placed here.
1792 ?>
1793 <?php if ($isSearchable): ?>
1794 <div class="mlsimport-selected-chips" aria-live="polite"></div>
1795 <input type="text" class="mlsimport-select-search" placeholder="<?php echo esc_attr($searchPlaceholder); ?>" aria-label="<?php echo esc_attr($searchPlaceholder); ?>" autocomplete="off">
1796 <?php endif; ?>
1797 <select class="mlsimport-select mlsimport-2025-select<?php echo esc_attr($searchableClass); ?>" id="<?php echo esc_attr($name); ?>" name="<?php echo esc_attr($name); ?>"<?php echo $isSearchable ? ' size="12"' : ''; ?> <?php echo esc_attr($multiple); ?>>
1798 <?php foreach ($field['values'] as $selectKey): ?>
1799
1800 <?php if ('' !== $selectKey): ?>
1801 <?php
1802 $option_value = $selectKey;
1803 $option_label = $selectKey;
1804 $comparison_values = array($option_value);
1805
1806 if ('City' === $key && isset($metadata_api_call_city[$selectKey])) {
1807 $option_label = $selectKey;
1808 $comparison_values[] = $metadata_api_call_city[$selectKey];
1809 } elseif ('CountyOrParish' === $key && isset($metadata_api_call_county[$selectKey])) {
1810 $option_label = $selectKey;
1811 $comparison_values[] = $metadata_api_call_county[$selectKey];
1812 } elseif ('PropertyType' === $key && isset($metadata_api_call_property_type[$selectKey])) {
1813 $option_label = $selectKey;
1814 $comparison_values[] = $metadata_api_call_property_type[$selectKey];
1815 }
1816
1817 $comparison_values = array_values(array_unique(array_filter($comparison_values, static function ($compare_value) {
1818 return '' !== $compare_value && null !== $compare_value;
1819 })));
1820
1821 $is_selected = false;
1822 if (is_array($value)) {
1823 $is_selected = count(array_intersect($comparison_values, $value)) > 0;
1824 } else {
1825 $is_selected = in_array($value, $comparison_values, true);
1826 }
1827 ?>
1828 <option value="<?php echo esc_attr($option_value); ?>" <?php echo $is_selected ? 'selected' : ''; ?>>
1829 <?php echo esc_html($option_label); ?>
1830 </option>
1831 <?php endif; ?>
1832
1833 <?php endforeach; ?>
1834 </select>
1835
1836 <?php elseif ($field['type'] === 'input'): ?>
1837 <input type="text" class="mlsimport-select mlsimport-input mlsimport-2025-input" id="<?php echo esc_attr($name); ?>" name="<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($value); ?>">
1838 <?php endif; ?>
1839 <?php if ('extraCity' === $key || 'extraCounty' === $key): ?>
1840 </div>
1841 <?php endif; ?>
1842 </fieldset>
1843 <?php endforeach; ?>
1844
1845 </div>
1846 <?php
1847 return ob_get_clean();
1848 }
1849
1850
1851
1852
1853
1854
1855 public function mlsimport_add_extra_fields() {
1856 }
1857
1858 /**
1859 *
1860 *
1861 *
1862 *
1863 *
1864 *
1865 */
1866 function mlsimport_notes_for_mls( $mlsimport_mls_id, $name, $description ) {
1867 // 111 - Rae Edmonton
1868
1869 if ( 111 === intval($mlsimport_mls_id) && 'mlsimport_item_standardstatus' === $name ) {
1870 return esc_html__( 'Your MLS does not use this field - all listings are considered Active.', 'mlsimport' );
1871 } else {
1872 return $description;
1873 }
1874 }
1875
1876
1877 /**
1878 *
1879 *
1880 * Get Last date
1881 */
1882 public function mlsimport_saas_get_last_date( $item_id ) {
1883 $last_date = get_post_meta( $item_id, 'mlsimport_last_date', true );
1884
1885 if ( '' === $last_date ) {
1886 $last_date = $this->mlsimport_saas_update_last_date( $item_id );
1887 }
1888 return $last_date;
1889 }
1890
1891
1892 /**
1893 *
1894 *
1895 * Save Last date
1896 */
1897 public function mlsimport_saas_update_last_date( $item_id ) {
1898
1899 $unix_time = current_time( 'timestamp', 0 ) - ( 2 * 60 * 60 );
1900 print $last_date_to_save = date( 'Y-m-d\TH:i', $unix_time );
1901 update_post_meta( $item_id, 'mlsimport_last_date', $last_date_to_save );
1902
1903 return $last_date_to_save;
1904 }
1905
1906
1907
1908
1909
1910 /**
1911 * Check and process MLSimport item for modified listings in the last 2 hours.
1912 * Optimized for memory: logs memory, unsets large arrays, and triggers garbage collection.
1913 *
1914 * @param int $item_id
1915 * @return int Number of listings found in the MLS feed, or 0 on failure.
1916 */
1917 public function mlsimport_saas_start_cron_links_per_item( int $item_id ): int {
1918 // Log memory before start
1919
1920 $found_items = 0;
1921
1922 // Auto-sync only maintains tasks the realtor has imported at least once
1923 // by hand. A task created and forgotten has no 'mlsimport_spawn_status'
1924 // meta, so the cron skips it instead of pulling the whole MLS feed.
1925 if ( '' === get_post_meta( $item_id, 'mlsimport_spawn_status', true ) ) {
1926 return 0;
1927 }
1928
1929 $last_date = $this->mlsimport_saas_get_last_date( $item_id );
1930 print 'MLSitem id: ' . $item_id . ' - ';
1931 esc_html_e('date to consider: ','mlsimport');
1932 print esc_html($last_date) . '. ';
1933
1934 // Make request to MLS API
1935 $mlsrequest = $this->mlsimport_make_listing_requests( $item_id, $last_date, '', '', true );
1936
1937 if ( isset( $mlsrequest['results'] ) ) {
1938 $found_items = intval( $mlsrequest['results'] );
1939 } else {
1940 delete_transient( 'mlsimport_saas_token' );
1941 mlsimport_telemetry_set( 'last_sync_failed', time() );
1942 mlsimport_telemetry_set( 'last_sync_failed_code', (string) ( $mlsrequest['error_code'] ?? 'unknown' ) );
1943 }
1944 print esc_html__('We found ','mlsimport') . esc_html( $found_items ) . ' listings.</br>' . PHP_EOL;
1945
1946 // Only process if items found
1947 if ( $found_items > 0 ) {
1948
1949 $item_id_array = array(
1950 'item_id' => $item_id,
1951 'how_many' => 0,
1952 'max_number' => $found_items,
1953 'batch_counter' => 1,
1954 );
1955
1956 // Potentially large array, log memory before/after
1957 $attachments_to_move = (array) $this->mlsimport_saas_generate_import_requests_per_item( $item_id_array, $last_date, true );
1958
1959 // Store in post meta (beware if array is huge)
1960 update_post_meta( $item_id, 'mlsimport_spawn_status_cron_job', 'started' );
1961 update_post_meta( $item_id, 'mlsimport_cron_attach_to_move_' . $item_id, $attachments_to_move );
1962
1963 // Save last date for next run
1964 $this->mlsimport_saas_update_last_date( $item_id );
1965
1966 // Prepare and pass only necessary arguments to background process
1967 $attachments_to_send = array(
1968 'args' => array(
1969 'attachments_to_move' => $item_id,
1970 'item_id_array' => $item_id_array,
1971 ),
1972 );
1973
1974 $this->mlsimport_background_process_per_item_cron_function( $attachments_to_send['args'] );
1975
1976 // Unset large arrays/objects after use
1977 unset($attachments_to_move, $attachments_to_send, $mlsrequest, $item_id_array);
1978 gc_collect_cycles();
1979 }
1980
1981 return $found_items;
1982 }
1983
1984
1985
1986
1987
1988
1989 /**
1990 * Reconciliation log (optimized, batched, memory logged)
1991 */
1992 public function mlsimport_saas_start_doing_reconciliation() {
1993 global $mlsimport, $wpdb;
1994
1995
1996 // Get all MLS keys in memory (we assume this is necessary for lookup)
1997 $mls_data = $this->mlsimport_saas_get_mls_reconciliation_data();
1998 $listingKey_in_MLS = $mls_data['all_data'] ?? [];
1999
2000 unset($mls_data);
2001 gc_collect_cycles();
2002
2003 if (empty($listingKey_in_MLS)) {
2004 return;
2005 }
2006 // Flip for fast lookup
2007 $listingKey_in_MLS = array_flip($listingKey_in_MLS);
2008
2009 // Batch fetch local listings
2010 $batch = 1000;
2011 $offset = 0;
2012 $to_delete = 0;
2013 $counter = 0;
2014
2015 $mlsimport_preload_all_mls_item_status_meta = $this->mlsimport_preload_all_mls_item_status_meta();
2016 //print_r($mlsimport_preload_all_mls_item_status_meta);
2017
2018 do {
2019 $local = $wpdb->get_results(
2020 $wpdb->prepare(
2021 "SELECT
2022 p.ID,
2023 listingkey_meta.meta_value AS listingkey,
2024 inserted_meta.meta_value AS mlsimport_item_inserted
2025 FROM {$wpdb->posts} p
2026 INNER JOIN {$wpdb->postmeta} listingkey_meta
2027 ON p.ID = listingkey_meta.post_id
2028 AND listingkey_meta.meta_key = %s
2029 LEFT JOIN {$wpdb->postmeta} inserted_meta
2030 ON p.ID = inserted_meta.post_id
2031 AND inserted_meta.meta_key = %s
2032 WHERE p.post_status NOT IN ('draft', 'trash')
2033 LIMIT %d OFFSET %d",
2034 'ListingKey',
2035 'MLSimport_item_inserted',
2036 $batch,
2037 $offset
2038 ),
2039 ARRAY_A
2040 );
2041
2042 $count = count($local);
2043
2044
2045 foreach ($local as $item) {
2046 $listingkey = $item['listingkey']; // not 'meta_value' anymore
2047 $property_id = $item['ID'];
2048 $mlsimportItemId = $item['mlsimport_item_inserted'];
2049 ++$counter;
2050 // IN MLS
2051 if (isset($listingKey_in_MLS[$listingkey])) {
2052
2053 if (!empty($mlsimportItemId) && isset($mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId])) {
2054 $mlsimport_item_standardstatus = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatus'] ?? null;
2055 $mlsimport_item_standardstatusprotect = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatusprotect'] ?? null;
2056 } else {
2057 $mlsimport_item_standardstatus = null;
2058 $mlsimport_item_standardstatusprotect = null;
2059 }
2060
2061 $keep_when_in_mls = $mlsimport->admin->theme_importer->check_if_delete_when_status_when_in_mls($property_id, $mlsimport_item_standardstatus, $mlsimport_item_standardstatusprotect);
2062 if (!$keep_when_in_mls) {
2063 ++$to_delete;
2064 $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql($property_id, $listingkey);
2065 }
2066 } else {
2067 // NOT IN MLS
2068
2069 if (!empty($mlsimportItemId) && isset($mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId])) {
2070 $mlsimport_item_standardstatus = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatus'] ?? null;
2071 $mlsimport_item_standardstatusprotect = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatusprotect'] ?? null;
2072 } else {
2073 $mlsimport_item_standardstatus = null;
2074 $mlsimport_item_standardstatusprotect = null;
2075 }
2076 $keep = $mlsimport->admin->theme_importer->check_if_delete_when_status($property_id, $mlsimport_item_standardstatus, null, $mlsimport_item_standardstatusprotect);
2077 if (!$keep) {
2078 ++$to_delete;
2079 $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql($property_id, $listingkey);
2080 }
2081 }
2082
2083 // Memory housekeeping
2084 unset($listingkey, $property_id, $mlsimportItemId, $mlsImportItemStatus, $mlsimport_item_standardstatusprotect, $keep_when_in_mls, $keep);
2085 if ($counter % 250 == 0) {
2086 gc_collect_cycles();
2087 }
2088 }
2089
2090 unset($local);
2091 gc_collect_cycles();
2092
2093 $offset += $batch;
2094 } while ($count === $batch);
2095
2096
2097 print esc_html(' to delete:' . $to_delete);
2098
2099 // Final cleanup
2100 unset($listingKey_in_MLS);
2101 gc_collect_cycles();
2102
2103
2104 return;
2105 }
2106
2107
2108
2109 /**
2110 * Preload all status meta for ALL mlsimport_item posts in ONE QUERY.
2111 * Returns: [mlsimport_item_id => ['mlsimport_item_standardstatus' => ..., 'mlsimport_item_standardstatusprotect' => ...], ...]
2112 */
2113 function mlsimport_preload_all_mls_item_status_meta() {
2114 global $wpdb;
2115
2116 $sql = "
2117 SELECT p.ID as post_id, pm.meta_key, pm.meta_value
2118 FROM {$wpdb->posts} p
2119 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
2120 WHERE p.post_type = 'mlsimport_item'
2121 AND pm.meta_key IN ('mlsimport_item_standardstatus', 'mlsimport_item_standardstatusprotect')
2122 ";
2123
2124 $rows = $wpdb->get_results($sql);
2125
2126 $meta = [];
2127 foreach ($rows as $row) {
2128 if (!isset($meta[$row->post_id])) {
2129 $meta[$row->post_id] = [
2130 'mlsimport_item_standardstatus' => null,
2131 'mlsimport_item_standardstatusprotect' => null
2132 ];
2133 }
2134 $meta[$row->post_id][$row->meta_key] = maybe_unserialize($row->meta_value);
2135 }
2136 return $meta;
2137 }
2138
2139
2140
2141 /**
2142 *
2143 *
2144 * Requestq Reconciliation log
2145 */
2146 public function mlsimport_saas_get_mls_reconciliation_data() {
2147
2148 $arguments = array();
2149 $answer = $this->theme_importer->globalApiRequestCurlSaas( 'reconciliation', $arguments, 'GET' );
2150 return $answer;
2151 }
2152
2153 /**
2154 *
2155 *
2156 * Reconciliation get local data
2157 */
2158 public function mlsimport_saas_get_all_meta_values($key) {
2159 global $wpdb;
2160 $result = $wpdb->get_results(
2161 $wpdb->prepare(
2162 "
2163 SELECT pm.meta_value, p.ID
2164 FROM {$wpdb->postmeta} pm
2165 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
2166 WHERE pm.meta_key = %s
2167 AND p.post_status = 'publish'
2168 ",
2169 $key
2170 ),
2171 ARRAY_A // Lighter than OBJECT, unless you need objects
2172 );
2173 return $result;
2174 }
2175
2176
2177
2178 /*
2179 * Do api Listing Requests
2180 *
2181 *
2182 *
2183 *
2184 * */
2185 public function mlsimport_make_listing_requests( $item_id, $last_date = '', $skip = '', $top = '', $is_hourly_sync = false ) {
2186 $options = get_option( $this->plugin_name . '_admin_options' );
2187 $mls_id = '';
2188 if ( isset( $options['mlsimport_mls_name'] ) ) {
2189 $mls_id = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) );
2190 }
2191
2192 $arguments = $this->mlsimport_saas_make_listing_requests_arguments( $item_id, $last_date, $skip, $top, $is_hourly_sync );
2193
2194
2195 if (
2196 $mls_id > 5000 && $mls_id < 6000 &&
2197 ( ! isset( $arguments['property_type'] ) or
2198 ( isset( $arguments['property_type'] ) && '' === $arguments['property_type'] ) or
2199 ( isset( $arguments['property_type'][0] ) && '' === $arguments['property_type'][0] )
2200 )
2201 ) {
2202 return array(
2203 'success' => false,
2204 'type' => 'rapattoni',
2205 'message' => esc_html__( 'This MLS requires to have one item selected from "Property Action Category" dropdown', 'mlsimport' ),
2206 );
2207 }
2208
2209 $potential_leght = strlen( wp_json_encode( $arguments ) );
2210 if ( $potential_leght > 1750 ) {
2211 return array(
2212 'success' => false,
2213 'potential_leght' => $potential_leght,
2214 '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' ),
2215 );
2216 }
2217
2218 //print_r($arguments);
2219 //print '----------------------------'.PHP_EOL;
2220 $answer = $this->theme_importer->globalApiRequestCurlSaas( 'listings', $arguments, 'POST' );
2221 $answer['potential_leght'] = $potential_leght;
2222
2223 // Record the pre-filter MLS feed count for telemetry. Every import path
2224 // — manual, hourly cron, and onboarding — routes through this method, so
2225 // recording here is the single rule that keeps the metric complete.
2226 if ( isset( $answer['results'] ) ) {
2227 mlsimport_telemetry_set( 'last_feed_found', (int) $answer['results'] );
2228 }
2229
2230 return ( $answer );
2231 }
2232
2233
2234
2235
2236
2237
2238 /*
2239 * Create Api query arguments
2240 *
2241 *
2242 *
2243 *
2244 *
2245 * */
2246
2247 public function mlsimport_saas_make_listing_requests_arguments( $item_id, $last_date = '', $skip = '', $top = '', $is_hourly_sync = false ) {
2248
2249 $options = get_option( $this->plugin_name . '_admin_options' );
2250 if ( isset( $options['mlsimport_mls_name'] ) ) {
2251 $mls_id = intval( $options['mlsimport_mls_name'] );
2252 } else {
2253 return '';
2254 }
2255
2256 if ( isset( $options['mlsimport_theme_used'] ) ) {
2257 $theme_id = intval( $options['mlsimport_theme_used'] );
2258 } else {
2259 return '';
2260 }
2261
2262 $values = array();
2263 $values['mls_id'] = $mls_id;
2264 $values['theme_id'] = $theme_id;
2265 if ( $is_hourly_sync ) {
2266 $values['hourly_sync'] = 1;
2267 }
2268
2269 if ( '' !== $top ) {
2270 $values['top'] = $top;
2271 $values['skip'] = intval( $skip );
2272 }
2273
2274 // // add price
2275 $mlsimport_item_min_price = get_post_meta( $item_id, 'mlsimport_item_min_price', true );
2276 $mlsimport_item_max_price = get_post_meta( $item_id, 'mlsimport_item_max_price', true );
2277 if ( '' !== $mlsimport_item_min_price && '' !== $mlsimport_item_max_price ) {
2278 $values['list_price_min'] = floatval( $mlsimport_item_min_price );
2279 $values['list_price_max'] = floatval( $mlsimport_item_max_price );
2280 }
2281
2282 // add city
2283 $values = $this->mls_import_return_multiple_param_value( 'city', $item_id, 'city', $values );
2284
2285 // add county
2286 $values = $this->mls_import_return_multiple_param_value( 'countyorparish', $item_id, 'county_or_parish', $values );
2287
2288 // add MLSAreaMajor
2289 $values = $this->mls_import_saas_add_to_parms_input( 'MLSAreaMajor', $item_id, 'mls_area_major', $values );
2290
2291 // add SubdivisionName
2292 $values = $this->mls_import_saas_add_to_parms_input( 'SubdivisionName', $item_id, 'subdivision_name', $values );
2293
2294 // add postal code
2295 $values = $this->mls_import_saas_add_to_parms_input( 'PostalCode', $item_id, 'postal_code', $values );
2296
2297 // add status
2298
2299 if ( 111 !== $mls_id ) { // edmonton check
2300 $values = $this->mls_import_return_multiple_param_value( 'StandardStatus', $item_id, 'status', $values );
2301 }
2302
2303 // add property_subtype
2304 $values = $this->mls_import_return_multiple_param_value( 'PropertySubType', $item_id, 'property_subtype', $values );
2305
2306 // add property_type
2307 $values = $this->mls_import_return_multiple_param_value( 'PropertyType', $item_id, 'property_type', $values );
2308
2309 // rapattoni exception
2310 if ( $mls_id > 5000 &&
2311 ( isset($values['property_type']) && $values['property_type'] !='' ) ) {
2312
2313 $values = $this->mls_import_saas_add_to_parms_input( 'PropertyType', $item_id, 'property_type', $values );
2314 $temp = $values['property_type'];
2315 $temp = str_replace( ' ', '', $temp );
2316 $values['property_type'] = array();
2317 $values['property_type'][] = $temp;
2318 }
2319
2320 // add internet_entirelisting_displayyn
2321 $values = $this->mls_import_saas_add_to_parms_input( 'InternetEntireListingDisplayYN', $item_id, 'internet_entirelisting_displayyn', $values );
2322
2323 // add internet_address_displayyn
2324 $values = $this->mls_import_saas_add_to_parms_input( 'InternetAddressDisplayYN', $item_id, 'internet_address_displayyn', $values );
2325
2326 // add ListAgentKey
2327 $values = $this->mls_import_saas_add_to_parms_input( 'ListAgentKey', $item_id, 'list_agentkey', $values );
2328 // add ListAgentKey
2329 $values = $this->mls_import_saas_add_to_parms_input( 'ListAgentMlsId', $item_id, 'list_agentmlsid', $values );
2330 // add BuyerAgentMlsId
2331 $values = $this->mls_import_saas_add_to_parms_input( 'BuyerAgentMlsId', $item_id, 'buyer_agentmlsid', $values );
2332 // add ListOfficeKey
2333 $values = $this->mls_import_saas_add_to_parms_input( 'ListOfficeKey', $item_id, 'list_officekey', $values );
2334 // add ListOfficeMlsId
2335 $values = $this->mls_import_saas_add_to_parms_input( 'ListOfficeMlsId', $item_id, 'list_officemlsid', $values );
2336
2337 // add ListingId
2338 $values = $this->mls_import_saas_add_to_parms_input( 'ListingId', $item_id, 'listingid', $values );
2339
2340 //add Exclude_ListOfficeKey
2341 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListOfficeKey', $item_id, 'exclude_list_officekey', $values );
2342 // add Exclude_ListOfficeMlsId
2343 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListOfficeMlsId', $item_id, 'exclude_list_officemlsid', $values );
2344
2345
2346
2347 //add Exclude_ListAgentKey
2348 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListAgentKey', $item_id, 'exclude_list_agentkey', $values );
2349 // add Exclude_ListAgentMlsId
2350 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListAgentMlsId', $item_id, 'exclude_list_agentmlsid', $values );
2351 // add CustomParameters
2352 $values = $this->mls_import_saas_add_to_parms_input( 'CustomParameters', $item_id, 'custom_parameters', $values );
2353
2354
2355 // if we have realtorca
2356 if ($mls_id >= 7000 && $mls_id < 8000 && $last_date!=='') {
2357 $dateTime_realtorca = new DateTime($last_date, new DateTimeZone('UTC'));
2358 // Format with seconds and UTC timezone marker
2359 $last_date = $dateTime_realtorca->format('Y-m-d\TH:i:s.000\Z');
2360 }
2361
2362 // PropTx / AMPRE requires a full OData DateTimeOffset literal for the ModificationTimestamp filter.
2363 if ( mlsimport_is_proptx_provider( $mls_id ) && $last_date !== '' ) {
2364 $last_date = mlsimport_format_odata_modification_time( $last_date );
2365 }
2366
2367 if ( '' !== $last_date ) {
2368 $values['modification_time'] = $last_date;
2369 }
2370
2371 return( $values );
2372 }
2373
2374
2375
2376 /*
2377 *
2378 * add input items to parameters array
2379 *
2380 */
2381
2382 public function mls_import_saas_add_to_parms_input( $key, $post_id, $new_name, $all_values ) {
2383 $name = strtolower( 'mlsimport_item_' . $key );
2384 $value = get_post_meta( $post_id, $name, true );
2385 if ( '' !== $value ) {
2386 $all_values[ $new_name ] = $value;
2387 }
2388
2389 return $all_values;
2390 }
2391
2392
2393 /*
2394 *
2395 * add list items to parameters array
2396 *
2397 */
2398
2399 public function mls_import_return_multiple_param_value( $key, $post_id, $new_name, $all_values ) {
2400 $name_check = strtolower( 'mlsimport_item_' . $key . '_check' );
2401 $name = strtolower( 'mlsimport_item_' . $key );
2402
2403 $value = get_post_meta( $post_id, $name, true );
2404
2405 // add extra county - should be moved into function if pass tests
2406 if ( 'countyorparish' === $key ) {
2407 $extracounty_values = get_post_meta( $post_id, 'mlsimport_item_extracounty', true );
2408
2409 if ( '' !== $extracounty_values ) {
2410 $extracounty_array = explode( ',', $extracounty_values );
2411
2412 if ( ! is_array( $value ) ) {
2413 if ( '' === $value ) {
2414 $value = array();
2415 } else {
2416 $value = array( $value );
2417 }
2418 }
2419
2420 foreach ( $extracounty_array as $extra ) {
2421 $value[] = $extra;
2422 }
2423 }
2424 }
2425
2426 // add extra city - should be moved into function if pass tests
2427 if ( 'city' === $key ) {
2428 $extracity_values = get_post_meta( $post_id, 'mlsimport_item_extracity', true );
2429 if ( '' !== $extracity_values ) {
2430 $extracity_array = explode( ',', $extracity_values );
2431
2432 if ( ! is_array( $value ) ) {
2433 if ('' === $value ) {
2434 $value = array();
2435 } else {
2436 $value = array( $value );
2437 }
2438 }
2439
2440 foreach ( $extracity_array as $extra ) {
2441 $value[] = $extra;
2442 }
2443 }
2444 }
2445
2446 $value_check = get_post_meta( $post_id, $name_check, true );
2447
2448 if ( 0 === intval($value_check) && '' !== $value ) {
2449 $all_values[ $new_name ] = $value;
2450 }
2451
2452 // status exception
2453 if ( 'status' === $new_name ) {
2454 $all_values[ $new_name ] = $value;
2455 }
2456
2457 return $all_values;
2458 }
2459
2460
2461
2462 /*
2463 *
2464 * All Enums fiels to be used on MLS import Taaks
2465 *
2466 *
2467 *
2468 *
2469 *
2470 *
2471 * */
2472
2473 public function mlsimport_saas_return_mls_fields() {
2474
2475 $mlsimport_mls_metadata_mls_enums = get_option( 'mlsimport_mls_metadata_mls_enums', '' );
2476
2477 if ( '' === $mlsimport_mls_metadata_mls_enums ) {
2478 ?>
2479 <div class="mlsimport_warning long_warning">Please select the import fields(from MLS Import Settings) before starting a MLS import process.</div>
2480 <?php
2481 }
2482
2483 $metadata_api_call_full = json_decode( $mlsimport_mls_metadata_mls_enums, true );
2484
2485 if ( isset( $metadata_api_call_full['global_array'] ) ) {
2486 $metadata_api_call = $metadata_api_call_full['global_array'];
2487 }
2488
2489 $city_array = array();
2490 if ( isset( $metadata_api_call['PropertyEnums']['City'] ) && is_array( $metadata_api_call['PropertyEnums']['City'] ) ) {
2491 $city_array = array_keys( $metadata_api_call['PropertyEnums']['City'] );
2492 }
2493
2494 $county_array = array();
2495 if ( isset( $metadata_api_call['PropertyEnums']['CountyOrParish'] ) && is_array( $metadata_api_call['PropertyEnums']['CountyOrParish'] ) ) {
2496 $county_array = array_keys( $metadata_api_call['PropertyEnums']['CountyOrParish'] );
2497 }
2498
2499 $mlsstatus_array = array();
2500 if ( isset( $metadata_api_call['PropertyEnums']['MlsStatus'] ) && is_array( $metadata_api_call['PropertyEnums']['MlsStatus'] ) ) {
2501 $mlsstatus_array = array_keys( $metadata_api_call['PropertyEnums']['MlsStatus'] );
2502 }
2503
2504 $propertysubtype_array = array();
2505 if ( isset( $metadata_api_call['PropertyEnums']['PropertySubType'] ) && is_array( $metadata_api_call['PropertyEnums']['PropertySubType'] ) ) {
2506 $propertysubtype_array = array_keys( $metadata_api_call['PropertyEnums']['PropertySubType'] );
2507 }
2508
2509 $propertytype_array = array();
2510 if ( isset( $metadata_api_call['PropertyEnums']['PropertyType'] ) && is_array( $metadata_api_call['PropertyEnums']['PropertyType'] ) ) {
2511 $propertytype_array = array_keys( $metadata_api_call['PropertyEnums']['PropertyType'] );
2512 }
2513
2514
2515 $standardstatus_array = array();
2516 if ( isset( $metadata_api_call['PropertyEnums']['StandardStatus'] ) && is_array( $metadata_api_call['PropertyEnums']['StandardStatus'] ) ) {
2517 $standardstatus_array = array_keys( $metadata_api_call['PropertyEnums']['StandardStatus'] );
2518 }
2519
2520 // if we do not have standart status
2521 if ( empty( $standardstatus_array ) ) {
2522 $standardstatus_array = $mlsstatus_array;
2523 }
2524
2525
2526
2527
2528 $extracounty_values = '';
2529 $extracity_values = '';
2530
2531 $field_import = array(
2532 'City' => array(
2533 'label' => esc_html__( 'Select cities', 'mlsimport' ),
2534 'description' => esc_html__( 'Select the cities from where we will import data.', 'mlsimport' ),
2535 'type' => 'select',
2536 'multiple' => 'yes',
2537 'values' => $city_array,
2538 ),
2539
2540 'extraCity' => array(
2541 'label' => esc_html__( 'Add extra Cities', 'mlsimport' ),
2542 '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' ),
2543 'type' => 'input',
2544 'multiple' => 'no',
2545 'values' => $extracity_values,
2546 ),
2547
2548 'CountyOrParish' => array(
2549 'label' => esc_html__( 'Select Counties', 'mlsimport' ),
2550 'description' => esc_html__( 'Select the counties from where we will import data.', 'mlsimport' ),
2551 'type' => 'select',
2552 'multiple' => 'yes',
2553 'values' => $county_array,
2554 'show_extra_field' => true,
2555 ),
2556
2557 'extraCounty' => array(
2558 'label' => esc_html__( 'Add extra Counties', 'mlsimport' ),
2559 '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' ),
2560 'type' => 'input',
2561 'multiple' => 'no',
2562 'values' => $extracounty_values,
2563 ),
2564
2565 'MLSAreaMajor' => array(
2566 'label' => esc_html__( 'MLS Area Major', 'mlsimport' ),
2567 'description' => esc_html__( 'Filter listings by MLSAreaMajor.', 'mlsimport' ),
2568 'type' => 'input',
2569 'multiple' => 'no',
2570 ),
2571
2572 'SubdivisionName' => array(
2573 'label' => esc_html__( 'Subdivision Name', 'mlsimport' ),
2574 'description' => esc_html__( 'Filter listings by SubDivisionName.', 'mlsimport' ),
2575 'type' => 'input',
2576 'multiple' => 'no',
2577 ),
2578
2579 'PostalCode' => array(
2580 'label' => esc_html__( 'Select Postal Code', 'mlsimport' ),
2581 'description' => esc_html__( 'Enter one or more postal codes to import listings from, separated by commas (e.g. 12345, 23456).', 'mlsimport' ),
2582 'type' => 'input',
2583 'multiple' => 'no',
2584 ),
2585
2586 'PropertySubType' => array(
2587 'label' => esc_html__( 'Select Property Category', 'mlsimport' ),
2588 'description' => esc_html__( 'Property Category', 'mlsimport' ),
2589 'type' => 'select',
2590 'multiple' => 'yes',
2591 'values' => $propertysubtype_array,
2592 ),
2593 'PropertyType' => array(
2594 'label' => esc_html__( 'Select Property Action Category', 'mlsimport' ),
2595 'description' => esc_html__( 'Property Action Category', 'mlsimport' ),
2596 'type' => 'select',
2597 'multiple' => 'yes',
2598 'values' => $propertytype_array,
2599 ),
2600 'StandardStatus' => array(
2601 'label' => esc_html__( 'Select Status', 'mlsimport' ),
2602 'description' => __( 'The list is auto-populated with MLS available statuses. To select multiple statuses, use Ctrl (Windows) or Command (Mac).', 'mlsimport' ),
2603 'type' => 'select',
2604 'multiple' => 'yes',
2605 'values' => $standardstatus_array,
2606 ),
2607 'StandardStatusProtect' => array(
2608 'label' => esc_html__( 'Protected Statuses', 'mlsimport' ),
2609 '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' ),
2610 'type' => 'select',
2611 'multiple' => 'yes',
2612 'values' => $standardstatus_array,
2613 ),
2614
2615 'InternetEntireListingDisplayYN' => array(
2616 'label' => esc_html__( 'Internet Entire Listing Display ', 'mlsimport'),
2617 'description' => esc_html__( 'A yes/no field that states the seller has allowed the listing to be displayed on Internet sites.', 'mlsimport' ),
2618 'type' => 'select',
2619 'multiple' => 'no',
2620 'values' => array(
2621 'yes',
2622 'no',
2623 ),
2624 ),
2625 'InternetAddressDisplayYN' => array(
2626 'label' => esc_html__( 'Internet Address display', 'mlsimport' ),
2627 'description' => esc_html__( 'A yes/no field that states the seller has allowed the listing address to be displayed on Internet sites.', 'mlsimport' ),
2628 'type' => 'select',
2629 'multiple' => 'no',
2630 'values' => array(
2631 'yes',
2632 'no',
2633 ),
2634 ),
2635 'ListAgentKey' => array(
2636 'label' => esc_html__( 'ListAgentKey', 'mlsimport' ),
2637 'description' => esc_html__( 'Import listings from a specific Agent (contact your MLS for this information)', 'mlsimport' ),
2638 'type' => 'input',
2639 'multiple' => 'no',
2640 ),
2641 'ListAgentMlsId' => array(
2642 'label' => esc_html__( 'ListAgentMlsId', 'mlsimport' ),
2643 'description' => esc_html__( 'Import listings from a specific Agent (contact your MLS for this information)', 'mlsimport' ),
2644 'type' => 'input',
2645 'multiple' => 'no',
2646 ),
2647 'BuyerAgentMlsId' => array(
2648 'label' => esc_html__( 'BuyerAgentMlsId', 'mlsimport' ),
2649 'description' => esc_html__( 'Import listings from a specific Buyer Agent (contact your MLS for this information)', 'mlsimport' ),
2650 'type' => 'input',
2651 'multiple' => 'no',
2652 ),
2653 'ListOfficeKey' => array(
2654 'label' => esc_html__( 'ListOfficeKey', 'mlsimport' ),
2655 'description' => esc_html__( 'Import listings from a specific Office (contact your MLS for this information)', 'mlsimport'),
2656 'type' => 'input',
2657 'multiple' => 'no',
2658 ),
2659 'ListOfficeMlsId' => array(
2660 'label' => esc_html__( 'ListOfficeMlsId', 'mlsimport' ),
2661 'description' => esc_html__( 'Import listings from a specific Office (contact your MLS for this information)', 'mlsimport' ),
2662 'type' => 'input',
2663 'multiple' => 'no',
2664 ),
2665 'ListingId' => array(
2666 'label' => esc_html__( 'ListingId', 'mlsimport' ),
2667 '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'),
2668 'type' => 'input',
2669 'multiple' => 'no',
2670 ),
2671 'Exclude_ListOfficeMlsId' => array(
2672 'label' => esc_html__( 'Exclude listings with ListOfficeMlsId', 'mlsimport' ),
2673 'description' => esc_html__( 'Exclude listings that belong to one or more ListOfficeMlsId.', 'mlsimport' ),
2674 'type' => 'input',
2675 'multiple' => 'no',
2676 ),
2677 'Exclude_ListOfficeKey' => array(
2678 'label' => esc_html__( 'Exclude listings with ListOfficeKey', 'mlsimport' ),
2679 'description' => esc_html__( 'Exclude listings that belong to one or more ListOfficeKey', 'mlsimport'),
2680 'type' => 'input',
2681 'multiple' => 'no',
2682 ),
2683
2684
2685 'Exclude_ListAgentMlsId' => array(
2686 'label' => esc_html__( 'Exclude listings with ListAgentMlsId', 'mlsimport' ),
2687 'description' => esc_html__( 'Exclude listings that belong to one or more ListAgentMlsId.', 'mlsimport' ),
2688 'type' => 'input',
2689 'multiple' => 'no',
2690 ),
2691 'Exclude_ListAgentKey' => array(
2692 'label' => esc_html__( 'Exclude listings with ListAgentKey ', 'mlsimport' ),
2693 'description' => esc_html__( 'Exclude listings that belong to one or more ListAgentKey ', 'mlsimport'),
2694 'type' => 'input',
2695 'multiple' => 'no',
2696 ),
2697 'CustomParameters' => array(
2698 'label' => esc_html__( 'Custom parameters', 'mlsimport' ),
2699 'description' => esc_html__( 'Add raw query fragment parameters (for example: $filter=WaterfrontYN eq true). They will be forwarded to the RESO API request.', 'mlsimport' ),
2700 'type' => 'input',
2701 'multiple' => 'no',
2702 ),
2703
2704
2705 );
2706 return $field_import;
2707 }
2708
2709
2710
2711
2712
2713
2714
2715 /**
2716 *
2717 *
2718 * AYsnc Test
2719 */
2720 public function mlsimport_move_files_per_item() {
2721 check_ajax_referer( 'mlsimport_item_actions', 'security' );
2722 $post_id = 0;
2723 $how_many = 0;
2724 $max_number = 0;
2725 if(isset( $_POST['post_id'] )){
2726 $post_id = intval( $_POST['post_id'] );
2727 }
2728 if(isset( $_POST['how_many'] )){
2729 $how_many = intval( $_POST['how_many'] );
2730 }
2731 if(isset( $_POST['post_number'] )){
2732 $max_number = intval( $_POST['post_number'] );
2733 }
2734
2735
2736 $is_onboard=intval($_POST['is_onboard']);
2737
2738
2739 update_option( 'mlsimport_force_stop_' . $post_id, 'no', false );
2740
2741 $item_id_array = array(
2742 'item_id' => $post_id,
2743 'how_many' => $how_many,
2744 'max_number' => $max_number,
2745 'batch_counter' => 1,
2746 );
2747
2748
2749 update_post_meta( $post_id, 'mlsimport_attach_to_move_' . $post_id, '' );
2750
2751 $attachments_to_move = (array) $this->mlsimport_saas_generate_import_requests_per_item( $item_id_array );
2752
2753 // If an error was returned from the API, sanitize and send it back to the client and stop further processing.
2754 if ( isset( $attachments_to_move['success'] ) && false === $attachments_to_move['success'] ) {
2755 if ( isset( $attachments_to_move['message'] ) ) {
2756 $attachments_to_move['message'] = wp_strip_all_tags( $attachments_to_move['message'] );
2757 }
2758 wp_send_json( $attachments_to_move );
2759 wp_die();
2760 }
2761
2762 update_post_meta( $post_id, 'mlsimport_attach_to_move_' . $post_id, $attachments_to_move );
2763
2764 // net stat data
2765 update_post_meta( $post_id, 'mlsimport_progress_properties', 0 );
2766 update_post_meta( $post_id, 'mlsimport_progress_batches', 0 );
2767 update_post_meta( $post_id, 'mlsimport_progress_memory', 0 );
2768
2769
2770
2771 $attachments_to_send = array(
2772 'args' => array(
2773 'attachments_to_move' => $post_id,
2774 'item_id_array' => $item_id_array,
2775 'is_onboard' =>$is_onboard,
2776 ),
2777 );
2778
2779 mlsimport_saas_single_write_import_custom_logs( 'Preparing the import. Please hold on.' . PHP_EOL );
2780 mlsimport_debuglogs_per_plugin( 'Preparing the import. Please hold on.' . PHP_EOL );
2781
2782 update_post_meta( $post_id, 'mlsimport_spawn_status', 'started' );
2783
2784 // old
2785 as_enqueue_async_action( 'mlsimport_background_process_per_item', $attachments_to_send );
2786
2787 // Remove any pending async jobs for this item and enqueue a unique one
2788 //bad ideea
2789 // as_unschedule_all_actions( 'mlsimport_background_process_per_item', $attachments_to_send );
2790 //as_enqueue_async_action( 'mlsimport_background_process_per_item', $attachments_to_send, '', true );
2791
2792
2793 spawn_cron();
2794
2795 unset( $attachments_to_send );
2796
2797 // Return success response to the AJAX caller.
2798 wp_send_json( array( 'success' => true ) );
2799 }
2800
2801 /**
2802 * Process MLS Import attachments via background cron.
2803 * Memory-optimized with detailed memory usage logging.
2804 *
2805 * @param array $input_arg
2806 * @return void
2807 */
2808 public function mlsimport_background_process_per_item_cron_function( $input_arg ) {
2809 global $mlsimport;
2810
2811 $log = 'In cron processing function ->' . wp_json_encode( $input_arg['item_id_array'] ) . PHP_EOL;
2812 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2813 mlsimport_saas_single_write_import_custom_logs( '[Memory] Start: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB', 'cron' );
2814
2815 // Load attachments to move from post meta
2816 $attachments_to_move = get_post_meta(
2817 $input_arg['item_id_array']['item_id'],
2818 'mlsimport_cron_attach_to_move_' . $input_arg['item_id_array']['item_id'],
2819 true
2820 );
2821 $log = '[Memory] After loading attachments: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2822 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2823
2824 if (!empty($attachments_to_move) && is_array($attachments_to_move)) {
2825 foreach ($attachments_to_move as $key => $import_arguments) {
2826 // Optionally clear any cache for this batch
2827 if ( isset($GLOBALS['wp_object_cache']) ) {
2828 $GLOBALS['wp_object_cache']->delete('mlsimport_force_stop_' . $input_arg['item_id_array']['item_id'], 'options');
2829 }
2830
2831 $log = '[Memory] Before API batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2832 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2833
2834 // API call
2835 $api_call_array = $this->theme_importer->globalApiRequestCurlSaas('listings', $import_arguments, 'POST');
2836
2837 $log = '[Memory] After API batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2838 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2839
2840 // Parse/process response
2841 $mlsimport->admin->theme_importer->mlsimportSaasCronParseSearchArrayPerItem(
2842 $api_call_array, $input_arg['item_id_array'], $key
2843 );
2844
2845 // Free per-iteration memory
2846 unset($api_call_array, $import_arguments);
2847 gc_collect_cycles();
2848
2849 $log = '[Memory] After cleanup batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2850 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2851 }
2852 }
2853
2854 mlsimport_saas_single_write_import_custom_logs('[Memory] End: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB', 'cron' );
2855 mlsimport_saas_single_write_import_custom_logs('CRON JOB Import Completed ' . PHP_EOL, 'cron');
2856 mlsimport_debuglogs_per_plugin('CRON JOB Import Completed ' . PHP_EOL);
2857 update_post_meta($input_arg['item_id_array']['item_id'], 'mlsimport_spawn_status', 'completed');
2858
2859 unset($attachments_to_move, $input_arg, $log);
2860 gc_collect_cycles();
2861 }
2862
2863
2864
2865
2866 /**
2867 *
2868 *
2869 * Generate import Requests per item
2870 */
2871 public function mlsimport_saas_generate_import_requests_per_item( $item_id_array, $last_date = '', $is_hourly_sync = false ) {
2872 $import_step = 25;
2873
2874 $prop_id = $item_id_array['item_id'];
2875 $max_found = $item_id_array['max_number'];
2876 $how_many = $item_id_array['how_many'];
2877 if ( 0=== intval($how_many) ) {
2878 $how_many = $max_found;
2879 }
2880 if ( $how_many > $max_found ) {
2881 $how_many = $max_found;
2882 }
2883
2884 $search_url_step = '';
2885 $urls_array = array();
2886
2887 $skip = 0;
2888 if ( $how_many > 10000 ) {
2889 $how_many = 10000;
2890 }
2891 update_post_meta($prop_id,'mlsimport_task_to_import', intval($how_many) );
2892
2893 if ( $how_many < $import_step ) {
2894 $import_step = $how_many;
2895 }
2896
2897 while ( $skip < $how_many ) {
2898
2899 // Determine how many items to request for this batch.
2900 $batch_step = min( $import_step, $how_many - $skip );
2901
2902 // Build the request arguments using the remaining count.
2903 $search_url_step = $this->mlsimport_saas_make_listing_requests_arguments( $prop_id, $last_date, $skip, $batch_step, $is_hourly_sync );
2904
2905 // If the API returned an error, propagate it immediately.
2906 if ( isset( $search_url_step['success'] ) && false === $search_url_step['success'] ) {
2907 return $search_url_step;
2908 }
2909
2910 $skip += $batch_step;
2911 $urls_array[] = $search_url_step;
2912
2913
2914 }
2915 return $urls_array;
2916 }
2917
2918
2919
2920
2921
2922
2923
2924 /**
2925 * Process Async function
2926 */
2927 public function mlsimport_background_process_per_item_function( $input_arg ) {
2928
2929 $mlsimportItemId = $input_arg['item_id_array']['item_id'];
2930 $log_prefix = 'In processing function - Item ID: ' . $mlsimportItemId . ' -> ';
2931 mlsimport_saas_single_write_import_custom_logs( $log_prefix . wp_json_encode( $input_arg['item_id_array'] ) . PHP_EOL );
2932
2933
2934 // Get from MLS Import the big argument array only once
2935 $attachments_to_move = get_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId, true );
2936
2937 // Retrieve all meta data in one go to reduce database queries
2938 $mlsimport_item_option_data = array(
2939 'mlsimport_item_standardstatus' => get_post_meta( $mlsimportItemId, 'mlsimport_item_standardstatus', true ),
2940 'mlsimport_item_standardstatusprotect' => get_post_meta( $mlsimportItemId, 'mlsimport_item_standardstatusprotect', true ),
2941 'mlsimport_item_property_user' => get_post_meta( $mlsimportItemId, 'mlsimport_item_property_user', true ),
2942 'mlsimport_item_agent' => get_post_meta( $mlsimportItemId, 'mlsimport_item_agent', true ),
2943 'mlsimport_item_property_status' => get_post_meta( $mlsimportItemId, 'mlsimport_item_property_status', true ),
2944 );
2945
2946 $total_batches = count( $attachments_to_move );
2947
2948 // removed because $this
2949 global $mlsimport;
2950
2951 $log = 'In processing function $attachments_to_move ->' . wp_json_encode( $attachments_to_move ) . PHP_EOL;
2952 mlsimport_saas_single_write_import_custom_logs( $log );
2953
2954
2955 foreach ( $attachments_to_move as $key => $import_arguments ) {
2956 // reconsider use
2957 // $GLOBALS['wp_object_cache']->delete('mlsimport_force_stop_' . $input_arg['item_id_array']['item_id'], 'options');
2958 $status = get_option( 'mlsimport_force_stop_' . $mlsimportItemId );
2959 if ( 'no' === $status ) {
2960 // Clear memory before processing each batch
2961 wp_cache_flush();
2962 gc_collect_cycles();
2963
2964 // wp_cache_flush();
2965 $mem_usage = memory_get_usage( true );
2966 $mem_usage_show = round( $mem_usage / 1048576, 2 );
2967
2968 update_post_meta( $mlsimportItemId, 'mlsimport_progress_batches', $key + 1 );
2969 update_post_meta( $mlsimportItemId, 'mlsimport_progress_memory', $mem_usage_show );
2970
2971
2972
2973 mlsimport_saas_single_write_import_custom_logs( $log );
2974 $log = 'Parsing import batch: ' . ( $key + 1 ) . ' of ' . $total_batches . '. Memory used: ' . $mem_usage_show . ' MB.' . PHP_EOL;
2975
2976
2977 // Combine logs and reduce function calls
2978 mlsimport_saas_single_write_import_custom_logs( $log );
2979 mlsimport_debuglogs_per_plugin( $log );
2980 print esc_html($log);
2981
2982 $api_call_array = $this->theme_importer->globalApiRequestCurlSaas( 'listings', $import_arguments, 'POST' );
2983
2984 $mlsimport->admin->theme_importer->mlsimportSaasParseSearchArrayPerItem( $api_call_array, $input_arg['item_id_array'], $key, $mlsimport_item_option_data );
2985
2986
2987
2988 // Explicitly unset large variables after each batch
2989 unset($api_call_array);
2990
2991 // Force garbage collection again after processing
2992 wp_cache_flush();
2993 gc_collect_cycles();
2994
2995
2996 // Add a small delay to allow memory to be freed
2997 if (($key + 1) < $total_batches) {
2998 usleep(100000); // 100ms pause between batches
2999 }
3000
3001 } else {
3002
3003 $final_mem_usage = memory_get_usage( true );
3004 $final_mem_usage_show = round( $final_mem_usage / 1048576, 2 );
3005
3006
3007 update_post_meta( $mlsimportItemId, 'mlsimport_spawn_status', 'completed' );
3008 delete_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId );
3009
3010 //new stats
3011 update_post_meta( $mlsimportItemId, 'mlsimport_progress_batches', $total_batches );
3012 update_post_meta( $mlsimportItemId, 'mlsimport_progress_memory', $final_mem_usage_show );
3013
3014
3015 mlsimport_saas_single_write_import_custom_logs( PHP_EOL . 'Parsing importing link FORCE STOP : ' );
3016 mlsimport_debuglogs_per_plugin( 'Parsing importing link FORCE STOP : ' );
3017 break; // Exit the loop if forced to stop
3018 }
3019 }
3020
3021 mlsimport_saas_single_write_import_custom_logs( 'Import Completed ' . PHP_EOL );
3022 mlsimport_debuglogs_per_plugin( 'Import Completed ' . PHP_EOL );
3023
3024 update_post_meta( $mlsimportItemId, 'mlsimport_spawn_status', 'completed' );
3025 delete_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId );
3026
3027 // Final cleanup
3028 unset($attachments_to_move);
3029 unset($mlsimport_item_option_data);
3030 unset($input_arg);
3031 unset($log);
3032
3033 // One final garbage collection
3034 wp_cache_flush();
3035 gc_collect_cycles();
3036 }
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046 /**
3047 *
3048 *
3049 *
3050 * update log function
3051 */
3052 public function mlsimport_logger_per_item() {
3053 //check_ajax_referer( 'mlsimport_item_actions', 'security' );
3054 $post_id=0;
3055 if(isset($_POST['post_id'] )){
3056 $post_id = intval( $_POST['post_id'] );
3057 }
3058
3059 $status = get_post_meta( $post_id, 'mlsimport_spawn_status', true );
3060 $path = WP_PLUGIN_DIR . '/mlsimport/logs/status_logs.log';
3061 $logs = file_get_contents( $path );
3062
3063 //get neww status data
3064 $current = intval( get_post_meta( $post_id, 'mlsimport_progress_properties', true ) );
3065 $total = intval( get_post_meta( $post_id, 'mlsimport_progress_batches', true ) );
3066 $memory = get_post_meta( $post_id, 'mlsimport_progress_memory', true );
3067 $mlsimport_task_to_import = intval( get_post_meta($post_id, 'mlsimport_task_to_import',true));
3068
3069
3070
3071 $force_status = intval( get_post_meta( $post_id, 'mlsimport_force_stop', true ) );
3072 $force_status = get_option( 'mlsimport_force_stop_' . $post_id );
3073
3074 if ( 'no' !== $force_status ) {
3075 echo wp_json_encode(
3076 array(
3077 'is_done' => 'done',
3078 'status' => $status,
3079 'logs' => $logs,
3080 )
3081 );
3082 die();
3083 }
3084
3085 if ( '' === $status || 'completed' === $status ) {
3086 echo wp_json_encode(
3087 array(
3088 'is_done' => 'done',
3089 'status' => $status,
3090 'logs' => $logs,
3091 'mlsimport_progress_properties' => $current,
3092 'mlsimport_task_to_import' => $total,
3093 )
3094 );
3095 } else {
3096 // return from log
3097 echo wp_json_encode(
3098 array(
3099 'is_done' => 'wip',
3100 'status' => $status,
3101 'logs' => $logs,
3102 'mlsimport_progress_properties' => $current,
3103 'mlsimport_progress_batches' => $total,
3104 'memory' => $memory,
3105 'mlsimport_task_to_import'=>$mlsimport_task_to_import,
3106 'post_id'=>$post_id
3107
3108 )
3109 );
3110 }
3111 die();
3112 }
3113
3114
3115
3116
3117
3118
3119 /**
3120 *
3121 *
3122 *
3123 *
3124 * Force Stop Import
3125 */
3126 public function mlsimport_stop_import_per_item() {
3127
3128
3129 check_ajax_referer( 'mlsimport_item_actions', 'security' );
3130 $post_id=0;
3131 if(isset($_POST['post_id'] )){
3132 $post_id = intval( $_POST['post_id'] );
3133 }
3134 update_option( 'mlsimport_force_stop_' . $post_id, 'yes', false );
3135 // ensure caches are cleared so running processes see the update immediately
3136 if ( function_exists( 'wp_cache_delete' ) ) {
3137 wp_cache_delete( 'mlsimport_force_stop_' . $post_id, 'options' );
3138 }
3139 mlsimport_saas_single_write_import_custom_logs( 'Stopped for ' . $post_id . PHP_EOL );
3140 mlsimport_debuglogs_per_plugin( 'Stopped for ' . $post_id . PHP_EOL );
3141 wp_send_json_success();
3142 }
3143
3144
3145
3146 /*
3147 *
3148 * Get MLS Metadata
3149 *
3150 *
3151 *
3152 *
3153 **/
3154
3155 public function mlsimport_saas_get_metadata_function() {
3156 check_ajax_referer( 'mlsimport_saas_get_metadata', 'security' );
3157 $theme_Start = new ThemeImport();
3158
3159 $values = array();
3160 $options = get_option( $this->plugin_name . '_admin_options' );
3161 $url = 'clients?theme_id=' . intval( $options['mlsimport_theme_used'] );
3162
3163 $answer = $theme_Start::globalApiRequestSaas( $url, $values, 'GET' );
3164
3165 update_option( 'mlsimport_mls_metadata_populated', 'yes' );
3166
3167 update_option( 'mlsimport_mls_metadata_theme_schema', $answer['theme_schema'] );
3168 update_option( 'mlsimport_mls_metadata_mls_data', $answer['mls_data']['mls_meta_data'] );
3169 update_option( 'mlsimport_mls_metadata_mls_enums', $answer['mls_data']['mls_meta_enums'] );
3170 }
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183 /**
3184 *
3185 *
3186 * write debug logs
3187 */
3188 public function mlsimport_debuglog_cron( $message ) {
3189 if ( is_array( $message ) ) {
3190 $message = wp_json_encode( $message );
3191 }
3192 $message = date( 'F j, Y, g:i a' ) . ' -> ' . $message;
3193 global $wp_filesystem;
3194 if ( empty( $wp_filesystem ) ) {
3195 require_once ABSPATH . '/wp-admin/includes/file.php';
3196 WP_Filesystem();
3197 }
3198
3199 $path = WP_PLUGIN_DIR . '/mlsimport/logs/cron_logs.log';
3200
3201 file_put_contents( $path, $message, FILE_APPEND | LOCK_EX );
3202 }
3203
3204 }
3205