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

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