PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 6.3.4
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v6.3.4
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.4, at admin/class-mlsimport-admin.php

3,198 lines 118.0 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 }
1033 }
1034
1035 $values['mls_token'] = $mls_token;
1036 $values['mls_id'] = $mls_id;
1037 $values['mlsimport_tresle_client_id'] = $mlsimport_tresle_client_id;
1038 $values['mlsimport_tresle_client_secret'] = $mlsimport_tresle_client_secret;
1039 $values['mlsimport_connectmls_username'] = $mlsimport_connectmls_username;
1040 $values['mlsimport_connectmls_password'] = $mlsimport_connectmls_password;
1041
1042 $values['mlsimport_rapattoni_client_id'] = $mlsimport_rapattoni_client_id;
1043 $values['mlsimport_rapattoni_client_secret'] = $mlsimport_rapattoni_client_secret;
1044 $values['mlsimport_rapattoni_username'] = $mlsimport_rapattoni_username;
1045 $values['mlsimport_rapattoni_password'] = $mlsimport_rapattoni_password;
1046
1047 $values['mlsimport_paragon_client_id'] = $mlsimport_paragon_client_id;
1048 $values['mlsimport_paragon_client_secret'] = $mlsimport_paragon_client_secret;
1049
1050
1051 $values['mlsimport_realtorca_client_id'] = $mlsimport_realtorca_client_id;
1052 $values['mlsimport_realtorca_client_secret'] = $mlsimport_realtorca_client_secret;
1053
1054 $values['mlsimport_brightmls_client_id'] = $mlsimport_brightmls_client_id;
1055 $values['mlsimport_brightmls_client_secret'] = $mlsimport_brightmls_client_secret;
1056
1057
1058
1059
1060
1061
1062
1063 $answer = $this->theme_importer->globalApiRequestSaas( 'clients', $values, 'PATCH' );
1064
1065
1066
1067
1068 if ( isset( $answer['success'] ) && true === $answer['success'] ) {
1069 if ( isset( $answer['tested'] ) && true === $answer['tested'] ) {
1070 update_option( 'mlsimport_connection_test', 'yes' );
1071 mlsimport_telemetry_set_once( 'mls_connected_at', time() );
1072 } else {
1073 delete_option( 'mlsimport_connection_test' );
1074 delete_option( 'mlsimport_mls_metadata_populated' );
1075 }
1076 } else {
1077 delete_option( 'mlsimport_connection_test' );
1078 delete_option( 'mlsimport_mls_metadata_populated' );
1079 }
1080
1081 return $answer;
1082 }
1083
1084 private function mlsimport_is_connectmls_provider( $mls_id_int ) {
1085 return $mls_id_int >= 8000 && $mls_id_int < 9000 && 8001 !== (int) $mls_id_int;
1086 }
1087
1088 private function mlsimport_is_brightmls_provider( int $mls_id_int ): bool {
1089 return 8001 === $mls_id_int;
1090 }
1091
1092 /**
1093 * AJAX handler for the plugin-deactivation exit survey.
1094 *
1095 * Thin wrapper: it verifies the nonce and capability, sanitizes input,
1096 * delegates the real work to mlsimport_exit_survey_record(), and POSTs
1097 * the result to the SaaS API. The POST is fire-and-forget — a failed or
1098 * not-yet-deployed endpoint must never stop the admin from deactivating.
1099 */
1100 public function mlsimport_exit_survey_submit() {
1101 check_ajax_referer( 'mlsimport_exit_survey', 'security' );
1102 if ( ! current_user_can( 'administrator' ) ) {
1103 wp_send_json_error( 'Unauthorized' );
1104 }
1105
1106 $input = array(
1107 'reason' => sanitize_text_field( wp_unslash( $_POST['reason'] ?? '' ) ),
1108 'details' => sanitize_textarea_field( wp_unslash( $_POST['details'] ?? '' ) ),
1109 );
1110
1111 // Only record a recognized reason; an unknown value is dropped
1112 // silently rather than blocking the user or storing junk.
1113 if ( $this->mlsimport_exit_survey_is_valid_reason( $input['reason'] ) ) {
1114 $payload = $this->mlsimport_exit_survey_record( $input );
1115 try {
1116 ThemeImport::globalApiRequestSaas( 'user-activity', $payload, 'POST' );
1117 } catch ( \Throwable $e ) {
1118 // Swallow: deactivation proceeds regardless of transport failure.
1119 }
1120 }
1121
1122 wp_send_json_success();
1123 }
1124
1125 /**
1126 * Whether a submitted exit-survey reason is one of the known options.
1127 *
1128 * Pure predicate — no WordPress functions, no translation.
1129 */
1130 private function mlsimport_exit_survey_is_valid_reason( string $reason ): bool {
1131 return in_array( $reason, $this->mlsimport_exit_survey_reasons(), true );
1132 }
1133
1134 /**
1135 * Whether the current admin request is the Import Task (mlsimport_item)
1136 * post edit screen. Used to scope the Select2 asset enqueue so the
1137 * searchable-select library does not load across all of wp-admin.
1138 *
1139 * @param string $hook_suffix Current admin page hook suffix.
1140 * @param string $post_type Post type of the screen being rendered.
1141 */
1142 private function mlsimport_is_import_task_edit_screen( string $hook_suffix, string $post_type ): bool {
1143 return 'mlsimport_item' === $post_type
1144 && in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true );
1145 }
1146
1147 /**
1148 * Extra CSS class for an Import Task select field. City and County lists
1149 * can hold 300+ entries, so they are upgraded to a searchable multi-select
1150 * (Select2) via this marker class; every other field keeps the plain select.
1151 *
1152 * @param string $field_key Field key from the $field_import definition.
1153 * @return string Leading-space class string, or '' when not searchable.
1154 */
1155 private function mlsimport_searchable_select_class( string $field_key ): string {
1156 return in_array( $field_key, array( 'City', 'CountyOrParish' ), true )
1157 ? ' mlsimport-searchable-select'
1158 : '';
1159 }
1160
1161 /**
1162 * Exit-survey testable core: resolve identity and count, build payload.
1163 *
1164 * Calls no dying functions — the AJAX wrapper handles nonce/capability
1165 * and wp_send_json_*. Always returns the payload to transmit.
1166 *
1167 * @param array $input Sanitized survey input (reason, details).
1168 */
1169 private function mlsimport_exit_survey_record( array $input ): array {
1170 $opts = get_option( 'mlsimport_admin_options', array() );
1171 if ( empty( $opts['mlsimport_install_uuid'] ) ) {
1172 $opts['mlsimport_install_uuid'] = wp_generate_uuid4();
1173 update_option( 'mlsimport_admin_options', $opts );
1174 }
1175
1176 $count = (int) get_option( 'mlsimport_deactivation_count', 0 ) + 1;
1177 update_option( 'mlsimport_deactivation_count', $count );
1178
1179 $reason = (string) ( $input['reason'] ?? '' );
1180 $options = $this->get_exit_survey_options();
1181
1182 return array(
1183 'event_type' => 'exit_survey',
1184 'reason' => $reason,
1185 'reason_label' => $options[ $reason ] ?? '',
1186 'details' => (string) ( $input['details'] ?? '' ),
1187 'account' => (string) ( $opts['mlsimport_username'] ?? '' ),
1188 'install_uuid' => $opts['mlsimport_install_uuid'],
1189 'deactivation_count' => $count,
1190 'environment' => wp_get_environment_type(),
1191 'site_url' => home_url(),
1192 'admin_email' => (string) get_option( 'admin_email' ),
1193 'timestamp' => time(),
1194 );
1195 }
1196
1197 /**
1198 * The known exit-survey reason keys — the single source of truth.
1199 *
1200 * Pure: keys only, no labels, no translation. The label map
1201 * (get_exit_survey_options) builds on top of this for the modal.
1202 *
1203 * @return string[]
1204 */
1205 private function mlsimport_exit_survey_reasons(): array {
1206 return array(
1207 'built_website',
1208 'no_leads',
1209 'technical_issues',
1210 'too_expensive',
1211 'switched_tool',
1212 'other',
1213 );
1214 }
1215
1216 /**
1217 * Exit-survey reason key => display label, for the modal and payload.
1218 *
1219 * Uses translation, so it is not exercised by the pure unit suite —
1220 * is_valid_reason() relies on mlsimport_exit_survey_reasons() instead.
1221 *
1222 * @return array<string,string>
1223 */
1224 private function get_exit_survey_options(): array {
1225 return array(
1226 'built_website' => esc_html__( "Built the website, don't need ongoing sync", 'mlsimport' ),
1227 'no_leads' => esc_html__( 'Not getting leads from my site', 'mlsimport' ),
1228 'technical_issues' => esc_html__( "Technical issues I couldn't fix", 'mlsimport' ),
1229 'too_expensive' => esc_html__( 'Too expensive', 'mlsimport' ),
1230 'switched_tool' => esc_html__( 'Switched to another tool', 'mlsimport' ),
1231 'other' => esc_html__( 'Other', 'mlsimport' ),
1232 );
1233 }
1234
1235
1236
1237
1238
1239
1240 /**
1241 * Request auth token from mlsimport.net
1242 *
1243 * @since 4.0.1
1244 * returns token fron mlsimport
1245 */
1246 public function mlsimport_saas_get_mls_api_token_from_transient() {
1247
1248 $token = get_transient( 'mlsimport_saas_token' );
1249
1250 if ( false === $token || '' === $token ) {
1251 $token_json_answer = $this->mlsimport_saas_get_mls_api_token();
1252
1253 if ( isset( $token_json_answer['success'] ) && true === $token_json_answer['success'] ) {
1254 $token = $token_json_answer['token'];
1255
1256 set_transient( 'mlsimport_saas_token', $token, 3500 );
1257 }
1258 }
1259
1260 return $token;
1261 }
1262
1263
1264 /**
1265 * call for token
1266 *
1267 * @since 4.0.1
1268 * returns token fron mlsimport
1269 */
1270 protected function mlsimport_saas_get_mls_api_token() {
1271 $values = array();
1272 $options = get_option( $this->plugin_name . '_admin_options' );
1273
1274 // Check if the MLS provider has changed since the last run
1275 $prev_mls = get_option( 'mlsimport_prev_mls_name', '' );
1276
1277
1278 $username = '';
1279 if ( isset( $options['mlsimport_username'] ) ) {
1280 $username = sanitize_text_field( trim( $options['mlsimport_username'] ) );
1281 }
1282
1283 $password = '';
1284 if ( isset( $options['mlsimport_password'] ) ) {
1285 $password = sanitize_text_field( trim( $options['mlsimport_password'] ) );
1286 }
1287 $mls_name = '';
1288 if ( isset( $options['mlsimport_mls_name'] ) ) {
1289 $mls_name = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) );
1290 }
1291
1292 $mls_token = '';
1293 if ( isset( $options['mlsimport_mls_token'] ) ) {
1294 $mls_token = sanitize_text_field( trim( $options['mlsimport_mls_token'] ) );
1295 }
1296
1297 if ( $prev_mls !== '' && $prev_mls !== $mls_name ) {
1298 delete_transient( 'mlsimport_token_request' );
1299 delete_transient( 'mlsimport_metadata_api_call_data_service_property' );
1300 delete_transient( 'mls_import_meta_enums' );
1301 delete_transient( 'mls_import_meta' );
1302 delete_transient( 'mlsimport_plugin_data_schema' );
1303 delete_transient( 'mlsimport_ready_to_go_mlsimport_data' );
1304 delete_transient( 'mlsimport_saas_token' );
1305
1306 delete_option( 'mlsimport_mls_metadata_populated' );
1307
1308 delete_option( 'mlsimport_admin_fields_select' );
1309 }
1310
1311 update_option( 'mlsimport_prev_mls_name', $mls_name );
1312
1313
1314
1315 $values['username'] = $username;
1316 $values['password'] = $password;
1317
1318 if ( '' === $username || '' === $password ) {
1319 return '';
1320 }
1321
1322 $theme_Start = new ThemeImport();
1323 $answer = $theme_Start::globalApiRequestSaas( 'token', $values, 'POST' );
1324
1325
1326
1327 return $answer;
1328 }
1329
1330
1331
1332
1333
1334
1335 /**
1336 * save meta options
1337 *
1338 * @since 3.0.1
1339 */
1340 public function mlsimport_item_product_metaboxes() {
1341 add_meta_box( 'mlsimport_item_metaboxes-sectionid', __( 'Set Import data', 'mlsimport' ), array( $this, 'mlsimport_saas_display_meta_options' ), 'mlsimport_item', 'normal', 'default' );
1342 }
1343
1344
1345
1346 /**
1347 *
1348 *
1349 *
1350 * save meta options
1351 *
1352 * @since 3.0.1
1353 */
1354 public function mlsimport_item_product_save_metaboxes( $post_id, $post ) {
1355
1356 if ( ! is_object( $post ) || ! isset( $post->post_type ) ) {
1357 return;
1358 }
1359
1360 if ( 'mlsimport_item' !== $post->post_type ) {
1361 return;
1362 }
1363
1364 $allowed_keys = array(
1365 'mlsimport_item_how_many',
1366 'mlsimport_item_title_format',
1367 'mlsimport_item_agent',
1368 'mlsimport_item_property_status',
1369 'mlsimport_item_property_user',
1370 'mlsimport_item_min_price',
1371 'mlsimport_item_max_price',
1372 'mlsimport_item_city_check',
1373 'mlsimport_item_city',
1374 'mlsimport_item_city[]',
1375 'mlsimport_item_countyorparish_check',
1376 'mlsimport_item_countyorparish',
1377 'mlsimport_item_mlsstatus_check',
1378 'mlsimport_item_mlsstatus',
1379 'mlsimport_item_propertysubtype_check',
1380 'mlsimport_item_propertysubtype',
1381 'mlsimport_item_propertytype_check',
1382 'mlsimport_item_propertytype',
1383 'mlsimport_item_standardstatus_check',
1384 'mlsimport_item_standardstatus',
1385 'mlsimport_item_standardstatusprotect_check',
1386 'mlsimport_item_standardstatusprotect',
1387
1388 'mlsimport_item_internetentirelistingdisplayyn',
1389 'mlsimport_item_internetaddressdisplayyn',
1390 'mlsimport_item_stat_cron',
1391 'mlsimport_item_listagentkey',
1392 'mlsimport_item_listagentmlsid',
1393 'mlsimport_item_buyeragentmlsid',
1394 'mlsimport_item_listofficekey',
1395 'mlsimport_item_postalcode',
1396 'mlsimport_item_listofficemlsid',
1397 'mlsimport_item_listingid',
1398 'mlsimport_item_extracity',
1399 'mlsimport_item_extracounty',
1400 'mlsimport_item_exclude_listofficemlsid',
1401 'mlsimport_item_exclude_listofficekey',
1402 'mlsimport_item_exclude_listagentmlsid',
1403 'mlsimport_item_exclude_listagentkey',
1404 'mlsimport_item_customparameters',
1405 'mlsimport_item_mlsareamajor',
1406 'mlsimport_item_subdivisionname',
1407 );
1408
1409
1410
1411
1412 foreach ( $allowed_keys as $key => $key_value ) {
1413 if( isset($_POST[$key_value]) ){
1414 $postmeta = mlsimport_sanitize_multi_dimensional_array ( $_POST[$key_value] ) ;
1415 update_post_meta( $post_id, sanitize_key( $key_value ), $postmeta );
1416 }
1417
1418 }
1419
1420 $blank_keys = array(
1421 'mlsimport_item_standardstatus',
1422 'mlsimport_item_standardstatusprotect',
1423 'mlsimport_item_city',
1424 'mlsimport_item_countyorparish',
1425 'mlsimport_item_propertysubtype',
1426 'mlsimport_item_propertytype',
1427 'mlsimport_item_standardstatus',
1428 'mlsimport_item_listingid',
1429 'mlsimport_item_customparameters',
1430 'mlsimport_item_mlsareamajor',
1431 'mlsimport_item_subdivisionname',
1432
1433 );
1434
1435 foreach ( $blank_keys as $key ) {
1436 if ( ! isset( $_POST[ $key ] ) ) {
1437 update_post_meta( $post_id, $key, '' );
1438 }
1439 }
1440
1441
1442 }
1443
1444
1445 /**
1446 * Display Meta Options
1447 *
1448 * @param WP_Post $post The post object.
1449 */
1450 public function mlsimport_saas_display_meta_options($post) {
1451 wp_nonce_field(plugin_basename(__FILE__), 'estate_agent_noncename');
1452 global $mlsimport;
1453
1454 $token = $mlsimport->admin->mlsimport_saas_get_mls_api_token_from_transient();
1455 $is_mls_connected = get_option('mlsimport_connection_test', '');
1456 $mlsimport->admin->mlsimport_saas_setting_up();
1457
1458 if ('yes' !== $is_mls_connected) {
1459 $mlsimport->admin->mlsimport_saas_check_mls_connection();
1460 $is_mls_connected = get_option('mlsimport_connection_test', '');
1461 }
1462
1463 if (trim($token) === '') {
1464 echo '<div class="mlsimport_warning">' . esc_html__('You are not connected to MlsImport - Please check your Username and Password.', 'mlsimport') . '</div>';
1465 return;
1466 }
1467
1468 if ('yes' !== $is_mls_connected) {
1469 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>';
1470 return;
1471 }
1472
1473 $postId = $post->ID;
1474 $mlsimportItemHowMany = esc_html(get_post_meta($postId, 'mlsimport_item_how_many', true));
1475 $mlsimportItemStatCron = esc_html(get_post_meta($postId, 'mlsimport_item_stat_cron', true));
1476 $lastDate = get_post_meta($postId, 'mlsimport_last_date', true);
1477 $status = get_option('mlsimport_force_stop_' . $postId);
1478 $fieldImport = $this->mlsimport_saas_return_mls_fields();
1479 $options = get_option('mlsimport_admin_options');
1480 $mlsimportMlsId = isset($options['mlsimport_mls_name']) && $options['mlsimport_mls_name'] !== ''
1481
1482 ? intval($options['mlsimport_mls_name'])
1483 : 0;
1484
1485 $mlsRequest = $this->mlsimport_make_listing_requests($postId);
1486 // print_r($mlsRequest);
1487
1488 $hasError = isset($mlsRequest['success']) && !$mlsRequest['success'];
1489 if ($hasError) {
1490 echo '<div class="mlsimport_warning">' . esc_html($mlsRequest['message']) . '</div>';
1491 }
1492
1493 $foundItems = isset($mlsRequest['results']) ? intval($mlsRequest['results']) : 'none';
1494 if ($foundItems === 'none') {
1495 $mlsimport->admin->mlsimport_saas_check_mls_connection();
1496 esc_html_e('Your Token was expired. Please refresh the page to renew it wait while we renew it.', 'mlsimport');
1497 }
1498
1499 echo $this->generateMetaOptionsHtml($postId, $foundItems, $lastDate, $mlsimportItemHowMany, $mlsimportItemStatCron, $mlsimportMlsId, $fieldImport, $hasError);
1500 }
1501
1502
1503
1504
1505 /**
1506 * Generate Meta Options HTML
1507 *
1508 * @param int $postId The post ID.
1509 * @param int $foundItems The number of found items.
1510 * @param string $lastDate The last date checked.
1511 * @param string $mlsimportItemHowMany How many items to import.
1512 * @param string $mlsimportItemStatCron The status of the cron job.
1513 * @param int $mlsimportMlsId The MLS import ID.
1514 * @param array $fieldImport The fields to import.
1515 * @return string The generated HTML.
1516 */
1517 private function generateMetaOptionsHtml($postId, $foundItems, $lastDate, $mlsimportItemHowMany, $mlsimportItemStatCron, $mlsimportMlsId, $fieldImport, $hasError = false) {
1518
1519
1520 ob_start();
1521
1522 $metadata_api_call_city = array();
1523 $metadata_api_call_county = array();
1524 $metadata_api_call_property_type = array();
1525 $mlsimport_mls_metadata_mls_enums = get_option('mlsimport_mls_metadata_mls_enums', '');
1526 if ('' !== $mlsimport_mls_metadata_mls_enums) {
1527 $metadata_api_call_full = json_decode($mlsimport_mls_metadata_mls_enums, true);
1528 if (isset($metadata_api_call_full['global_array']['PropertyEnums'])) {
1529 $property_enums = $metadata_api_call_full['global_array']['PropertyEnums'];
1530 if (isset($property_enums['City']) && is_array($property_enums['City'])) {
1531 $metadata_api_call_city = $property_enums['City'];
1532 }
1533
1534 if (isset($property_enums['CountyOrParish']) && is_array($property_enums['CountyOrParish'])) {
1535 $metadata_api_call_county = $property_enums['CountyOrParish'];
1536 }
1537
1538 if (isset($property_enums['PropertyType']) && is_array($property_enums['PropertyType'])) {
1539 $metadata_api_call_property_type = $property_enums['PropertyType'];
1540 }
1541 }
1542 }
1543
1544 ?>
1545 <div class="mlsimport_item_search_url" style="display:none;"><?php echo esc_html__('Last date/time we check :', 'mlsimport') . ' ' . esc_html($lastDate); ?></div>
1546 <ul>
1547 <li>1. Set the import parameters.</li>
1548 <li>2. Hit Publish or Update, otherwise import will not work correctly.</li>
1549 <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>
1550 <li>4. Press the Update button after you make any change in the import settings.</li>
1551 </ul>
1552
1553 <?php if (is_numeric($foundItems) && $foundItems >= 500): ?>
1554 <div class="mlsimport_notification">
1555 <?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'); ?>
1556 </div>
1557 <?php endif; ?>
1558
1559 <div class="mlsimport_import_no">
1560 <?php esc_html_e('We found', 'mlsimport'); ?>
1561 <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.
1562 </div>
1563
1564 <fieldset class="mlsimport-fieldset">
1565 <label class="mlsimport-label" for="mlsimport_item_how_many">
1566 <?php esc_html_e('How Many to import. Use 0 if you want to import all listings found.', 'mlsimport'); ?>
1567 </label>
1568 <input type="text" id="mlsimport_item_how_many" name="mlsimport_item_how_many"
1569 class="mlsimport-input mlsimport-2025-input " value="<?php echo esc_attr($mlsimportItemHowMany); ?>"/>
1570 </fieldset>
1571
1572 <fieldset class="mlsimport-fieldset mlsimport_auto_switch">
1573 <?php esc_html_e('Enable Auto Update every hour?', 'mlsimport'); ?>
1574 <label class="mlsimport_switch">
1575 <input type="hidden" value="0" name="mlsimport_item_stat_cron">
1576 <input type="checkbox" class="mlsimport-import-checkbox" value="1" name="mlsimport_item_stat_cron"<?php if (intval($mlsimportItemStatCron) !== 0) echo esc_html(' checked'); ?>>
1577 <span class="slider round"></span>
1578 </label>
1579 </fieldset>
1580
1581 <?php if ($mlsimportItemStatCron !== '' && !$hasError): ?>
1582 <div id="mlsimport_item_status">Ready to import!</div>
1583 <div id="mlsimport_item_progress" class="mlsimport-progress-bar">
1584 <div class="mlsimport-progress-bar-inner" style="width:0%;"></div>
1585 </div>
1586 <input class="button mlsimport_button save_data " type="button" id="mlsimport-start_item"
1587 data-post-number="<?php echo intval($foundItems); ?>"
1588 data-post_id="<?php echo intval($postId); ?>" value="Start Import">
1589 <input class="button mlsimport_button error_action" type="button" id="mlsimport_stop_item"
1590 data-post-number="<?php echo intval($foundItems); ?>"
1591 data-post_id="<?php echo intval($postId); ?>" value="Stop Import">
1592 <?php endif; ?>
1593
1594 <input type="hidden" id="mlsimport_item_actions" value="<?php echo esc_attr(wp_create_nonce("mlsimport_item_actions")); ?>"/>
1595 <div class="mlsimport_param_wrapper"><h2><?php esc_html_e('Import Parameters', 'mlsimport'); ?></h2>
1596
1597 <?php
1598 $mlsimportItemTitleFormat = esc_html(get_post_meta($postId, 'mlsimport_item_title_format', true));
1599 ?>
1600
1601 <fieldset class="mlsimport-fieldset">
1602 <label class="mlsimport-label" for="mlsimport_item_title_format">
1603 <?php esc_html_e('Title Format', 'mlsimport'); ?>
1604 </label>
1605
1606 <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>
1607 <input type="text" id="mlsimport_item_title_format" name="mlsimport_item_title_format"
1608 class="mlsimport-input mlsimport-2025-input"
1609 value="<?php echo '' !== $mlsimportItemTitleFormat ? trim(esc_html($mlsimportItemTitleFormat)) : esc_html('{Address},{City},{CountyOrParish},{PropertyType}'); ?>"/>
1610 </fieldset>
1611
1612 <?php
1613 $mlsimportItemAgent = esc_html(get_post_meta($postId, 'mlsimport_item_agent', true));
1614 ?>
1615
1616 <fieldset class="mlsimport-fieldset">
1617 <label class="mlsimport-label" for="mlsimport_item_agent">
1618 <?php esc_html_e('Select Agent', 'mlsimport'); ?>
1619 </label>
1620 <select class="mlsimport-select mlsimport-2025-select" name="mlsimport_item_agent" id="mlsimport_item_agent">
1621 <?php
1622 $permitedTags = mlsimport_allowed_html_tags_content();
1623 $selectAgent =$this->theme_importer->mlsimportSaasThemeImportSelectAgent($mlsimportItemAgent);
1624 print wp_kses($selectAgent, $permitedTags);
1625 ?>
1626 </select>
1627 </fieldset>
1628
1629 <?php
1630 $mlsimportItemPropertyStatus = esc_html(get_post_meta($postId, 'mlsimport_item_property_status', true));
1631 if ('' === $mlsimportItemPropertyStatus) {
1632 $mlsimportItemPropertyStatus = 'publish';
1633 }
1634 $statusArray = array('publish', 'draft');
1635 ?>
1636 <fieldset class="mlsimport-fieldset">
1637 <label class="mlsimport-label" for="mlsimport_item_property_status">
1638 <?php esc_html_e('Select Property Status on import', 'mlsimport'); ?>
1639 </label>
1640 <select class="mlsimport-select mlsimport-2025-select" name="mlsimport_item_property_status" id="mlsimport_item_property_status">
1641 <?php foreach ($statusArray as $value): ?>
1642 <option value="<?php echo esc_attr($value); ?>" <?php if ($value === $mlsimportItemPropertyStatus) echo esc_html('selected'); ?>>
1643 <?php echo esc_html($value); ?>
1644 </option>
1645 <?php endforeach; ?>
1646 </select>
1647 </fieldset>
1648
1649 <?php
1650 $mlsimportItemPropertyUser = esc_html(get_post_meta($postId, 'mlsimport_item_property_user', true));
1651 ?>
1652 <fieldset class="mlsimport-fieldset">
1653 <label class="mlsimport-label" for="mlsimport_item_property_user">
1654 <?php esc_html_e('User', 'mlsimport'); ?>
1655 </label>
1656 <select class="mlsimport-select mlsimport-2025-select" id="mlsimport_item_property_user" name="mlsimport_item_property_user">
1657 <?php
1658 $selectUser = $this->theme_importer->mlsimportSaasThemeImportSelectUser($mlsimportItemPropertyUser);
1659 print wp_kses($selectUser, $permitedTags);
1660 ?>
1661 </select>
1662 </fieldset>
1663
1664 <?php
1665 $mlsimportItemMinPrice = floatval(get_post_meta($postId, 'mlsimport_item_min_price', true));
1666 $mlsimportItemMaxPrice = floatval(get_post_meta($postId, 'mlsimport_item_max_price', true));
1667 if (0 === intval($mlsimportItemMaxPrice)) {
1668 $mlsimportItemMaxPrice = 10000000;
1669 }
1670 ?>
1671 <fieldset class="mlsimport-fieldset">
1672 <label class="mlsimport-label">
1673 <?php esc_html_e('Price Between', 'mlsimport'); ?>
1674 </label>
1675 <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
1676 <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); ?>">
1677 </fieldset>
1678
1679 <?php
1680 $options = get_option($this->plugin_name . '_admin_options');
1681
1682 $mlsId = '';
1683 if (isset($options['mlsimport_mls_name'])) {
1684 $mlsId = sanitize_text_field(trim($options['mlsimport_mls_name']));
1685 }
1686
1687 if ($mlsId > 5000) {
1688 $fieldImport['PropertyType']['multiple'] = 'no';
1689 }
1690
1691
1692 if ($mlsId >= 7000) {
1693 // there is no such thing for realtor.ca
1694 unset($fieldImport['PropertyType']);
1695 }
1696
1697
1698
1699
1700 foreach ($fieldImport as $key => $field):
1701 if (!empty($field['hidden'])) {
1702 continue;
1703 }
1704 $nameCheck = strtolower('mlsimport_item_' . $key . '_check');
1705 $name = strtolower('mlsimport_item_' . $key);
1706
1707 $value = get_post_meta($postId, $name, true);
1708 $valueCheck = get_post_meta($postId, $nameCheck, true);
1709 $extraClass = '';
1710 if ('extraCity' === $key || 'extraCounty' === $key) {
1711 $extraClass = ' mlsimport_hidden_field_button button mlsimport_button';
1712 }
1713 ?>
1714 <fieldset class="mlsimport-fieldset">
1715 <label class="mlsimport-label <?php echo esc_attr($extraClass); ?>" for="<?php echo esc_attr($name); ?>">
1716 <?php echo esc_html($field['label']); ?>
1717 </label>
1718 <?php if ('extraCity' === $key || 'extraCounty' === $key): ?>
1719 <div class="mlsimport-input-wrapper" style="display:none">
1720 <?php endif; ?>
1721 <p class="mlsimport-exp"><?php echo wp_kses_post($this->mlsimport_notes_for_mls($mlsimportMlsId, $name, $field['description'])); ?>
1722 <?php
1723 $isCheckboxAdmin = 0;
1724 if (1 === intval($valueCheck)) {
1725 $isCheckboxAdmin = 1;
1726 }
1727
1728 $selectAllNone = [
1729 'InternetAddressDisplayYN',
1730 'InternetEntireListingDisplayYN',
1731 'PostalCode',
1732 'ListAgentKey',
1733 'ListAgentMlsId',
1734 'BuyerAgentMlsId',
1735 'ListOfficeKey',
1736 'ListOfficeMlsId',
1737 'StandardStatus',
1738 'ListingId',
1739 'extraCity',
1740 'extraCounty',
1741 'Exclude_ListOfficeKey',
1742 'Exclude_ListOfficeMlsId',
1743 'Exclude_ListAgentKey',
1744 'Exclude_ListAgentMlsId',
1745 'CustomParameters',
1746 'MLSAreaMajor',
1747 'SubdivisionName',
1748 ];
1749
1750 if ($mlsId > 5000) {
1751 $selectAllNone[] = 'PropertyType';
1752 }
1753
1754 if (!in_array($key, $selectAllNone)): ?>
1755 <?php
1756 esc_html_e('- Or Select All ', 'mlsimport');
1757
1758 ?>
1759 <input type="hidden" name="<?php echo esc_attr($nameCheck); ?>" value="0"/>
1760 <input type="checkbox" class="mlsimport-import-checkbox" name="<?php echo esc_attr($nameCheck); ?>" value="1" <?php print esc_attr(checked($isCheckboxAdmin, 1, 0)); ?>/>
1761 <?php endif; ?>
1762 </p>
1763
1764 <?php
1765 $permittedStatus = ['active', 'active under contract', 'coming soon', 'activeundercontract', 'comingsoon', 'pending'];
1766
1767 if ($field['type'] === 'select'): ?>
1768 <?php
1769 $multiple = '';
1770 if ('yes' === $field['multiple']) {
1771 $multiple = 'multiple';
1772 $name .= '[]';
1773 }
1774
1775 if ('StandardStatus' === $key && '' === $value) {
1776 $value = ['Active'];
1777 }
1778
1779
1780
1781 // City/County lists can hold 300+ entries — render a filter
1782 // input above the full native multi-select listbox.
1783 $searchableClass = $this->mlsimport_searchable_select_class($key);
1784 $isSearchable = '' !== $searchableClass;
1785 $searchPlaceholder = $isSearchable
1786 ? esc_html__('Type to search…', 'mlsimport')
1787 : '';
1788
1789 // Additional conditions can be placed here.
1790 ?>
1791 <?php if ($isSearchable): ?>
1792 <div class="mlsimport-selected-chips" aria-live="polite"></div>
1793 <input type="text" class="mlsimport-select-search" placeholder="<?php echo esc_attr($searchPlaceholder); ?>" aria-label="<?php echo esc_attr($searchPlaceholder); ?>" autocomplete="off">
1794 <?php endif; ?>
1795 <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); ?>>
1796 <?php foreach ($field['values'] as $selectKey): ?>
1797
1798 <?php if ('' !== $selectKey): ?>
1799 <?php
1800 $option_value = $selectKey;
1801 $option_label = $selectKey;
1802 $comparison_values = array($option_value);
1803
1804 if ('City' === $key && isset($metadata_api_call_city[$selectKey])) {
1805 $option_label = $selectKey;
1806 $comparison_values[] = $metadata_api_call_city[$selectKey];
1807 } elseif ('CountyOrParish' === $key && isset($metadata_api_call_county[$selectKey])) {
1808 $option_label = $selectKey;
1809 $comparison_values[] = $metadata_api_call_county[$selectKey];
1810 } elseif ('PropertyType' === $key && isset($metadata_api_call_property_type[$selectKey])) {
1811 $option_label = $selectKey;
1812 $comparison_values[] = $metadata_api_call_property_type[$selectKey];
1813 }
1814
1815 $comparison_values = array_values(array_unique(array_filter($comparison_values, static function ($compare_value) {
1816 return '' !== $compare_value && null !== $compare_value;
1817 })));
1818
1819 $is_selected = false;
1820 if (is_array($value)) {
1821 $is_selected = count(array_intersect($comparison_values, $value)) > 0;
1822 } else {
1823 $is_selected = in_array($value, $comparison_values, true);
1824 }
1825 ?>
1826 <option value="<?php echo esc_attr($option_value); ?>" <?php echo $is_selected ? 'selected' : ''; ?>>
1827 <?php echo esc_html($option_label); ?>
1828 </option>
1829 <?php endif; ?>
1830
1831 <?php endforeach; ?>
1832 </select>
1833
1834 <?php elseif ($field['type'] === 'input'): ?>
1835 <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); ?>">
1836 <?php endif; ?>
1837 <?php if ('extraCity' === $key || 'extraCounty' === $key): ?>
1838 </div>
1839 <?php endif; ?>
1840 </fieldset>
1841 <?php endforeach; ?>
1842
1843 </div>
1844 <?php
1845 return ob_get_clean();
1846 }
1847
1848
1849
1850
1851
1852
1853 public function mlsimport_add_extra_fields() {
1854 }
1855
1856 /**
1857 *
1858 *
1859 *
1860 *
1861 *
1862 *
1863 */
1864 function mlsimport_notes_for_mls( $mlsimport_mls_id, $name, $description ) {
1865 // 111 - Rae Edmonton
1866
1867 if ( 111 === intval($mlsimport_mls_id) && 'mlsimport_item_standardstatus' === $name ) {
1868 return esc_html__( 'Your MLS does not use this field - all listings are considered Active.', 'mlsimport' );
1869 } else {
1870 return $description;
1871 }
1872 }
1873
1874
1875 /**
1876 *
1877 *
1878 * Get Last date
1879 */
1880 public function mlsimport_saas_get_last_date( $item_id ) {
1881 $last_date = get_post_meta( $item_id, 'mlsimport_last_date', true );
1882
1883 if ( '' === $last_date ) {
1884 $last_date = $this->mlsimport_saas_update_last_date( $item_id );
1885 }
1886 return $last_date;
1887 }
1888
1889
1890 /**
1891 *
1892 *
1893 * Save Last date
1894 */
1895 public function mlsimport_saas_update_last_date( $item_id ) {
1896
1897 $unix_time = current_time( 'timestamp', 0 ) - ( 2 * 60 * 60 );
1898 print $last_date_to_save = date( 'Y-m-d\TH:i', $unix_time );
1899 update_post_meta( $item_id, 'mlsimport_last_date', $last_date_to_save );
1900
1901 return $last_date_to_save;
1902 }
1903
1904
1905
1906
1907
1908 /**
1909 * Check and process MLSimport item for modified listings in the last 2 hours.
1910 * Optimized for memory: logs memory, unsets large arrays, and triggers garbage collection.
1911 *
1912 * @param int $item_id
1913 * @return int Number of listings found in the MLS feed, or 0 on failure.
1914 */
1915 public function mlsimport_saas_start_cron_links_per_item( int $item_id ): int {
1916 // Log memory before start
1917
1918 $found_items = 0;
1919
1920 // Auto-sync only maintains tasks the realtor has imported at least once
1921 // by hand. A task created and forgotten has no 'mlsimport_spawn_status'
1922 // meta, so the cron skips it instead of pulling the whole MLS feed.
1923 if ( '' === get_post_meta( $item_id, 'mlsimport_spawn_status', true ) ) {
1924 return 0;
1925 }
1926
1927 $last_date = $this->mlsimport_saas_get_last_date( $item_id );
1928 print 'MLSitem id: ' . $item_id . ' - ';
1929 esc_html_e('date to consider: ','mlsimport');
1930 print esc_html($last_date) . '. ';
1931
1932 // Make request to MLS API
1933 $mlsrequest = $this->mlsimport_make_listing_requests( $item_id, $last_date, '', '', true );
1934
1935 if ( isset( $mlsrequest['results'] ) ) {
1936 $found_items = intval( $mlsrequest['results'] );
1937 } else {
1938 delete_transient( 'mlsimport_saas_token' );
1939 mlsimport_telemetry_set( 'last_sync_failed', time() );
1940 mlsimport_telemetry_set( 'last_sync_failed_code', (string) ( $mlsrequest['error_code'] ?? 'unknown' ) );
1941 }
1942 print esc_html__('We found ','mlsimport') . esc_html( $found_items ) . ' listings.</br>' . PHP_EOL;
1943
1944 // Only process if items found
1945 if ( $found_items > 0 ) {
1946
1947 $item_id_array = array(
1948 'item_id' => $item_id,
1949 'how_many' => 0,
1950 'max_number' => $found_items,
1951 'batch_counter' => 1,
1952 );
1953
1954 // Potentially large array, log memory before/after
1955 $attachments_to_move = (array) $this->mlsimport_saas_generate_import_requests_per_item( $item_id_array, $last_date, true );
1956
1957 // Store in post meta (beware if array is huge)
1958 update_post_meta( $item_id, 'mlsimport_spawn_status_cron_job', 'started' );
1959 update_post_meta( $item_id, 'mlsimport_cron_attach_to_move_' . $item_id, $attachments_to_move );
1960
1961 // Save last date for next run
1962 $this->mlsimport_saas_update_last_date( $item_id );
1963
1964 // Prepare and pass only necessary arguments to background process
1965 $attachments_to_send = array(
1966 'args' => array(
1967 'attachments_to_move' => $item_id,
1968 'item_id_array' => $item_id_array,
1969 ),
1970 );
1971
1972 $this->mlsimport_background_process_per_item_cron_function( $attachments_to_send['args'] );
1973
1974 // Unset large arrays/objects after use
1975 unset($attachments_to_move, $attachments_to_send, $mlsrequest, $item_id_array);
1976 gc_collect_cycles();
1977 }
1978
1979 return $found_items;
1980 }
1981
1982
1983
1984
1985
1986
1987 /**
1988 * Reconciliation log (optimized, batched, memory logged)
1989 */
1990 public function mlsimport_saas_start_doing_reconciliation() {
1991 global $mlsimport, $wpdb;
1992
1993
1994 // Get all MLS keys in memory (we assume this is necessary for lookup)
1995 $mls_data = $this->mlsimport_saas_get_mls_reconciliation_data();
1996 $listingKey_in_MLS = $mls_data['all_data'] ?? [];
1997
1998 unset($mls_data);
1999 gc_collect_cycles();
2000
2001 if (empty($listingKey_in_MLS)) {
2002 return;
2003 }
2004 // Flip for fast lookup
2005 $listingKey_in_MLS = array_flip($listingKey_in_MLS);
2006
2007 // Batch fetch local listings
2008 $batch = 1000;
2009 $offset = 0;
2010 $to_delete = 0;
2011 $counter = 0;
2012
2013 $mlsimport_preload_all_mls_item_status_meta = $this->mlsimport_preload_all_mls_item_status_meta();
2014 //print_r($mlsimport_preload_all_mls_item_status_meta);
2015
2016 do {
2017 $local = $wpdb->get_results(
2018 $wpdb->prepare(
2019 "SELECT
2020 p.ID,
2021 listingkey_meta.meta_value AS listingkey,
2022 inserted_meta.meta_value AS mlsimport_item_inserted
2023 FROM {$wpdb->posts} p
2024 INNER JOIN {$wpdb->postmeta} listingkey_meta
2025 ON p.ID = listingkey_meta.post_id
2026 AND listingkey_meta.meta_key = %s
2027 LEFT JOIN {$wpdb->postmeta} inserted_meta
2028 ON p.ID = inserted_meta.post_id
2029 AND inserted_meta.meta_key = %s
2030 WHERE p.post_status NOT IN ('draft', 'trash')
2031 LIMIT %d OFFSET %d",
2032 'ListingKey',
2033 'MLSimport_item_inserted',
2034 $batch,
2035 $offset
2036 ),
2037 ARRAY_A
2038 );
2039
2040 $count = count($local);
2041
2042
2043 foreach ($local as $item) {
2044 $listingkey = $item['listingkey']; // not 'meta_value' anymore
2045 $property_id = $item['ID'];
2046 $mlsimportItemId = $item['mlsimport_item_inserted'];
2047 ++$counter;
2048 // IN MLS
2049 if (isset($listingKey_in_MLS[$listingkey])) {
2050
2051 if (!empty($mlsimportItemId) && isset($mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId])) {
2052 $mlsimport_item_standardstatus = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatus'] ?? null;
2053 $mlsimport_item_standardstatusprotect = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatusprotect'] ?? null;
2054 } else {
2055 $mlsimport_item_standardstatus = null;
2056 $mlsimport_item_standardstatusprotect = null;
2057 }
2058
2059 $keep_when_in_mls = $mlsimport->admin->theme_importer->check_if_delete_when_status_when_in_mls($property_id, $mlsimport_item_standardstatus, $mlsimport_item_standardstatusprotect);
2060 if (!$keep_when_in_mls) {
2061 ++$to_delete;
2062 $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql($property_id, $listingkey);
2063 }
2064 } else {
2065 // NOT IN MLS
2066
2067 if (!empty($mlsimportItemId) && isset($mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId])) {
2068 $mlsimport_item_standardstatus = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatus'] ?? null;
2069 $mlsimport_item_standardstatusprotect = $mlsimport_preload_all_mls_item_status_meta[$mlsimportItemId]['mlsimport_item_standardstatusprotect'] ?? null;
2070 } else {
2071 $mlsimport_item_standardstatus = null;
2072 $mlsimport_item_standardstatusprotect = null;
2073 }
2074 $keep = $mlsimport->admin->theme_importer->check_if_delete_when_status($property_id, $mlsimport_item_standardstatus, null, $mlsimport_item_standardstatusprotect);
2075 if (!$keep) {
2076 ++$to_delete;
2077 $mlsimport->admin->theme_importer->mlsimportSaasDeletePropertyViaMysql($property_id, $listingkey);
2078 }
2079 }
2080
2081 // Memory housekeeping
2082 unset($listingkey, $property_id, $mlsimportItemId, $mlsImportItemStatus, $mlsimport_item_standardstatusprotect, $keep_when_in_mls, $keep);
2083 if ($counter % 250 == 0) {
2084 gc_collect_cycles();
2085 }
2086 }
2087
2088 unset($local);
2089 gc_collect_cycles();
2090
2091 $offset += $batch;
2092 } while ($count === $batch);
2093
2094
2095 print esc_html(' to delete:' . $to_delete);
2096
2097 // Final cleanup
2098 unset($listingKey_in_MLS);
2099 gc_collect_cycles();
2100
2101
2102 return;
2103 }
2104
2105
2106
2107 /**
2108 * Preload all status meta for ALL mlsimport_item posts in ONE QUERY.
2109 * Returns: [mlsimport_item_id => ['mlsimport_item_standardstatus' => ..., 'mlsimport_item_standardstatusprotect' => ...], ...]
2110 */
2111 function mlsimport_preload_all_mls_item_status_meta() {
2112 global $wpdb;
2113
2114 $sql = "
2115 SELECT p.ID as post_id, pm.meta_key, pm.meta_value
2116 FROM {$wpdb->posts} p
2117 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
2118 WHERE p.post_type = 'mlsimport_item'
2119 AND pm.meta_key IN ('mlsimport_item_standardstatus', 'mlsimport_item_standardstatusprotect')
2120 ";
2121
2122 $rows = $wpdb->get_results($sql);
2123
2124 $meta = [];
2125 foreach ($rows as $row) {
2126 if (!isset($meta[$row->post_id])) {
2127 $meta[$row->post_id] = [
2128 'mlsimport_item_standardstatus' => null,
2129 'mlsimport_item_standardstatusprotect' => null
2130 ];
2131 }
2132 $meta[$row->post_id][$row->meta_key] = maybe_unserialize($row->meta_value);
2133 }
2134 return $meta;
2135 }
2136
2137
2138
2139 /**
2140 *
2141 *
2142 * Requestq Reconciliation log
2143 */
2144 public function mlsimport_saas_get_mls_reconciliation_data() {
2145
2146 $arguments = array();
2147 $answer = $this->theme_importer->globalApiRequestCurlSaas( 'reconciliation', $arguments, 'GET' );
2148 return $answer;
2149 }
2150
2151 /**
2152 *
2153 *
2154 * Reconciliation get local data
2155 */
2156 public function mlsimport_saas_get_all_meta_values($key) {
2157 global $wpdb;
2158 $result = $wpdb->get_results(
2159 $wpdb->prepare(
2160 "
2161 SELECT pm.meta_value, p.ID
2162 FROM {$wpdb->postmeta} pm
2163 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
2164 WHERE pm.meta_key = %s
2165 AND p.post_status = 'publish'
2166 ",
2167 $key
2168 ),
2169 ARRAY_A // Lighter than OBJECT, unless you need objects
2170 );
2171 return $result;
2172 }
2173
2174
2175
2176 /*
2177 * Do api Listing Requests
2178 *
2179 *
2180 *
2181 *
2182 * */
2183 public function mlsimport_make_listing_requests( $item_id, $last_date = '', $skip = '', $top = '', $is_hourly_sync = false ) {
2184 $options = get_option( $this->plugin_name . '_admin_options' );
2185 $mls_id = '';
2186 if ( isset( $options['mlsimport_mls_name'] ) ) {
2187 $mls_id = sanitize_text_field( trim( $options['mlsimport_mls_name'] ) );
2188 }
2189
2190 $arguments = $this->mlsimport_saas_make_listing_requests_arguments( $item_id, $last_date, $skip, $top, $is_hourly_sync );
2191
2192
2193 if (
2194 $mls_id > 5000 && $mls_id < 6000 &&
2195 ( ! isset( $arguments['property_type'] ) or
2196 ( isset( $arguments['property_type'] ) && '' === $arguments['property_type'] ) or
2197 ( isset( $arguments['property_type'][0] ) && '' === $arguments['property_type'][0] )
2198 )
2199 ) {
2200 return array(
2201 'success' => false,
2202 'type' => 'rapattoni',
2203 'message' => esc_html__( 'This MLS requires to have one item selected from "Property Action Category" dropdown', 'mlsimport' ),
2204 );
2205 }
2206
2207 $potential_leght = strlen( wp_json_encode( $arguments ) );
2208 if ( $potential_leght > 1750 ) {
2209 return array(
2210 'success' => false,
2211 'potential_leght' => $potential_leght,
2212 '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' ),
2213 );
2214 }
2215
2216 //print_r($arguments);
2217 //print '----------------------------'.PHP_EOL;
2218 $answer = $this->theme_importer->globalApiRequestCurlSaas( 'listings', $arguments, 'POST' );
2219 $answer['potential_leght'] = $potential_leght;
2220
2221 // Record the pre-filter MLS feed count for telemetry. Every import path
2222 // — manual, hourly cron, and onboarding — routes through this method, so
2223 // recording here is the single rule that keeps the metric complete.
2224 if ( isset( $answer['results'] ) ) {
2225 mlsimport_telemetry_set( 'last_feed_found', (int) $answer['results'] );
2226 }
2227
2228 return ( $answer );
2229 }
2230
2231
2232
2233
2234
2235
2236 /*
2237 * Create Api query arguments
2238 *
2239 *
2240 *
2241 *
2242 *
2243 * */
2244
2245 public function mlsimport_saas_make_listing_requests_arguments( $item_id, $last_date = '', $skip = '', $top = '', $is_hourly_sync = false ) {
2246
2247 $options = get_option( $this->plugin_name . '_admin_options' );
2248 if ( isset( $options['mlsimport_mls_name'] ) ) {
2249 $mls_id = intval( $options['mlsimport_mls_name'] );
2250 } else {
2251 return '';
2252 }
2253
2254 if ( isset( $options['mlsimport_theme_used'] ) ) {
2255 $theme_id = intval( $options['mlsimport_theme_used'] );
2256 } else {
2257 return '';
2258 }
2259
2260 $values = array();
2261 $values['mls_id'] = $mls_id;
2262 $values['theme_id'] = $theme_id;
2263 if ( $is_hourly_sync ) {
2264 $values['hourly_sync'] = 1;
2265 }
2266
2267 if ( '' !== $top ) {
2268 $values['top'] = $top;
2269 $values['skip'] = intval( $skip );
2270 }
2271
2272 // // add price
2273 $mlsimport_item_min_price = get_post_meta( $item_id, 'mlsimport_item_min_price', true );
2274 $mlsimport_item_max_price = get_post_meta( $item_id, 'mlsimport_item_max_price', true );
2275 if ( '' !== $mlsimport_item_min_price && '' !== $mlsimport_item_max_price ) {
2276 $values['list_price_min'] = floatval( $mlsimport_item_min_price );
2277 $values['list_price_max'] = floatval( $mlsimport_item_max_price );
2278 }
2279
2280 // add city
2281 $values = $this->mls_import_return_multiple_param_value( 'city', $item_id, 'city', $values );
2282
2283 // add county
2284 $values = $this->mls_import_return_multiple_param_value( 'countyorparish', $item_id, 'county_or_parish', $values );
2285
2286 // add MLSAreaMajor
2287 $values = $this->mls_import_saas_add_to_parms_input( 'MLSAreaMajor', $item_id, 'mls_area_major', $values );
2288
2289 // add SubdivisionName
2290 $values = $this->mls_import_saas_add_to_parms_input( 'SubdivisionName', $item_id, 'subdivision_name', $values );
2291
2292 // add postal code
2293 $values = $this->mls_import_saas_add_to_parms_input( 'PostalCode', $item_id, 'postal_code', $values );
2294
2295 // add status
2296
2297 if ( 111 !== $mls_id ) { // edmonton check
2298 $values = $this->mls_import_return_multiple_param_value( 'StandardStatus', $item_id, 'status', $values );
2299 }
2300
2301 // add property_subtype
2302 $values = $this->mls_import_return_multiple_param_value( 'PropertySubType', $item_id, 'property_subtype', $values );
2303
2304 // add property_type
2305 $values = $this->mls_import_return_multiple_param_value( 'PropertyType', $item_id, 'property_type', $values );
2306
2307 // rapattoni exception
2308 if ( $mls_id > 5000 &&
2309 ( isset($values['property_type']) && $values['property_type'] !='' ) ) {
2310
2311 $values = $this->mls_import_saas_add_to_parms_input( 'PropertyType', $item_id, 'property_type', $values );
2312 $temp = $values['property_type'];
2313 $temp = str_replace( ' ', '', $temp );
2314 $values['property_type'] = array();
2315 $values['property_type'][] = $temp;
2316 }
2317
2318 // add internet_entirelisting_displayyn
2319 $values = $this->mls_import_saas_add_to_parms_input( 'InternetEntireListingDisplayYN', $item_id, 'internet_entirelisting_displayyn', $values );
2320
2321 // add internet_address_displayyn
2322 $values = $this->mls_import_saas_add_to_parms_input( 'InternetAddressDisplayYN', $item_id, 'internet_address_displayyn', $values );
2323
2324 // add ListAgentKey
2325 $values = $this->mls_import_saas_add_to_parms_input( 'ListAgentKey', $item_id, 'list_agentkey', $values );
2326 // add ListAgentKey
2327 $values = $this->mls_import_saas_add_to_parms_input( 'ListAgentMlsId', $item_id, 'list_agentmlsid', $values );
2328 // add BuyerAgentMlsId
2329 $values = $this->mls_import_saas_add_to_parms_input( 'BuyerAgentMlsId', $item_id, 'buyer_agentmlsid', $values );
2330 // add ListOfficeKey
2331 $values = $this->mls_import_saas_add_to_parms_input( 'ListOfficeKey', $item_id, 'list_officekey', $values );
2332 // add ListOfficeMlsId
2333 $values = $this->mls_import_saas_add_to_parms_input( 'ListOfficeMlsId', $item_id, 'list_officemlsid', $values );
2334
2335 // add ListingId
2336 $values = $this->mls_import_saas_add_to_parms_input( 'ListingId', $item_id, 'listingid', $values );
2337
2338 //add Exclude_ListOfficeKey
2339 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListOfficeKey', $item_id, 'exclude_list_officekey', $values );
2340 // add Exclude_ListOfficeMlsId
2341 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListOfficeMlsId', $item_id, 'exclude_list_officemlsid', $values );
2342
2343
2344
2345 //add Exclude_ListAgentKey
2346 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListAgentKey', $item_id, 'exclude_list_agentkey', $values );
2347 // add Exclude_ListAgentMlsId
2348 $values = $this->mls_import_saas_add_to_parms_input( 'Exclude_ListAgentMlsId', $item_id, 'exclude_list_agentmlsid', $values );
2349 // add CustomParameters
2350 $values = $this->mls_import_saas_add_to_parms_input( 'CustomParameters', $item_id, 'custom_parameters', $values );
2351
2352
2353 // if we have realtorca
2354 if ($mls_id >= 7000 && $mls_id < 8000 && $last_date!=='') {
2355 $dateTime_realtorca = new DateTime($last_date, new DateTimeZone('UTC'));
2356 // Format with seconds and UTC timezone marker
2357 $last_date = $dateTime_realtorca->format('Y-m-d\TH:i:s.000\Z');
2358 }
2359
2360 if ( '' !== $last_date ) {
2361 $values['modification_time'] = $last_date;
2362 }
2363
2364 return( $values );
2365 }
2366
2367
2368
2369 /*
2370 *
2371 * add input items to parameters array
2372 *
2373 */
2374
2375 public function mls_import_saas_add_to_parms_input( $key, $post_id, $new_name, $all_values ) {
2376 $name = strtolower( 'mlsimport_item_' . $key );
2377 $value = get_post_meta( $post_id, $name, true );
2378 if ( '' !== $value ) {
2379 $all_values[ $new_name ] = $value;
2380 }
2381
2382 return $all_values;
2383 }
2384
2385
2386 /*
2387 *
2388 * add list items to parameters array
2389 *
2390 */
2391
2392 public function mls_import_return_multiple_param_value( $key, $post_id, $new_name, $all_values ) {
2393 $name_check = strtolower( 'mlsimport_item_' . $key . '_check' );
2394 $name = strtolower( 'mlsimport_item_' . $key );
2395
2396 $value = get_post_meta( $post_id, $name, true );
2397
2398 // add extra county - should be moved into function if pass tests
2399 if ( 'countyorparish' === $key ) {
2400 $extracounty_values = get_post_meta( $post_id, 'mlsimport_item_extracounty', true );
2401
2402 if ( '' !== $extracounty_values ) {
2403 $extracounty_array = explode( ',', $extracounty_values );
2404
2405 if ( ! is_array( $value ) ) {
2406 if ( '' === $value ) {
2407 $value = array();
2408 } else {
2409 $value = array( $value );
2410 }
2411 }
2412
2413 foreach ( $extracounty_array as $extra ) {
2414 $value[] = $extra;
2415 }
2416 }
2417 }
2418
2419 // add extra city - should be moved into function if pass tests
2420 if ( 'city' === $key ) {
2421 $extracity_values = get_post_meta( $post_id, 'mlsimport_item_extracity', true );
2422 if ( '' !== $extracity_values ) {
2423 $extracity_array = explode( ',', $extracity_values );
2424
2425 if ( ! is_array( $value ) ) {
2426 if ('' === $value ) {
2427 $value = array();
2428 } else {
2429 $value = array( $value );
2430 }
2431 }
2432
2433 foreach ( $extracity_array as $extra ) {
2434 $value[] = $extra;
2435 }
2436 }
2437 }
2438
2439 $value_check = get_post_meta( $post_id, $name_check, true );
2440
2441 if ( 0 === intval($value_check) && '' !== $value ) {
2442 $all_values[ $new_name ] = $value;
2443 }
2444
2445 // status exception
2446 if ( 'status' === $new_name ) {
2447 $all_values[ $new_name ] = $value;
2448 }
2449
2450 return $all_values;
2451 }
2452
2453
2454
2455 /*
2456 *
2457 * All Enums fiels to be used on MLS import Taaks
2458 *
2459 *
2460 *
2461 *
2462 *
2463 *
2464 * */
2465
2466 public function mlsimport_saas_return_mls_fields() {
2467
2468 $mlsimport_mls_metadata_mls_enums = get_option( 'mlsimport_mls_metadata_mls_enums', '' );
2469
2470 if ( '' === $mlsimport_mls_metadata_mls_enums ) {
2471 ?>
2472 <div class="mlsimport_warning long_warning">Please select the import fields(from MLS Import Settings) before starting a MLS import process.</div>
2473 <?php
2474 }
2475
2476 $metadata_api_call_full = json_decode( $mlsimport_mls_metadata_mls_enums, true );
2477
2478 if ( isset( $metadata_api_call_full['global_array'] ) ) {
2479 $metadata_api_call = $metadata_api_call_full['global_array'];
2480 }
2481
2482 $city_array = array();
2483 if ( isset( $metadata_api_call['PropertyEnums']['City'] ) && is_array( $metadata_api_call['PropertyEnums']['City'] ) ) {
2484 $city_array = array_keys( $metadata_api_call['PropertyEnums']['City'] );
2485 }
2486
2487 $county_array = array();
2488 if ( isset( $metadata_api_call['PropertyEnums']['CountyOrParish'] ) && is_array( $metadata_api_call['PropertyEnums']['CountyOrParish'] ) ) {
2489 $county_array = array_keys( $metadata_api_call['PropertyEnums']['CountyOrParish'] );
2490 }
2491
2492 $mlsstatus_array = array();
2493 if ( isset( $metadata_api_call['PropertyEnums']['MlsStatus'] ) && is_array( $metadata_api_call['PropertyEnums']['MlsStatus'] ) ) {
2494 $mlsstatus_array = array_keys( $metadata_api_call['PropertyEnums']['MlsStatus'] );
2495 }
2496
2497 $propertysubtype_array = array();
2498 if ( isset( $metadata_api_call['PropertyEnums']['PropertySubType'] ) && is_array( $metadata_api_call['PropertyEnums']['PropertySubType'] ) ) {
2499 $propertysubtype_array = array_keys( $metadata_api_call['PropertyEnums']['PropertySubType'] );
2500 }
2501
2502 $propertytype_array = array();
2503 if ( isset( $metadata_api_call['PropertyEnums']['PropertyType'] ) && is_array( $metadata_api_call['PropertyEnums']['PropertyType'] ) ) {
2504 $propertytype_array = array_keys( $metadata_api_call['PropertyEnums']['PropertyType'] );
2505 }
2506
2507
2508 $standardstatus_array = array();
2509 if ( isset( $metadata_api_call['PropertyEnums']['StandardStatus'] ) && is_array( $metadata_api_call['PropertyEnums']['StandardStatus'] ) ) {
2510 $standardstatus_array = array_keys( $metadata_api_call['PropertyEnums']['StandardStatus'] );
2511 }
2512
2513 // if we do not have standart status
2514 if ( empty( $standardstatus_array ) ) {
2515 $standardstatus_array = $mlsstatus_array;
2516 }
2517
2518
2519
2520
2521 $extracounty_values = '';
2522 $extracity_values = '';
2523
2524 $field_import = array(
2525 'City' => array(
2526 'label' => esc_html__( 'Select cities', 'mlsimport' ),
2527 'description' => esc_html__( 'Select the cities from where we will import data.', 'mlsimport' ),
2528 'type' => 'select',
2529 'multiple' => 'yes',
2530 'values' => $city_array,
2531 ),
2532
2533 'extraCity' => array(
2534 'label' => esc_html__( 'Add extra Cities', 'mlsimport' ),
2535 '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' ),
2536 'type' => 'input',
2537 'multiple' => 'no',
2538 'values' => $extracity_values,
2539 ),
2540
2541 'CountyOrParish' => array(
2542 'label' => esc_html__( 'Select Counties', 'mlsimport' ),
2543 'description' => esc_html__( 'Select the counties from where we will import data.', 'mlsimport' ),
2544 'type' => 'select',
2545 'multiple' => 'yes',
2546 'values' => $county_array,
2547 'show_extra_field' => true,
2548 ),
2549
2550 'extraCounty' => array(
2551 'label' => esc_html__( 'Add extra Counties', 'mlsimport' ),
2552 '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' ),
2553 'type' => 'input',
2554 'multiple' => 'no',
2555 'values' => $extracounty_values,
2556 ),
2557
2558 'MLSAreaMajor' => array(
2559 'label' => esc_html__( 'MLS Area Major', 'mlsimport' ),
2560 'description' => esc_html__( 'Filter listings by MLSAreaMajor.', 'mlsimport' ),
2561 'type' => 'input',
2562 'multiple' => 'no',
2563 ),
2564
2565 'SubdivisionName' => array(
2566 'label' => esc_html__( 'Subdivision Name', 'mlsimport' ),
2567 'description' => esc_html__( 'Filter listings by SubDivisionName.', 'mlsimport' ),
2568 'type' => 'input',
2569 'multiple' => 'no',
2570 ),
2571
2572 'PostalCode' => array(
2573 'label' => esc_html__( 'Select Postal Code', 'mlsimport' ),
2574 'description' => esc_html__( 'Enter one or more postal codes to import listings from, separated by commas (e.g. 12345, 23456).', 'mlsimport' ),
2575 'type' => 'input',
2576 'multiple' => 'no',
2577 ),
2578
2579 'PropertySubType' => array(
2580 'label' => esc_html__( 'Select Property Category', 'mlsimport' ),
2581 'description' => esc_html__( 'Property Category', 'mlsimport' ),
2582 'type' => 'select',
2583 'multiple' => 'yes',
2584 'values' => $propertysubtype_array,
2585 ),
2586 'PropertyType' => array(
2587 'label' => esc_html__( 'Select Property Action Category', 'mlsimport' ),
2588 'description' => esc_html__( 'Property Action Category', 'mlsimport' ),
2589 'type' => 'select',
2590 'multiple' => 'yes',
2591 'values' => $propertytype_array,
2592 ),
2593 'StandardStatus' => array(
2594 'label' => esc_html__( 'Select Status', 'mlsimport' ),
2595 'description' => __( 'The list is auto-populated with MLS available statuses. To select multiple statuses, use Ctrl (Windows) or Command (Mac).', 'mlsimport' ),
2596 'type' => 'select',
2597 'multiple' => 'yes',
2598 'values' => $standardstatus_array,
2599 ),
2600 'StandardStatusProtect' => array(
2601 'label' => esc_html__( 'Protected Statuses', 'mlsimport' ),
2602 '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' ),
2603 'type' => 'select',
2604 'multiple' => 'yes',
2605 'values' => $standardstatus_array,
2606 ),
2607
2608 'InternetEntireListingDisplayYN' => array(
2609 'label' => esc_html__( 'Internet Entire Listing Display ', 'mlsimport'),
2610 'description' => esc_html__( 'A yes/no field that states the seller has allowed the listing to be displayed on Internet sites.', 'mlsimport' ),
2611 'type' => 'select',
2612 'multiple' => 'no',
2613 'values' => array(
2614 'yes',
2615 'no',
2616 ),
2617 ),
2618 'InternetAddressDisplayYN' => array(
2619 'label' => esc_html__( 'Internet Address display', 'mlsimport' ),
2620 'description' => esc_html__( 'A yes/no field that states the seller has allowed the listing address to be displayed on Internet sites.', 'mlsimport' ),
2621 'type' => 'select',
2622 'multiple' => 'no',
2623 'values' => array(
2624 'yes',
2625 'no',
2626 ),
2627 ),
2628 'ListAgentKey' => array(
2629 'label' => esc_html__( 'ListAgentKey', 'mlsimport' ),
2630 'description' => esc_html__( 'Import listings from a specific Agent (contact your MLS for this information)', 'mlsimport' ),
2631 'type' => 'input',
2632 'multiple' => 'no',
2633 ),
2634 'ListAgentMlsId' => array(
2635 'label' => esc_html__( 'ListAgentMlsId', 'mlsimport' ),
2636 'description' => esc_html__( 'Import listings from a specific Agent (contact your MLS for this information)', 'mlsimport' ),
2637 'type' => 'input',
2638 'multiple' => 'no',
2639 ),
2640 'BuyerAgentMlsId' => array(
2641 'label' => esc_html__( 'BuyerAgentMlsId', 'mlsimport' ),
2642 'description' => esc_html__( 'Import listings from a specific Buyer Agent (contact your MLS for this information)', 'mlsimport' ),
2643 'type' => 'input',
2644 'multiple' => 'no',
2645 ),
2646 'ListOfficeKey' => array(
2647 'label' => esc_html__( 'ListOfficeKey', 'mlsimport' ),
2648 'description' => esc_html__( 'Import listings from a specific Office (contact your MLS for this information)', 'mlsimport'),
2649 'type' => 'input',
2650 'multiple' => 'no',
2651 ),
2652 'ListOfficeMlsId' => array(
2653 'label' => esc_html__( 'ListOfficeMlsId', 'mlsimport' ),
2654 'description' => esc_html__( 'Import listings from a specific Office (contact your MLS for this information)', 'mlsimport' ),
2655 'type' => 'input',
2656 'multiple' => 'no',
2657 ),
2658 'ListingId' => array(
2659 'label' => esc_html__( 'ListingId', 'mlsimport' ),
2660 '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'),
2661 'type' => 'input',
2662 'multiple' => 'no',
2663 ),
2664 'Exclude_ListOfficeMlsId' => array(
2665 'label' => esc_html__( 'Exclude listings with ListOfficeMlsId', 'mlsimport' ),
2666 'description' => esc_html__( 'Exclude listings that belong to one or more ListOfficeMlsId.', 'mlsimport' ),
2667 'type' => 'input',
2668 'multiple' => 'no',
2669 ),
2670 'Exclude_ListOfficeKey' => array(
2671 'label' => esc_html__( 'Exclude listings with ListOfficeKey', 'mlsimport' ),
2672 'description' => esc_html__( 'Exclude listings that belong to one or more ListOfficeKey', 'mlsimport'),
2673 'type' => 'input',
2674 'multiple' => 'no',
2675 ),
2676
2677
2678 'Exclude_ListAgentMlsId' => array(
2679 'label' => esc_html__( 'Exclude listings with ListAgentMlsId', 'mlsimport' ),
2680 'description' => esc_html__( 'Exclude listings that belong to one or more ListAgentMlsId.', 'mlsimport' ),
2681 'type' => 'input',
2682 'multiple' => 'no',
2683 ),
2684 'Exclude_ListAgentKey' => array(
2685 'label' => esc_html__( 'Exclude listings with ListAgentKey ', 'mlsimport' ),
2686 'description' => esc_html__( 'Exclude listings that belong to one or more ListAgentKey ', 'mlsimport'),
2687 'type' => 'input',
2688 'multiple' => 'no',
2689 ),
2690 'CustomParameters' => array(
2691 'label' => esc_html__( 'Custom parameters', 'mlsimport' ),
2692 'description' => esc_html__( 'Add raw query fragment parameters (for example: $filter=WaterfrontYN eq true). They will be forwarded to the RESO API request.', 'mlsimport' ),
2693 'type' => 'input',
2694 'multiple' => 'no',
2695 ),
2696
2697
2698 );
2699 return $field_import;
2700 }
2701
2702
2703
2704
2705
2706
2707
2708 /**
2709 *
2710 *
2711 * AYsnc Test
2712 */
2713 public function mlsimport_move_files_per_item() {
2714 check_ajax_referer( 'mlsimport_item_actions', 'security' );
2715 $post_id = 0;
2716 $how_many = 0;
2717 $max_number = 0;
2718 if(isset( $_POST['post_id'] )){
2719 $post_id = intval( $_POST['post_id'] );
2720 }
2721 if(isset( $_POST['how_many'] )){
2722 $how_many = intval( $_POST['how_many'] );
2723 }
2724 if(isset( $_POST['post_number'] )){
2725 $max_number = intval( $_POST['post_number'] );
2726 }
2727
2728
2729 $is_onboard=intval($_POST['is_onboard']);
2730
2731
2732 update_option( 'mlsimport_force_stop_' . $post_id, 'no', false );
2733
2734 $item_id_array = array(
2735 'item_id' => $post_id,
2736 'how_many' => $how_many,
2737 'max_number' => $max_number,
2738 'batch_counter' => 1,
2739 );
2740
2741
2742 update_post_meta( $post_id, 'mlsimport_attach_to_move_' . $post_id, '' );
2743
2744 $attachments_to_move = (array) $this->mlsimport_saas_generate_import_requests_per_item( $item_id_array );
2745
2746 // If an error was returned from the API, sanitize and send it back to the client and stop further processing.
2747 if ( isset( $attachments_to_move['success'] ) && false === $attachments_to_move['success'] ) {
2748 if ( isset( $attachments_to_move['message'] ) ) {
2749 $attachments_to_move['message'] = wp_strip_all_tags( $attachments_to_move['message'] );
2750 }
2751 wp_send_json( $attachments_to_move );
2752 wp_die();
2753 }
2754
2755 update_post_meta( $post_id, 'mlsimport_attach_to_move_' . $post_id, $attachments_to_move );
2756
2757 // net stat data
2758 update_post_meta( $post_id, 'mlsimport_progress_properties', 0 );
2759 update_post_meta( $post_id, 'mlsimport_progress_batches', 0 );
2760 update_post_meta( $post_id, 'mlsimport_progress_memory', 0 );
2761
2762
2763
2764 $attachments_to_send = array(
2765 'args' => array(
2766 'attachments_to_move' => $post_id,
2767 'item_id_array' => $item_id_array,
2768 'is_onboard' =>$is_onboard,
2769 ),
2770 );
2771
2772 mlsimport_saas_single_write_import_custom_logs( 'Preparing the import. Please hold on.' . PHP_EOL );
2773 mlsimport_debuglogs_per_plugin( 'Preparing the import. Please hold on.' . PHP_EOL );
2774
2775 update_post_meta( $post_id, 'mlsimport_spawn_status', 'started' );
2776
2777 // old
2778 as_enqueue_async_action( 'mlsimport_background_process_per_item', $attachments_to_send );
2779
2780 // Remove any pending async jobs for this item and enqueue a unique one
2781 //bad ideea
2782 // as_unschedule_all_actions( 'mlsimport_background_process_per_item', $attachments_to_send );
2783 //as_enqueue_async_action( 'mlsimport_background_process_per_item', $attachments_to_send, '', true );
2784
2785
2786 spawn_cron();
2787
2788 unset( $attachments_to_send );
2789
2790 // Return success response to the AJAX caller.
2791 wp_send_json( array( 'success' => true ) );
2792 }
2793
2794 /**
2795 * Process MLS Import attachments via background cron.
2796 * Memory-optimized with detailed memory usage logging.
2797 *
2798 * @param array $input_arg
2799 * @return void
2800 */
2801 public function mlsimport_background_process_per_item_cron_function( $input_arg ) {
2802 global $mlsimport;
2803
2804 $log = 'In cron processing function ->' . wp_json_encode( $input_arg['item_id_array'] ) . PHP_EOL;
2805 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2806 mlsimport_saas_single_write_import_custom_logs( '[Memory] Start: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB', 'cron' );
2807
2808 // Load attachments to move from post meta
2809 $attachments_to_move = get_post_meta(
2810 $input_arg['item_id_array']['item_id'],
2811 'mlsimport_cron_attach_to_move_' . $input_arg['item_id_array']['item_id'],
2812 true
2813 );
2814 $log = '[Memory] After loading attachments: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2815 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2816
2817 if (!empty($attachments_to_move) && is_array($attachments_to_move)) {
2818 foreach ($attachments_to_move as $key => $import_arguments) {
2819 // Optionally clear any cache for this batch
2820 if ( isset($GLOBALS['wp_object_cache']) ) {
2821 $GLOBALS['wp_object_cache']->delete('mlsimport_force_stop_' . $input_arg['item_id_array']['item_id'], 'options');
2822 }
2823
2824 $log = '[Memory] Before API batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2825 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2826
2827 // API call
2828 $api_call_array = $this->theme_importer->globalApiRequestCurlSaas('listings', $import_arguments, 'POST');
2829
2830 $log = '[Memory] After API batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2831 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2832
2833 // Parse/process response
2834 $mlsimport->admin->theme_importer->mlsimportSaasCronParseSearchArrayPerItem(
2835 $api_call_array, $input_arg['item_id_array'], $key
2836 );
2837
2838 // Free per-iteration memory
2839 unset($api_call_array, $import_arguments);
2840 gc_collect_cycles();
2841
2842 $log = '[Memory] After cleanup batch ' . $key . ': ' . (memory_get_usage(true) / 1024 / 1024) . ' MB' . PHP_EOL;
2843 mlsimport_saas_single_write_import_custom_logs( $log, 'cron' );
2844 }
2845 }
2846
2847 mlsimport_saas_single_write_import_custom_logs('[Memory] End: ' . (memory_get_usage(true) / 1024 / 1024) . ' MB', 'cron' );
2848 mlsimport_saas_single_write_import_custom_logs('CRON JOB Import Completed ' . PHP_EOL, 'cron');
2849 mlsimport_debuglogs_per_plugin('CRON JOB Import Completed ' . PHP_EOL);
2850 update_post_meta($input_arg['item_id_array']['item_id'], 'mlsimport_spawn_status', 'completed');
2851
2852 unset($attachments_to_move, $input_arg, $log);
2853 gc_collect_cycles();
2854 }
2855
2856
2857
2858
2859 /**
2860 *
2861 *
2862 * Generate import Requests per item
2863 */
2864 public function mlsimport_saas_generate_import_requests_per_item( $item_id_array, $last_date = '', $is_hourly_sync = false ) {
2865 $import_step = 25;
2866
2867 $prop_id = $item_id_array['item_id'];
2868 $max_found = $item_id_array['max_number'];
2869 $how_many = $item_id_array['how_many'];
2870 if ( 0=== intval($how_many) ) {
2871 $how_many = $max_found;
2872 }
2873 if ( $how_many > $max_found ) {
2874 $how_many = $max_found;
2875 }
2876
2877 $search_url_step = '';
2878 $urls_array = array();
2879
2880 $skip = 0;
2881 if ( $how_many > 10000 ) {
2882 $how_many = 10000;
2883 }
2884 update_post_meta($prop_id,'mlsimport_task_to_import', intval($how_many) );
2885
2886 if ( $how_many < $import_step ) {
2887 $import_step = $how_many;
2888 }
2889
2890 while ( $skip < $how_many ) {
2891
2892 // Determine how many items to request for this batch.
2893 $batch_step = min( $import_step, $how_many - $skip );
2894
2895 // Build the request arguments using the remaining count.
2896 $search_url_step = $this->mlsimport_saas_make_listing_requests_arguments( $prop_id, $last_date, $skip, $batch_step, $is_hourly_sync );
2897
2898 // If the API returned an error, propagate it immediately.
2899 if ( isset( $search_url_step['success'] ) && false === $search_url_step['success'] ) {
2900 return $search_url_step;
2901 }
2902
2903 $skip += $batch_step;
2904 $urls_array[] = $search_url_step;
2905
2906
2907 }
2908 return $urls_array;
2909 }
2910
2911
2912
2913
2914
2915
2916
2917 /**
2918 * Process Async function
2919 */
2920 public function mlsimport_background_process_per_item_function( $input_arg ) {
2921
2922 $mlsimportItemId = $input_arg['item_id_array']['item_id'];
2923 $log_prefix = 'In processing function - Item ID: ' . $mlsimportItemId . ' -> ';
2924 mlsimport_saas_single_write_import_custom_logs( $log_prefix . wp_json_encode( $input_arg['item_id_array'] ) . PHP_EOL );
2925
2926
2927 // Get from MLS Import the big argument array only once
2928 $attachments_to_move = get_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId, true );
2929
2930 // Retrieve all meta data in one go to reduce database queries
2931 $mlsimport_item_option_data = array(
2932 'mlsimport_item_standardstatus' => get_post_meta( $mlsimportItemId, 'mlsimport_item_standardstatus', true ),
2933 'mlsimport_item_standardstatusprotect' => get_post_meta( $mlsimportItemId, 'mlsimport_item_standardstatusprotect', true ),
2934 'mlsimport_item_property_user' => get_post_meta( $mlsimportItemId, 'mlsimport_item_property_user', true ),
2935 'mlsimport_item_agent' => get_post_meta( $mlsimportItemId, 'mlsimport_item_agent', true ),
2936 'mlsimport_item_property_status' => get_post_meta( $mlsimportItemId, 'mlsimport_item_property_status', true ),
2937 );
2938
2939 $total_batches = count( $attachments_to_move );
2940
2941 // removed because $this
2942 global $mlsimport;
2943
2944 $log = 'In processing function $attachments_to_move ->' . wp_json_encode( $attachments_to_move ) . PHP_EOL;
2945 mlsimport_saas_single_write_import_custom_logs( $log );
2946
2947
2948 foreach ( $attachments_to_move as $key => $import_arguments ) {
2949 // reconsider use
2950 // $GLOBALS['wp_object_cache']->delete('mlsimport_force_stop_' . $input_arg['item_id_array']['item_id'], 'options');
2951 $status = get_option( 'mlsimport_force_stop_' . $mlsimportItemId );
2952 if ( 'no' === $status ) {
2953 // Clear memory before processing each batch
2954 wp_cache_flush();
2955 gc_collect_cycles();
2956
2957 // wp_cache_flush();
2958 $mem_usage = memory_get_usage( true );
2959 $mem_usage_show = round( $mem_usage / 1048576, 2 );
2960
2961 update_post_meta( $mlsimportItemId, 'mlsimport_progress_batches', $key + 1 );
2962 update_post_meta( $mlsimportItemId, 'mlsimport_progress_memory', $mem_usage_show );
2963
2964
2965
2966 mlsimport_saas_single_write_import_custom_logs( $log );
2967 $log = 'Parsing import batch: ' . ( $key + 1 ) . ' of ' . $total_batches . '. Memory used: ' . $mem_usage_show . ' MB.' . PHP_EOL;
2968
2969
2970 // Combine logs and reduce function calls
2971 mlsimport_saas_single_write_import_custom_logs( $log );
2972 mlsimport_debuglogs_per_plugin( $log );
2973 print esc_html($log);
2974
2975 $api_call_array = $this->theme_importer->globalApiRequestCurlSaas( 'listings', $import_arguments, 'POST' );
2976
2977 $mlsimport->admin->theme_importer->mlsimportSaasParseSearchArrayPerItem( $api_call_array, $input_arg['item_id_array'], $key, $mlsimport_item_option_data );
2978
2979
2980
2981 // Explicitly unset large variables after each batch
2982 unset($api_call_array);
2983
2984 // Force garbage collection again after processing
2985 wp_cache_flush();
2986 gc_collect_cycles();
2987
2988
2989 // Add a small delay to allow memory to be freed
2990 if (($key + 1) < $total_batches) {
2991 usleep(100000); // 100ms pause between batches
2992 }
2993
2994 } else {
2995
2996 $final_mem_usage = memory_get_usage( true );
2997 $final_mem_usage_show = round( $final_mem_usage / 1048576, 2 );
2998
2999
3000 update_post_meta( $mlsimportItemId, 'mlsimport_spawn_status', 'completed' );
3001 delete_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId );
3002
3003 //new stats
3004 update_post_meta( $mlsimportItemId, 'mlsimport_progress_batches', $total_batches );
3005 update_post_meta( $mlsimportItemId, 'mlsimport_progress_memory', $final_mem_usage_show );
3006
3007
3008 mlsimport_saas_single_write_import_custom_logs( PHP_EOL . 'Parsing importing link FORCE STOP : ' );
3009 mlsimport_debuglogs_per_plugin( 'Parsing importing link FORCE STOP : ' );
3010 break; // Exit the loop if forced to stop
3011 }
3012 }
3013
3014 mlsimport_saas_single_write_import_custom_logs( 'Import Completed ' . PHP_EOL );
3015 mlsimport_debuglogs_per_plugin( 'Import Completed ' . PHP_EOL );
3016
3017 update_post_meta( $mlsimportItemId, 'mlsimport_spawn_status', 'completed' );
3018 delete_post_meta( $mlsimportItemId, 'mlsimport_attach_to_move_' . $mlsimportItemId );
3019
3020 // Final cleanup
3021 unset($attachments_to_move);
3022 unset($mlsimport_item_option_data);
3023 unset($input_arg);
3024 unset($log);
3025
3026 // One final garbage collection
3027 wp_cache_flush();
3028 gc_collect_cycles();
3029 }
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039 /**
3040 *
3041 *
3042 *
3043 * update log function
3044 */
3045 public function mlsimport_logger_per_item() {
3046 //check_ajax_referer( 'mlsimport_item_actions', 'security' );
3047 $post_id=0;
3048 if(isset($_POST['post_id'] )){
3049 $post_id = intval( $_POST['post_id'] );
3050 }
3051
3052 $status = get_post_meta( $post_id, 'mlsimport_spawn_status', true );
3053 $path = WP_PLUGIN_DIR . '/mlsimport/logs/status_logs.log';
3054 $logs = file_get_contents( $path );
3055
3056 //get neww status data
3057 $current = intval( get_post_meta( $post_id, 'mlsimport_progress_properties', true ) );
3058 $total = intval( get_post_meta( $post_id, 'mlsimport_progress_batches', true ) );
3059 $memory = get_post_meta( $post_id, 'mlsimport_progress_memory', true );
3060 $mlsimport_task_to_import = intval( get_post_meta($post_id, 'mlsimport_task_to_import',true));
3061
3062
3063
3064 $force_status = intval( get_post_meta( $post_id, 'mlsimport_force_stop', true ) );
3065 $force_status = get_option( 'mlsimport_force_stop_' . $post_id );
3066
3067 if ( 'no' !== $force_status ) {
3068 echo wp_json_encode(
3069 array(
3070 'is_done' => 'done',
3071 'status' => $status,
3072 'logs' => $logs,
3073 )
3074 );
3075 die();
3076 }
3077
3078 if ( '' === $status || 'completed' === $status ) {
3079 echo wp_json_encode(
3080 array(
3081 'is_done' => 'done',
3082 'status' => $status,
3083 'logs' => $logs,
3084 'mlsimport_progress_properties' => $current,
3085 'mlsimport_task_to_import' => $total,
3086 )
3087 );
3088 } else {
3089 // return from log
3090 echo wp_json_encode(
3091 array(
3092 'is_done' => 'wip',
3093 'status' => $status,
3094 'logs' => $logs,
3095 'mlsimport_progress_properties' => $current,
3096 'mlsimport_progress_batches' => $total,
3097 'memory' => $memory,
3098 'mlsimport_task_to_import'=>$mlsimport_task_to_import,
3099 'post_id'=>$post_id
3100
3101 )
3102 );
3103 }
3104 die();
3105 }
3106
3107
3108
3109
3110
3111
3112 /**
3113 *
3114 *
3115 *
3116 *
3117 * Force Stop Import
3118 */
3119 public function mlsimport_stop_import_per_item() {
3120
3121
3122 check_ajax_referer( 'mlsimport_item_actions', 'security' );
3123 $post_id=0;
3124 if(isset($_POST['post_id'] )){
3125 $post_id = intval( $_POST['post_id'] );
3126 }
3127 update_option( 'mlsimport_force_stop_' . $post_id, 'yes', false );
3128 // ensure caches are cleared so running processes see the update immediately
3129 if ( function_exists( 'wp_cache_delete' ) ) {
3130 wp_cache_delete( 'mlsimport_force_stop_' . $post_id, 'options' );
3131 }
3132 mlsimport_saas_single_write_import_custom_logs( 'Stopped for ' . $post_id . PHP_EOL );
3133 mlsimport_debuglogs_per_plugin( 'Stopped for ' . $post_id . PHP_EOL );
3134 wp_send_json_success();
3135 }
3136
3137
3138
3139 /*
3140 *
3141 * Get MLS Metadata
3142 *
3143 *
3144 *
3145 *
3146 **/
3147
3148 public function mlsimport_saas_get_metadata_function() {
3149 check_ajax_referer( 'mlsimport_saas_get_metadata', 'security' );
3150 $theme_Start = new ThemeImport();
3151
3152 $values = array();
3153 $options = get_option( $this->plugin_name . '_admin_options' );
3154 $url = 'clients?theme_id=' . intval( $options['mlsimport_theme_used'] );
3155
3156 $answer = $theme_Start::globalApiRequestSaas( $url, $values, 'GET' );
3157
3158 update_option( 'mlsimport_mls_metadata_populated', 'yes' );
3159
3160 update_option( 'mlsimport_mls_metadata_theme_schema', $answer['theme_schema'] );
3161 update_option( 'mlsimport_mls_metadata_mls_data', $answer['mls_data']['mls_meta_data'] );
3162 update_option( 'mlsimport_mls_metadata_mls_enums', $answer['mls_data']['mls_meta_enums'] );
3163 }
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176 /**
3177 *
3178 *
3179 * write debug logs
3180 */
3181 public function mlsimport_debuglog_cron( $message ) {
3182 if ( is_array( $message ) ) {
3183 $message = wp_json_encode( $message );
3184 }
3185 $message = date( 'F j, Y, g:i a' ) . ' -> ' . $message;
3186 global $wp_filesystem;
3187 if ( empty( $wp_filesystem ) ) {
3188 require_once ABSPATH . '/wp-admin/includes/file.php';
3189 WP_Filesystem();
3190 }
3191
3192 $path = WP_PLUGIN_DIR . '/mlsimport/logs/cron_logs.log';
3193
3194 file_put_contents( $path, $message, FILE_APPEND | LOCK_EX );
3195 }
3196
3197 }
3198