PluginProbe
Property Hive / 2.4.0
Property Hive v2.4.0
2.4.0 2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 All 262 releases
propertyhive / includes / admin / class-ph-admin-onboarding.php

class-ph-admin-onboarding.php in Property Hive 2.4.0, at includes/admin/class-ph-admin-onboarding.php

1,939 lines 75.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Property Hive first-run onboarding.
4 *
5 * @author PropertyHive
6 * @category Admin
7 * @package PropertyHive/Admin
8 * @version 1.0.0
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! class_exists( 'PH_Admin_Onboarding' ) ) :
16
17 /**
18 * PH_Admin_Onboarding class.
19 */
20 class PH_Admin_Onboarding {
21
22 const OPTION_NAME = 'propertyhive_onboarding';
23
24 const NONCE_ACTION = 'propertyhive-onboarding';
25
26 const RESTART_NONCE_ACTION = 'propertyhive-restart-onboarding';
27
28 /**
29 * Wizard step ids.
30 *
31 * @var array
32 */
33 private $steps = array( 'intro', 'departments', 'country', 'office', 'usage', 'license', 'demo-data', 'complete' );
34
35 /**
36 * Constructor.
37 */
38 public function __construct() {
39 add_action( 'admin_menu', array( $this, 'admin_dashboard_pages' ) );
40 add_action( 'admin_head', array( $this, 'hide_dashboard_page_menu_item' ) );
41 //add_action( 'admin_init', array( $this, 'maybe_restart_onboarding' ) );
42 add_action( 'load-dashboard_page_ph-onboarding', array( $this, 'remove_admin_toolbar_html_class' ) );
43 add_filter( 'propertyhive_screen_ids', array( $this, 'add_screen_id' ) );
44 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
45 add_action( 'propertyhive_settings_start', array( $this, 'output_restart_onboarding_notice' ) );
46
47 add_action( 'wp_ajax_propertyhive_onboarding_save_step', array( $this, 'ajax_save_step' ) );
48 add_action( 'wp_ajax_propertyhive_onboarding_skip', array( $this, 'ajax_skip' ) );
49 add_action( 'wp_ajax_propertyhive_onboarding_track', array( $this, 'ajax_track' ) );
50 add_action( 'wp_ajax_propertyhive_onboarding_activate_license', array( $this, 'ajax_activate_license' ) );
51 }
52
53 /**
54 * Register the hidden onboarding page.
55 */
56 public function admin_dashboard_pages() {
57 add_dashboard_page(
58 __( 'Property Hive Setup', 'propertyhive' ),
59 __( 'Property Hive Setup', 'propertyhive' ),
60 'manage_options',
61 'ph-onboarding',
62 array( $this, 'output' )
63 );
64 }
65
66 /**
67 * Hide the dashboard submenu item.
68 */
69 public function hide_dashboard_page_menu_item() {
70 remove_submenu_page( 'index.php', 'ph-onboarding' );
71 }
72
73 /**
74 * Prepare the HTML element classes for the full-screen onboarding page.
75 */
76 public function remove_admin_toolbar_html_class() {
77 ob_start( array( $this, 'filter_admin_html_class' ) );
78 }
79
80 /**
81 * Remove wp-toolbar from the admin HTML tag and add an onboarding class.
82 *
83 * @param string $buffer Page output.
84 * @return string
85 */
86 public function filter_admin_html_class( $buffer ) {
87 return preg_replace_callback(
88 '/<html\s+class=(["\'])([^"\']*)\1([^>]*)>/i',
89 array( $this, 'remove_wp_toolbar_from_html_tag' ),
90 $buffer,
91 1
92 );
93 }
94
95 /**
96 * Strip wp-toolbar and add the onboarding class while preserving attributes.
97 *
98 * @param array $matches Regex matches.
99 * @return string
100 */
101 private function remove_wp_toolbar_from_html_tag( $matches ) {
102 $quote = $matches[1];
103 $classes = preg_split( '/\s+/', trim( $matches[2] ) );
104 $classes = array_filter(
105 $classes,
106 function( $class ) {
107 return 'wp-toolbar' !== $class;
108 }
109 );
110
111 $classes[] = 'propertyhive-onboarding-html';
112 $classes = array_unique( $classes );
113
114 return '<html class=' . $quote . esc_attr( implode( ' ', $classes ) ) . $quote . $matches[3] . '>';
115 }
116
117 /**
118 * Add onboarding screen to Property Hive admin screens.
119 *
120 * @param array $screen_ids Screen ids.
121 * @return array
122 */
123 public function add_screen_id( $screen_ids ) {
124 $screen_ids[] = 'dashboard_page_ph-onboarding';
125 return $screen_ids;
126 }
127
128 /**
129 * Add body class on the full-screen onboarding page.
130 *
131 * @param string $classes Body classes.
132 * @return string
133 */
134 public function admin_body_class( $classes ) {
135 $screen = get_current_screen();
136 if ( $screen && 'dashboard_page_ph-onboarding' === $screen->id ) {
137 $classes .= ' propertyhive-onboarding-screen';
138 }
139 return $classes;
140 }
141
142 /**
143 * Get onboarding state.
144 *
145 * @return array
146 */
147 public static function get_state() {
148 $state = get_option( self::OPTION_NAME, array() );
149 if ( ! is_array( $state ) ) {
150 $state = array();
151 }
152
153 return wp_parse_args(
154 $state,
155 array(
156 'status' => 'not_started',
157 'last_step' => '',
158 'departments' => array(),
159 'country' => '',
160 'office' => array(),
161 'office_id' => 0,
162 'usage' => array(),
163 'usage_tracking' => true,
164 'has_license_key' => 'no',
165 'license_key_type' => 'pro',
166 'license_activated' => false,
167 'demo_data_imported' => false,
168 'import_feature_enabled' => false,
169 'completed_via' => '',
170 'events' => array(),
171 )
172 );
173 }
174
175 /**
176 * Save onboarding state.
177 *
178 * @param array $updates State updates.
179 * @return array
180 */
181 private static function update_state( $updates ) {
182 $state = self::get_state();
183 $state = array_merge( $state, $updates );
184
185 update_option( self::OPTION_NAME, $state, false );
186
187 return $state;
188 }
189
190 /**
191 * Add a local event, and optionally send it remotely.
192 *
193 * @param string $event Event name.
194 * @param array $data Event data.
195 */
196 public static function track_event( $event, $data = array() ) {
197 $event = sanitize_key( $event );
198 if ( '' === $event ) {
199 return;
200 }
201
202 $state = self::get_state();
203 $events = isset( $state['events'] ) && is_array( $state['events'] ) ? $state['events'] : array();
204
205 $events[] = array(
206 'event' => $event,
207 'data' => self::sanitize_event_data( $data ),
208 'created_at' => time(),
209 );
210
211 if ( count( $events ) > 50 ) {
212 $events = array_slice( $events, -50 );
213 }
214
215 self::update_state( array( 'events' => $events ) );
216
217 $endpoint = apply_filters( 'propertyhive_onboarding_tracking_endpoint', '' );
218 if ( 'yes' !== get_option( 'propertyhive_data_sharing', 'no' ) || empty( $endpoint ) ) {
219 return;
220 }
221
222 wp_remote_post(
223 esc_url_raw( $endpoint ),
224 array(
225 'timeout' => 5,
226 'body' => array(
227 'event' => $event,
228 'data' => wp_json_encode( self::sanitize_event_data( $data ) ),
229 'site_url' => home_url(),
230 'ph_version' => defined( 'PH_VERSION' ) ? PH_VERSION : '',
231 ),
232 )
233 );
234 }
235
236 /**
237 * Sanitize arbitrary event data.
238 *
239 * @param array $data Event data.
240 * @return array
241 */
242 private static function sanitize_event_data( $data ) {
243 if ( ! is_array( $data ) ) {
244 return array();
245 }
246
247 $sanitized = array();
248 foreach ( $data as $key => $value ) {
249 $key = sanitize_key( $key );
250 if ( is_array( $value ) ) {
251 $sanitized[ $key ] = array_map( 'sanitize_text_field', wp_unslash( $value ) );
252 } else {
253 $sanitized[ $key ] = sanitize_text_field( wp_unslash( $value ) );
254 }
255 }
256
257 return $sanitized;
258 }
259
260 /**
261 * Save a wizard step via AJAX.
262 */
263 public function ajax_save_step() {
264 $this->check_ajax_permissions();
265
266 $step = isset( $_POST['step'] ) ? sanitize_key( wp_unslash( $_POST['step'] ) ) : '';
267 if ( ! in_array( $step, $this->steps, true ) ) {
268 wp_send_json_error(
269 array( 'message' => __( 'Invalid setup step.', 'propertyhive' ) ),
270 400
271 );
272 }
273
274 switch ( $step ) {
275 case 'intro':
276 $state = $this->save_intro_step();
277 break;
278 case 'departments':
279 $state = $this->save_departments_step();
280 break;
281 case 'country':
282 $state = $this->save_country_step();
283 break;
284 case 'office':
285 $state = $this->save_office_step();
286 break;
287 case 'usage':
288 $state = $this->save_usage_step();
289 break;
290 case 'license':
291 $state = $this->save_license_step();
292 break;
293 case 'demo-data':
294 $state = $this->save_demo_data_step();
295 break;
296 case 'complete':
297 $state = $this->save_complete_step();
298 break;
299 }
300
301 self::track_event( 'step_saved', array( 'step' => $step ) );
302
303 wp_send_json_success( $state );
304 }
305
306 /**
307 * Skip the wizard.
308 */
309 public function ajax_skip() {
310 $this->check_ajax_permissions();
311
312 $step = isset( $_POST['step'] ) ? sanitize_key( wp_unslash( $_POST['step'] ) ) : '';
313
314 $updates = array(
315 'status' => 'skipped',
316 'last_step' => $step,
317 'skipped_at' => time(),
318 );
319
320 if ( isset( $_POST['usage_tracking'] ) ) {
321 $usage_tracking = 'yes' === sanitize_text_field( wp_unslash( $_POST['usage_tracking'] ) );
322
323 update_option( 'propertyhive_data_sharing', $usage_tracking ? 'yes' : 'no' );
324
325 $updates['usage_tracking'] = $usage_tracking;
326 }
327
328 $state = self::update_state( $updates );
329
330 self::track_event( 'skipped', array( 'step' => $step ) );
331
332 wp_send_json_success(
333 array(
334 'state' => $state,
335 'redirect_url' => admin_url( 'admin.php?page=ph-settings' ),
336 )
337 );
338 }
339
340 /**
341 * Track a view-only event.
342 */
343 public function ajax_track() {
344 $this->check_ajax_permissions();
345
346 $event = isset( $_POST['event'] ) ? sanitize_key( wp_unslash( $_POST['event'] ) ) : '';
347 $step = isset( $_POST['step'] ) ? sanitize_key( wp_unslash( $_POST['step'] ) ) : '';
348
349 if ( '' !== $event ) {
350 self::track_event( $event, array( 'step' => $step ) );
351 }
352
353 wp_send_json_success();
354 }
355
356 /**
357 * Activate a license key from the onboarding wizard.
358 */
359 public function ajax_activate_license() {
360 $this->check_ajax_permissions();
361
362 $license_key_type = isset( $_POST['license_key_type'] ) ? sanitize_key( wp_unslash( $_POST['license_key_type'] ) ) : '';
363 if ( ! in_array( $license_key_type, array( 'pro', 'old' ), true ) ) {
364 wp_send_json_error(
365 array( 'message' => __( 'Please choose a valid license type.', 'propertyhive' ) ),
366 400
367 );
368 }
369
370 $license_key = isset( $_POST['license_key'] ) ? sanitize_text_field( ph_clean( wp_unslash( $_POST['license_key'] ) ) ) : '';
371 if ( '' === $license_key ) {
372 wp_send_json_error(
373 array( 'message' => __( 'Please enter your license key.', 'propertyhive' ) ),
374 400
375 );
376 }
377
378 $snapshot = $this->snapshot_license_state();
379
380 update_option( 'missing_invalid_expired_license_key_notice_dismissed', '' );
381
382 if ( 'pro' === $license_key_type ) {
383 $result = $this->activate_pro_license_for_onboarding( $license_key );
384 } else {
385 $result = $this->activate_old_license_for_onboarding( $license_key );
386 }
387
388 if ( empty( $result['activated'] ) ) {
389 $this->restore_license_state( $snapshot );
390 }
391
392 self::update_state(
393 array(
394 'has_license_key' => 'yes',
395 'license_key_type' => $license_key_type,
396 'license_activated' => ! empty( $result['activated'] ),
397 'license_activated_at' => ! empty( $result['activated'] ) ? time() : 0,
398 )
399 );
400
401 self::track_event(
402 ! empty( $result['activated'] ) ? 'license_activated' : 'license_activation_failed',
403 array( 'license_type' => $license_key_type )
404 );
405
406 wp_send_json_success(
407 array(
408 'activated' => ! empty( $result['activated'] ),
409 'license_type' => $license_key_type,
410 'summary' => isset( $result['summary'] ) ? $result['summary'] : '',
411 'message' => isset( $result['message'] ) ? $result['message'] : '',
412 'renew_url' => isset( $result['renew_url'] ) ? $result['renew_url'] : '',
413 )
414 );
415 }
416
417 /**
418 * Activate a Pro license key and confirm it with a fresh status check.
419 *
420 * @param string $license_key License key.
421 * @return array
422 */
423 private function activate_pro_license_for_onboarding( $license_key ) {
424 $this->clear_pro_license_cache_surfaces();
425
426 update_option( 'propertyhive_license_type', 'pro' );
427 update_option( 'propertyhive_pro_license_key', $license_key );
428
429 $activation = PH()->license->activate_pro_license_key();
430 if ( ! isset( $activation['success'] ) || true !== $activation['success'] ) {
431 return array(
432 'activated' => false,
433 'message' => ! empty( $activation['error'] ) ? $activation['error'] : __( "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost.", 'propertyhive' ),
434 );
435 }
436
437 if ( ! PH()->license->is_valid_pro_license_key( true ) ) {
438 $license = PH()->license->get_current_pro_license();
439
440 return array(
441 'activated' => false,
442 'message' => ! empty( $license['error'] ) ? $license['error'] : __( "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost.", 'propertyhive' ),
443 );
444 }
445
446 return array(
447 'activated' => true,
448 'summary' => '',
449 );
450 }
451
452 /**
453 * Activate an old-style license key.
454 *
455 * @param string $license_key License key.
456 * @return array
457 */
458 private function activate_old_license_for_onboarding( $license_key ) {
459 update_option( 'propertyhive_license_type', 'old' );
460 update_option( 'propertyhive_license_key', $license_key );
461
462 PH()->license->ph_check_licenses( true );
463
464 $license = PH()->license->get_current_license();
465 $error = get_option( 'propertyhive_license_key_error', '' );
466
467 if ( '' !== $error ) {
468 return array(
469 'activated' => false,
470 'message' => sprintf(
471 /* translators: %s: license service error detail. */
472 __( "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost. %s", 'propertyhive' ),
473 $error
474 ),
475 );
476 }
477
478 if ( empty( $license ) || ! is_array( $license ) || ! isset( $license['active'] ) || '1' !== (string) $license['active'] ) {
479 return array(
480 'activated' => false,
481 'message' => isset( $license['active'] ) && '1' !== (string) $license['active'] ? __( 'Your Property Hive license key is inactive. You can keep going and add it later - nothing is lost.', 'propertyhive' ) : __( "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost.", 'propertyhive' ),
482 );
483 }
484
485 if ( empty( $license['expires_at'] ) || ( strtotime( $license['expires_at'] ) + DAY_IN_SECONDS ) <= time() ) {
486 $expires_at = ! empty( $license['expires_at'] ) ? date_i18n( 'jS F Y', strtotime( $license['expires_at'] ) ) : '';
487
488 return array(
489 'activated' => false,
490 'message' => '' !== $expires_at ? sprintf(
491 /* translators: %s: license expiry date. */
492 __( 'License expired on %s.', 'propertyhive' ),
493 $expires_at
494 ) : __( "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost.", 'propertyhive' ),
495 'renew_url' => 'https://wp-property-hive.com/license-key/',
496 );
497 }
498
499 return array(
500 'activated' => true,
501 'summary' => date_i18n( 'jS F Y', strtotime( $license['expires_at'] ) ),
502 );
503 }
504
505 /**
506 * Check whether the onboarding restart tool should be available.
507 *
508 * @return bool
509 */
510 public static function can_restart_onboarding() {
511 return in_array( self::get_environment_type(), array( 'local', 'development', 'staging' ), true );
512 }
513
514 /**
515 * Get the current WordPress environment type.
516 *
517 * @return string
518 */
519 private static function get_environment_type() {
520 if ( function_exists( 'wp_get_environment_type' ) ) {
521 return wp_get_environment_type();
522 }
523
524 return 'production';
525 }
526
527 /**
528 * Restart the onboarding wizard from a gated admin URL.
529 */
530 public function maybe_restart_onboarding() {
531 if ( empty( $_GET['propertyhive_restart_onboarding'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
532 return;
533 }
534
535 if ( ! current_user_can( 'manage_options' ) ) {
536 wp_die( esc_html__( 'You do not have permission to restart setup.', 'propertyhive' ) );
537 }
538
539 check_admin_referer( self::RESTART_NONCE_ACTION );
540
541 if ( ! self::can_restart_onboarding() ) {
542 wp_die( esc_html__( 'The setup wizard can only be restarted on local, development or staging sites.', 'propertyhive' ) );
543 }
544
545 delete_option( self::OPTION_NAME );
546 self::track_event( 'restarted', array( 'environment_type' => self::get_environment_type() ) );
547
548 wp_safe_redirect( admin_url( 'index.php?page=ph-onboarding' ) );
549 exit;
550 }
551
552 /**
553 * Output a development-only setup restart prompt on the settings page.
554 */
555 public function output_restart_onboarding_notice() {
556 if ( ! current_user_can( 'manage_options' ) || ! self::can_restart_onboarding() ) {
557 return;
558 }
559
560 ?>
561 <div class="notice notice-info inline">
562 <p>
563 <strong><?php esc_html_e( 'Development setup tools', 'propertyhive' ); ?></strong>
564 </p>
565 <p>
566 <?php
567 printf(
568 /* translators: %s: WordPress environment type. */
569 esc_html__( 'This site is marked as %s, so administrators can restart the Property Hive setup wizard for testing.', 'propertyhive' ),
570 '<code>' . esc_html( self::get_environment_type() ) . '</code>'
571 );
572 ?>
573 </p>
574 <p>
575 <a class="button" href="<?php echo esc_url( $this->get_restart_onboarding_url() ); ?>"><?php esc_html_e( 'Restart setup wizard', 'propertyhive' ); ?></a>
576 </p>
577 </div>
578 <?php
579 }
580
581 /**
582 * Get the onboarding restart URL.
583 *
584 * @return string
585 */
586 private function get_restart_onboarding_url() {
587 return wp_nonce_url(
588 add_query_arg( 'propertyhive_restart_onboarding', '1', admin_url( 'admin.php?page=ph-settings' ) ),
589 self::RESTART_NONCE_ACTION
590 );
591 }
592
593 /**
594 * Validate AJAX permissions.
595 */
596 private function check_ajax_permissions() {
597 check_ajax_referer( self::NONCE_ACTION, 'security' );
598
599 if ( ! current_user_can( 'manage_options' ) ) {
600 wp_send_json_error(
601 array( 'message' => __( 'You do not have permission to run setup.', 'propertyhive' ) ),
602 403
603 );
604 }
605 }
606
607 /**
608 * Return a validation error response.
609 *
610 * @param string $message Error summary.
611 * @param array $errors Field errors.
612 */
613 private function send_validation_error( $message, $errors = array() ) {
614 wp_send_json_error(
615 array(
616 'message' => $message,
617 'errors' => $errors,
618 ),
619 400
620 );
621 }
622
623 /**
624 * Get the first validation error message.
625 *
626 * @param array $errors Field errors.
627 * @param string $fallback Fallback message.
628 * @return string
629 */
630 private function get_first_validation_error_message( $errors, $fallback ) {
631 foreach ( $errors as $message ) {
632 if ( is_string( $message ) && '' !== $message ) {
633 return $message;
634 }
635 }
636
637 return $fallback;
638 }
639
640 /**
641 * Snapshot license options and transients so failed attempts can roll back.
642 *
643 * @return array
644 */
645 private function snapshot_license_state() {
646 $option_names = array(
647 'propertyhive_license_type',
648 'propertyhive_pro_license_key',
649 'propertyhive_license_key',
650 'propertyhive_license_key_details',
651 'propertyhive_license_key_error',
652 'propertyhive_pro_license_key_status',
653 'propertyhive_pro_license_key_last_checked',
654 'ph_pro_last_known_license_product_id_and_package',
655 'propertyhive_pro_instance_id',
656 );
657
658 $transient_names = array(
659 'ph_pro_license_status',
660 'ph_pro_license_product_id_and_package',
661 );
662
663 $snapshot = array(
664 'options' => array(),
665 'transients' => array(),
666 );
667
668 foreach ( $option_names as $option_name ) {
669 $snapshot['options'][ $option_name ] = $this->snapshot_option( $option_name );
670 }
671
672 foreach ( $transient_names as $transient_name ) {
673 $snapshot['transients'][ $transient_name ] = array(
674 'value' => $this->snapshot_option( '_transient_' . $transient_name ),
675 'timeout' => $this->snapshot_option( '_transient_timeout_' . $transient_name ),
676 );
677 }
678
679 return $snapshot;
680 }
681
682 /**
683 * Snapshot one option while retaining whether it existed.
684 *
685 * @param string $option_name Option name.
686 * @return array
687 */
688 private function snapshot_option( $option_name ) {
689 $missing = '__propertyhive_onboarding_missing_option__';
690 $value = get_option( $option_name, $missing );
691
692 return array(
693 'exists' => $missing !== $value,
694 'value' => $value,
695 );
696 }
697
698 /**
699 * Restore a previously snapshotted option.
700 *
701 * @param string $option_name Option name.
702 * @param array $snapshot Snapshot.
703 */
704 private function restore_option_snapshot( $option_name, $snapshot ) {
705 if ( empty( $snapshot['exists'] ) ) {
706 delete_option( $option_name );
707 return;
708 }
709
710 update_option( $option_name, $snapshot['value'] );
711 }
712
713 /**
714 * Restore license options and transients after a failed activation.
715 *
716 * @param array $snapshot Snapshot.
717 */
718 private function restore_license_state( $snapshot ) {
719 if ( ! empty( $snapshot['options'] ) && is_array( $snapshot['options'] ) ) {
720 foreach ( $snapshot['options'] as $option_name => $option_snapshot ) {
721 $this->restore_option_snapshot( $option_name, $option_snapshot );
722 }
723 }
724
725 if ( ! empty( $snapshot['transients'] ) && is_array( $snapshot['transients'] ) ) {
726 foreach ( $snapshot['transients'] as $transient_name => $transient_snapshot ) {
727 $this->restore_option_snapshot( '_transient_' . $transient_name, $transient_snapshot['value'] );
728 $this->restore_option_snapshot( '_transient_timeout_' . $transient_name, $transient_snapshot['timeout'] );
729 }
730 }
731 }
732
733 /**
734 * Clear all Pro license cache surfaces before validating a submitted key.
735 */
736 private function clear_pro_license_cache_surfaces() {
737 delete_transient( 'ph_pro_license_status' );
738 delete_transient( 'ph_pro_license_product_id_and_package' );
739 delete_option( 'propertyhive_pro_license_key_status' );
740 delete_option( 'propertyhive_pro_license_key_last_checked' );
741 delete_option( 'ph_pro_last_known_license_product_id_and_package' );
742 }
743
744 /**
745 * Save intro step.
746 *
747 * @return array
748 */
749 private function save_intro_step() {
750 $usage_tracking = isset( $_POST['usage_tracking'] ) && 'yes' === sanitize_text_field( wp_unslash( $_POST['usage_tracking'] ) );
751
752 update_option( 'propertyhive_data_sharing', $usage_tracking ? 'yes' : 'no' );
753
754 return self::update_state(
755 array(
756 'status' => 'in_progress',
757 'last_step' => 'intro',
758 'started_at' => $this->get_started_at(),
759 'usage_tracking' => $usage_tracking,
760 )
761 );
762 }
763
764 /**
765 * Save departments.
766 *
767 * @return array
768 */
769 private function save_departments_step() {
770 $departments = isset( $_POST['departments'] ) && is_array( $_POST['departments'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['departments'] ) ) : array();
771 $allowed = array( 'residential-sales', 'residential-lettings', 'commercial' );
772 $departments = array_values( array_intersect( $allowed, $departments ) );
773
774 if ( empty( $departments ) ) {
775 $this->send_validation_error(
776 __( 'Please choose at least one property sector.', 'propertyhive' ),
777 array(
778 'departments' => __( 'Please choose at least one property sector.', 'propertyhive' ),
779 )
780 );
781 }
782
783 update_option( 'propertyhive_active_departments_sales', in_array( 'residential-sales', $departments, true ) ? 'yes' : 'no' );
784 update_option( 'propertyhive_active_departments_lettings', in_array( 'residential-lettings', $departments, true ) ? 'yes' : 'no' );
785 update_option( 'propertyhive_active_departments_commercial', in_array( 'commercial', $departments, true ) ? 'yes' : 'no' );
786 update_option( 'propertyhive_primary_department', reset( $departments ) );
787
788 return self::update_state(
789 array(
790 'status' => 'in_progress',
791 'last_step' => 'departments',
792 'departments' => $departments,
793 'started_at' => $this->get_started_at(),
794 )
795 );
796 }
797
798 /**
799 * Save country.
800 *
801 * @return array
802 */
803 private function save_country_step() {
804 $country = isset( $_POST['country'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_POST['country'] ) ) ) : '';
805
806 $countries = $this->get_countries();
807 if ( ! isset( $countries[ $country ] ) ) {
808 $this->send_validation_error(
809 __( 'Please choose a valid country.', 'propertyhive' ),
810 array(
811 'country' => __( 'Please choose a valid country.', 'propertyhive' ),
812 )
813 );
814 }
815
816 update_option( 'propertyhive_default_country', $country );
817 update_option( 'propertyhive_countries', array( $country ) );
818
819 if ( ! empty( $countries[ $country ]['currency_code'] ) ) {
820 update_option( 'propertyhive_search_form_currency', sanitize_text_field( $countries[ $country ]['currency_code'] ) );
821 }
822
823 return self::update_state(
824 array(
825 'status' => 'in_progress',
826 'last_step' => 'country',
827 'country' => $country,
828 )
829 );
830 }
831
832 /**
833 * Save primary office details.
834 *
835 * @return array
836 */
837 private function save_office_step() {
838 $office = $this->sanitize_office_payload();
839 $errors = $this->validate_office_payload( $office );
840
841 if ( ! empty( $errors ) ) {
842 $this->send_validation_error(
843 $this->get_first_validation_error_message( $errors, __( 'Please check your office details.', 'propertyhive' ) ),
844 $errors
845 );
846 }
847
848 $office['email_address'] = sanitize_email( $office['email_address'] );
849
850 $office_id = $this->get_primary_office_id();
851 if ( ! $office_id ) {
852 $office_id = wp_insert_post(
853 array(
854 'post_title' => '' !== $office['name'] ? $office['name'] : __( 'My Office', 'propertyhive' ),
855 'post_content' => '',
856 'post_status' => 'publish',
857 'post_type' => 'office',
858 ),
859 true
860 );
861
862 if ( is_wp_error( $office_id ) || ! $office_id ) {
863 wp_send_json_error(
864 array( 'message' => __( 'Office details could not be saved.', 'propertyhive' ) ),
865 500
866 );
867 }
868 }
869
870 update_post_meta( $office_id, 'primary', '1' );
871
872 if ( '' !== $office['name'] && get_the_title( $office_id ) !== $office['name'] ) {
873 wp_update_post(
874 array(
875 'ID' => $office_id,
876 'post_title' => $office['name'],
877 )
878 );
879 }
880
881 update_post_meta( $office_id, '_office_address_1', $office['address_1'] );
882 update_post_meta( $office_id, '_office_address_2', $office['address_2'] );
883 update_post_meta( $office_id, '_office_address_3', $office['address_3'] );
884 update_post_meta( $office_id, '_office_address_4', $office['address_4'] );
885 update_post_meta( $office_id, '_office_address_postcode', $office['postcode'] );
886
887 foreach ( $this->get_office_contact_departments() as $department ) {
888 $department_key = str_replace( 'residential-', '', $department );
889 update_post_meta( $office_id, '_office_telephone_number_' . $department_key, $office['telephone_number'] );
890 update_post_meta( $office_id, '_office_email_address_' . $department_key, $office['email_address'] );
891 }
892
893 return self::update_state(
894 array(
895 'status' => 'in_progress',
896 'last_step' => 'office',
897 'office' => $office,
898 'office_id' => absint( $office_id ),
899 )
900 );
901 }
902
903 /**
904 * Save intended usage.
905 *
906 * @return array
907 */
908 private function save_usage_step() {
909 $usage = isset( $_POST['usage'] ) && is_array( $_POST['usage'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['usage'] ) ) : array();
910 $usage = array_values( array_intersect( array( 'import_properties', 'crm', 'portal_uploads', 'not_sure' ), $usage ) );
911
912 if ( empty( $usage ) ) {
913 $this->send_validation_error(
914 __( 'Please choose how you will use Property Hive.', 'propertyhive' ),
915 array(
916 'usage' => __( 'Please choose how you will use Property Hive.', 'propertyhive' ),
917 )
918 );
919 }
920
921 $crm_enabled = in_array( 'crm', $usage, true );
922 $disabled = $crm_enabled ? 'no' : 'yes';
923
924 update_option( 'propertyhive_module_disabled_contacts', $disabled );
925 update_option( 'propertyhive_module_disabled_appraisals', $disabled );
926 update_option( 'propertyhive_module_disabled_viewings', $disabled );
927 update_option( 'propertyhive_module_disabled_offers_sales', $disabled );
928 update_option( 'propertyhive_module_disabled_tenancies', $disabled );
929
930 return self::update_state(
931 array(
932 'status' => 'in_progress',
933 'last_step' => 'usage',
934 'usage' => $usage,
935 )
936 );
937 }
938
939 /**
940 * Save license step choices.
941 *
942 * @return array
943 */
944 private function save_license_step() {
945 $has_license_key = isset( $_POST['has_license_key'] ) ? sanitize_key( wp_unslash( $_POST['has_license_key'] ) ) : 'no';
946 $license_key_type = isset( $_POST['license_key_type'] ) ? sanitize_key( wp_unslash( $_POST['license_key_type'] ) ) : 'pro';
947
948 if ( ! in_array( $has_license_key, array( 'yes', 'no' ), true ) ) {
949 $has_license_key = 'no';
950 }
951
952 if ( ! in_array( $license_key_type, array( 'pro', 'old' ), true ) ) {
953 $license_key_type = 'pro';
954 }
955
956 return self::update_state(
957 array(
958 'status' => 'in_progress',
959 'last_step' => 'license',
960 'has_license_key' => $has_license_key,
961 'license_key_type' => $license_key_type,
962 )
963 );
964 }
965
966 /**
967 * Save demo-data result.
968 *
969 * @return array
970 */
971 private function save_demo_data_step() {
972 $demo_choice = isset( $_POST['demo_data_choice'] ) ? sanitize_key( wp_unslash( $_POST['demo_data_choice'] ) ) : '';
973 $imported = isset( $_POST['demo_data_imported'] ) && 'yes' === sanitize_text_field( wp_unslash( $_POST['demo_data_imported'] ) );
974
975 if ( ! in_array( $demo_choice, array( 'yes', 'no' ), true ) ) {
976 $this->send_validation_error(
977 __( 'Please choose whether to import demo data.', 'propertyhive' ),
978 array(
979 'demo_data_choice' => __( 'Please choose whether to import demo data.', 'propertyhive' ),
980 )
981 );
982 }
983
984 if ( 'yes' === $demo_choice && ! $imported ) {
985 $this->send_validation_error(
986 __( 'Demo data could not be imported. Please try again or choose No.', 'propertyhive' ),
987 array(
988 'demo_data_choice' => __( 'Demo data could not be imported. Please try again or choose No.', 'propertyhive' ),
989 )
990 );
991 }
992
993 self::track_event( $imported ? 'demo_data_completed' : 'demo_data_skipped' );
994
995 return self::update_state(
996 array(
997 'status' => 'in_progress',
998 'last_step' => 'demo-data',
999 'demo_data_imported' => $imported,
1000 )
1001 );
1002 }
1003
1004 /**
1005 * Complete the wizard.
1006 *
1007 * @return array
1008 */
1009 private function save_complete_step() {
1010 $import_feature = $this->get_import_feature_for_onboarding();
1011 $import_feature_active = $this->is_import_feature_active( ! empty( $import_feature['plugin_file'] ) ? $import_feature['plugin_file'] : '' );
1012 $completed_via = isset( $_POST['completed_via'] ) ? sanitize_key( wp_unslash( $_POST['completed_via'] ) ) : '';
1013
1014 if ( ! in_array( $completed_via, array( 'import', 'properties', 'site', 'settings' ), true ) ) {
1015 $completed_via = '';
1016 }
1017
1018 $state = self::update_state(
1019 array(
1020 'status' => 'completed',
1021 'last_step' => 'complete',
1022 'completed_at' => time(),
1023 'import_feature_enabled' => $import_feature_active,
1024 'completed_via' => $completed_via,
1025 )
1026 );
1027
1028 self::track_event( 'completed', array( 'completed_via' => $completed_via ) );
1029
1030 return $state;
1031 }
1032
1033 /**
1034 * Get existing start time or current time.
1035 *
1036 * @return int
1037 */
1038 private function get_started_at() {
1039 $state = self::get_state();
1040 if ( ! empty( $state['started_at'] ) ) {
1041 return absint( $state['started_at'] );
1042 }
1043
1044 self::track_event( 'started' );
1045
1046 return time();
1047 }
1048
1049 /**
1050 * Get license state for rendering without remote checks.
1051 *
1052 * @return array
1053 */
1054 private function get_license_render_state() {
1055 $pro_status = get_transient( 'ph_pro_license_status' );
1056 if ( false === $pro_status ) {
1057 $pro_status = get_option( 'propertyhive_pro_license_key_status', '' );
1058 }
1059
1060 if ( is_array( $pro_status ) && isset( $pro_status['success'] ) && true === $pro_status['success'] ) {
1061 return array(
1062 'type' => 'pro',
1063 'active' => true,
1064 'message' => __( 'You already have an active Property Hive subscription on this site.', 'propertyhive' ),
1065 );
1066 }
1067
1068 // TO DO: TAKE INTO ACCOUNT LICENSE TYPE SELECTED
1069 // SCENARIO - HAVING PRO TICKED AND NO LICENSE KEY BUT AN ACTIVE OLD KEY
1070 $license = PH()->license->get_current_license();
1071 if (
1072 is_array( $license ) &&
1073 isset( $license['active'] ) &&
1074 '1' === (string) $license['active'] &&
1075 ! empty( $license['expires_at'] ) &&
1076 ( strtotime( $license['expires_at'] ) + DAY_IN_SECONDS ) > time()
1077 ) {
1078 return array(
1079 'type' => 'old',
1080 'active' => true,
1081 'message' => __( 'Your license is active - add-on updates are enabled.', 'propertyhive' ),
1082 );
1083 }
1084
1085 return array(
1086 'type' => '',
1087 'active' => false,
1088 'message' => '',
1089 );
1090 }
1091
1092 /**
1093 * Resolve the Property Import feature from the cached add-ons feed.
1094 *
1095 * @return array
1096 */
1097 private function get_import_feature_for_onboarding() {
1098 $features = get_transient( 'propertyhive_features' );
1099 if ( false === $features || ! is_array( $features ) ) {
1100 return array();
1101 }
1102
1103 foreach ( $features as $feature ) {
1104 if ( empty( $feature['wordpress_plugin_file'] ) || ! is_string( $feature['wordpress_plugin_file'] ) ) {
1105 continue;
1106 }
1107
1108 $plugin_file = $feature['wordpress_plugin_file'];
1109 $plugin_parts = explode( '/', $plugin_file );
1110 $slug = $plugin_parts[0];
1111 $haystack = strtolower( $plugin_file . ' ' . $slug );
1112
1113 if (
1114 false === strpos( $haystack, 'property-import' ) &&
1115 false === strpos( $haystack, 'property_import' ) &&
1116 false === strpos( $haystack, 'wp-property-hive-property-import' )
1117 ) {
1118 continue;
1119 }
1120
1121 return array(
1122 'slug' => $slug,
1123 'plugin_file' => $plugin_file,
1124 'active' => $this->is_import_feature_active( $plugin_file ),
1125 );
1126 }
1127
1128 return array();
1129 }
1130
1131 /**
1132 * Determine whether the Property Import feature is active.
1133 *
1134 * @param string $plugin_file Optional plugin file.
1135 * @return bool
1136 */
1137 private function is_import_feature_active( $plugin_file = '' ) {
1138 if ( ! function_exists( 'is_plugin_active' ) ) {
1139 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1140 }
1141
1142 if ( '' !== $plugin_file && is_plugin_active( $plugin_file ) ) {
1143 return true;
1144 }
1145
1146 return class_exists( 'PH_Property_Import' );
1147 }
1148
1149 /**
1150 * Output onboarding page.
1151 */
1152 public function output() {
1153 if ( ! current_user_can( 'manage_options' ) ) {
1154 wp_die( esc_html__( 'You do not have permission to run setup.', 'propertyhive' ) );
1155 }
1156
1157 $state = self::get_state();
1158 $countries = $this->get_countries();
1159 $default_country = $this->get_default_country();
1160 $office = $this->get_office_defaults( $state );
1161 $demo_active = class_exists( 'PH_Demo_Data' );
1162 $video_url = apply_filters( 'propertyhive_onboarding_video_url', '' );
1163 $departments = ! empty( $state['departments'] ) && is_array( $state['departments'] ) ? $state['departments'] : array( 'residential-sales', 'residential-lettings' );
1164 $usage = ! empty( $state['usage'] ) && is_array( $state['usage'] ) ? $state['usage'] : array();
1165 $usage_tracking = 'yes' === get_option( 'propertyhive_data_sharing', 'yes' );
1166 $has_license_key = ! empty( $state['has_license_key'] ) && 'yes' === $state['has_license_key'] ? 'yes' : 'no';
1167 $license_key_type = ! empty( $state['license_key_type'] ) && in_array( $state['license_key_type'], array( 'pro', 'old' ), true ) ? $state['license_key_type'] : 'pro';
1168 $license_render_state = $this->get_license_render_state();
1169 $license_activated = ! empty( $state['license_activated'] ) || ! empty( $license_render_state['active'] );
1170 $import_feature = $this->get_import_feature_for_onboarding();
1171 $import_feature_active = ! empty( $import_feature['active'] ) || $this->is_import_feature_active();
1172 $demo_choice = ! empty( $state['demo_data_imported'] ) ? 'yes' : 'no';
1173 $address_lookup_enabled = function_exists( 'ph_is_development_environment' ) && ph_is_development_environment() && '' !== trim( (string) get_option( 'propertyhive_getaddress_api_key', '' ) );
1174 $search_results_page_id = function_exists( 'ph_get_page_id' ) ? absint( ph_get_page_id( 'search_results' ) ) : 0;
1175 $site_preview_url = home_url( '/' );
1176 if ( $search_results_page_id && get_post( $search_results_page_id ) ) {
1177 $search_results_permalink = get_permalink( $search_results_page_id );
1178 if ( $search_results_permalink ) {
1179 $site_preview_url = $search_results_permalink;
1180 }
1181 }
1182
1183 $current_step = $this->get_current_step( $state );
1184
1185 self::track_event( 'step_viewed', array( 'step' => $current_step ) );
1186 $this->localize_script( $demo_active, $import_feature, $import_feature_active, $license_activated );
1187 ?>
1188 <div class="ph-onboarding" data-current-step="<?php echo esc_attr( $current_step ); ?>" data-demo-imported="<?php echo ! empty( $state['demo_data_imported'] ) ? 'yes' : 'no'; ?>">
1189 <div class="ph-onboarding__chrome">
1190 <div class="ph-onboarding__brand" aria-label="<?php esc_attr_e( 'Property Hive', 'propertyhive' ); ?>">
1191 <img src="<?php echo esc_url( PH()->plugin_url() . '/assets/images/admin/propertyhive-logo-onboarding.png' ); ?>" alt="<?php esc_attr_e( 'Property Hive', 'propertyhive' ); ?>">
1192 </div>
1193 <button type="button" class="ph-onboarding__skip" data-ph-onboarding-skip><?php esc_html_e( 'Skip setup', 'propertyhive' ); ?></button>
1194 </div>
1195
1196 <div class="ph-onboarding__shell">
1197 <main class="ph-onboarding__content">
1198 <div class="ph-onboarding__progress" aria-hidden="true">
1199 <span data-progress-bar></span>
1200 </div>
1201
1202 <section class="ph-onboarding__panel ph-onboarding__panel--intro is-active" data-step="intro">
1203 <header class="ph-onboarding__intro">
1204 <h1><?php esc_html_e( 'Let\'s get Property Hive set up for you. Answer a few quick questions and we\'ll take care of the basics.', 'propertyhive' ); ?></h1>
1205 </header>
1206 <div class="ph-onboarding__intro-visual">
1207 <img src="<?php echo esc_url( PH()->plugin_url() . '/assets/images/admin/onboarding-intro-search.png' ); ?>" alt="">
1208 </div>
1209 </section>
1210
1211 <section class="ph-onboarding__panel" data-step="departments">
1212 <h2><?php esc_html_e( 'Which property sectors do you work in?', 'propertyhive' ); ?></h2>
1213 <p><?php esc_html_e( 'Choose the ones you want to use in Property Hive.', 'propertyhive' ); ?></p>
1214 <div class="ph-onboarding__cards" data-input-group="departments" data-validation-field="departments">
1215 <?php $this->output_choice_card( 'departments', 'residential-sales', __( 'Residential sales', 'propertyhive' ), __( 'Market and manage properties for sale.', 'propertyhive' ), in_array( 'residential-sales', $departments, true ) ); ?>
1216 <?php $this->output_choice_card( 'departments', 'residential-lettings', __( 'Residential lettings', 'propertyhive' ), __( 'Manage lettings, tenancies and landlords.', 'propertyhive' ), in_array( 'residential-lettings', $departments, true ) ); ?>
1217 <?php $this->output_choice_card( 'departments', 'commercial', __( 'Commercial', 'propertyhive' ), __( 'Manage commercial properties.', 'propertyhive' ), in_array( 'commercial', $departments, true ) ); ?>
1218 </div>
1219 <?php $this->output_field_error( 'departments' ); ?>
1220 <?php $this->output_settings_note( __( 'General', 'propertyhive' ) ); ?>
1221 </section>
1222
1223 <section class="ph-onboarding__panel" data-step="country">
1224 <h2><?php esc_html_e( 'Where does your agency operate?', 'propertyhive' ); ?></h2>
1225 <p><?php esc_html_e( 'Choose your country and we\'ll set the right currency and regional settings for Property Hive.', 'propertyhive' ); ?></p>
1226 <label class="ph-onboarding__field" data-validation-field="country">
1227 <span><?php esc_html_e( 'Country', 'propertyhive' ); ?></span>
1228 <select name="country" data-country-select required aria-describedby="ph-onboarding-error-country">
1229 <?php foreach ( $countries as $code => $country ) : ?>
1230 <option value="<?php echo esc_attr( $code ); ?>" <?php selected( $default_country, $code ); ?>><?php echo esc_html( $country['name'] ); ?></option>
1231 <?php endforeach; ?>
1232 </select>
1233 <?php $this->output_field_error( 'country' ); ?>
1234 </label>
1235 <?php $this->output_settings_note( __( 'General > International', 'propertyhive' ) ); ?>
1236 </section>
1237
1238 <section class="ph-onboarding__panel" data-step="office">
1239 <h2><?php esc_html_e( 'What are your office details?', 'propertyhive' ); ?></h2>
1240 <p><?php esc_html_e( 'Tell us about your main office and we\'ll get it set up in Property Hive.', 'propertyhive' ); ?></p>
1241 <?php if ( $address_lookup_enabled ) : ?>
1242 <div class="ph-onboarding__address-lookup" data-address-lookup>
1243 <label class="ph-onboarding__field ph-onboarding__field--wide">
1244 <span><?php esc_html_e( 'Find address', 'propertyhive' ); ?></span>
1245 <input type="search" data-address-lookup-input autocomplete="off" placeholder="<?php esc_attr_e( 'Start typing an address or postcode', 'propertyhive' ); ?>">
1246 </label>
1247 <div class="ph-onboarding__address-results" data-address-lookup-results hidden></div>
1248 <div class="ph-onboarding__address-status" data-address-lookup-status role="status" aria-live="polite"></div>
1249 </div>
1250 <?php endif; ?>
1251 <div class="ph-onboarding__form-grid ph-onboarding__form-grid--office">
1252 <label class="ph-onboarding__field" data-validation-field="office_name">
1253 <span><?php esc_html_e( 'Office name', 'propertyhive' ); ?></span>
1254 <input type="text" name="office_name" value="<?php echo esc_attr( $office['name'] ); ?>" data-office-field="office_name" maxlength="120" aria-describedby="ph-onboarding-error-office_name">
1255 <?php $this->output_field_error( 'office_name' ); ?>
1256 </label>
1257 <label class="ph-onboarding__field" data-validation-field="office_address_1">
1258 <span><?php esc_html_e( 'Address Line 1', 'propertyhive' ); ?></span>
1259 <input type="text" name="office_address_1" value="<?php echo esc_attr( $office['address_1'] ); ?>" data-office-field="office_address_1" maxlength="120" aria-describedby="ph-onboarding-error-office_address_1">
1260 <?php $this->output_field_error( 'office_address_1' ); ?>
1261 </label>
1262 <label class="ph-onboarding__field" data-validation-field="office_address_2">
1263 <span><?php esc_html_e( 'Address Line 2', 'propertyhive' ); ?></span>
1264 <input type="text" name="office_address_2" value="<?php echo esc_attr( $office['address_2'] ); ?>" data-office-field="office_address_2" maxlength="120" aria-describedby="ph-onboarding-error-office_address_2">
1265 <?php $this->output_field_error( 'office_address_2' ); ?>
1266 </label>
1267 <label class="ph-onboarding__field" data-validation-field="office_address_3">
1268 <span><?php esc_html_e( 'Address Line 3', 'propertyhive' ); ?></span>
1269 <input type="text" name="office_address_3" value="<?php echo esc_attr( $office['address_3'] ); ?>" data-office-field="office_address_3" maxlength="120" aria-describedby="ph-onboarding-error-office_address_3">
1270 <?php $this->output_field_error( 'office_address_3' ); ?>
1271 </label>
1272 <label class="ph-onboarding__field" data-validation-field="office_address_4">
1273 <span><?php esc_html_e( 'Address Line 4', 'propertyhive' ); ?></span>
1274 <input type="text" name="office_address_4" value="<?php echo esc_attr( $office['address_4'] ); ?>" data-office-field="office_address_4" maxlength="120" aria-describedby="ph-onboarding-error-office_address_4">
1275 <?php $this->output_field_error( 'office_address_4' ); ?>
1276 </label>
1277 <label class="ph-onboarding__field" data-validation-field="office_postcode">
1278 <span><?php esc_html_e( 'Postcode', 'propertyhive' ); ?></span>
1279 <input type="text" name="office_postcode" value="<?php echo esc_attr( $office['postcode'] ); ?>" data-office-field="office_postcode" maxlength="20" aria-describedby="ph-onboarding-error-office_postcode">
1280 <?php $this->output_field_error( 'office_postcode' ); ?>
1281 </label>
1282 <label class="ph-onboarding__field" data-validation-field="office_telephone_number">
1283 <span><?php esc_html_e( 'Phone number', 'propertyhive' ); ?></span>
1284 <input type="tel" name="office_telephone_number" value="<?php echo esc_attr( $office['telephone_number'] ); ?>" data-office-field="office_telephone_number" maxlength="30" inputmode="tel" aria-describedby="ph-onboarding-error-office_telephone_number">
1285 <?php $this->output_field_error( 'office_telephone_number' ); ?>
1286 </label>
1287 <label class="ph-onboarding__field" data-validation-field="office_email_address">
1288 <span><?php esc_html_e( 'Email address', 'propertyhive' ); ?></span>
1289 <input type="email" name="office_email_address" value="<?php echo esc_attr( $office['email_address'] ); ?>" data-office-field="office_email_address" maxlength="100" aria-describedby="ph-onboarding-error-office_email_address">
1290 <?php $this->output_field_error( 'office_email_address' ); ?>
1291 </label>
1292 </div>
1293 <?php $this->output_settings_note( __( 'Offices', 'propertyhive' ) ); ?>
1294 </section>
1295
1296 <section class="ph-onboarding__panel" data-step="usage">
1297 <h2><?php esc_html_e( 'What else do you need?', 'propertyhive' ); ?></h2>
1298 <p><?php esc_html_e( 'You\'ve got the property website foundations. Now choose how else you\'d like to use Property Hive.', 'propertyhive' ); ?></p>
1299 <div class="ph-onboarding__cards ph-onboarding__cards--usage" data-input-group="usage" data-validation-field="usage">
1300 <?php $this->output_choice_card( 'usage', 'import_properties', __( 'Import properties', 'propertyhive' ), __( 'Keep your website in sync with properties from your CRM.', 'propertyhive' ), in_array( 'import_properties', $usage, true ), true ); ?>
1301 <?php $this->output_choice_card( 'usage', 'crm', __( 'Use it as a CRM', 'propertyhive' ), __( 'Manage contacts, appraisals, viewings, offers and tenancies.', 'propertyhive' ), in_array( 'crm', $usage, true ) ); ?>
1302 <?php $this->output_choice_card( 'usage', 'portal_uploads', __( 'Upload to portals', 'propertyhive' ), __( 'Automatically send your properties to third party portals.', 'propertyhive' ), in_array( 'portal_uploads', $usage, true ), true ); ?>
1303 <?php $this->output_choice_card( 'usage', 'not_sure', __( 'Just the website for now', 'propertyhive' ), __( 'No problem. You can always choose later.', 'propertyhive' ), in_array( 'not_sure', $usage, true ) ); ?>
1304 </div>
1305 <?php $this->output_field_error( 'usage' ); ?>
1306 <div class="ph-onboarding__pro-note" data-usage-pro-note <?php echo in_array( 'import_properties', $usage, true ) || in_array( 'portal_uploads', $usage, true ) ? '' : 'hidden'; ?>>
1307 <p>
1308 <strong><?php esc_html_e( 'Pro features are included with our paid plans.', 'propertyhive' ); ?></strong>
1309 <?php esc_html_e( 'You can try them free for 7 days.', 'propertyhive' ); ?>
1310 </p>
1311 <span class="ph-onboarding__pro-note-actions">
1312 <a class="button button-primary" href="<?php echo esc_url( $this->get_url( '/pricing', 'trial-cta' ) ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Start your free 7-day trial', 'propertyhive' ); ?></a>
1313 <a class="ph-onboarding__pro-note-link" href="<?php echo esc_url( $this->get_url( '/pricing', 'whats-included' ) ); ?>#compare" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'What\'s included?', 'propertyhive' ); ?></a>
1314 </span>
1315 </div>
1316 <?php /*$this->output_settings_note( __( 'General > Modules', 'propertyhive' ) );*/ ?>
1317 </section>
1318
1319 <section class="ph-onboarding__panel" data-step="license">
1320 <h2><?php esc_html_e( 'Do you have a Property Hive subscription?', 'propertyhive' ); ?></h2>
1321 <?php /*<p><?php esc_html_e( 'If you already have a paid Property Hive plan, enter your licence key below. You can always add it later.', 'propertyhive' ); ?></p>*/ ?>
1322
1323 <?php if ( ! empty( $license_render_state['active'] ) ) : ?>
1324 <input type="hidden" name="has_license_key" value="yes">
1325 <input type="hidden" name="license_key_type" value="<?php echo esc_attr( $license_render_state['type'] ); ?>">
1326 <div class="ph-onboarding__license-state is-ok">
1327 <strong>&#10003; <?php echo esc_html( $license_render_state['message'] ); ?></strong>
1328 </div>
1329 <?php if ( 'old' === $license_render_state['type'] ) : ?>
1330 <div class="ph-onboarding__pro-note">
1331 <p>
1332 <strong><?php esc_html_e( 'Try Property Hive Pro free for 7 days.', 'propertyhive' ); ?></strong>
1333 <?php esc_html_e( 'Pro features need a subscription.', 'propertyhive' ); ?>
1334 </p>
1335 <span class="ph-onboarding__pro-note-actions">
1336 <a class="button button-primary" href="<?php echo esc_url( $this->get_url( '/pricing', 'license-step-trial' ) ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Start your free 7-day trial', 'propertyhive' ); ?></a>
1337 <a class="ph-onboarding__pro-note-link" href="<?php echo esc_url( $this->get_url( '/pricing', 'license-step-whats-included' ) ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'View pricing', 'propertyhive' ); ?></a>
1338 </span>
1339 </div>
1340 <?php endif; ?>
1341 <?php else : ?>
1342 <div class="ph-onboarding__cards ph-onboarding__cards--radio" data-input-group="license-choice">
1343 <?php $this->output_radio_card( 'has_license_key', 'yes', __( 'Yes, I already have a subscription', 'propertyhive' ), __( 'Enter your licence key to connect it to this site.', 'propertyhive' ), 'yes' === $has_license_key, false ); ?>
1344 <?php $this->output_radio_card( 'has_license_key', 'no', __( 'No, not yet', 'propertyhive' ), __( 'That\'s fine. You can carry on with the free version.', 'propertyhive' ), 'no' === $has_license_key, false ); ?>
1345 </div>
1346
1347 <div class="ph-onboarding__license-panel <?php echo 'yes' === $has_license_key ? 'is-open' : ''; ?>" data-license-panel="yes">
1348 <div class="ph-onboarding__license-type" data-input-group="license-key-type" style="display:none">
1349 <label class="ph-onboarding__choice ph-onboarding__choice--compact">
1350 <input type="radio" name="license_key_type" value="pro" checked <?php /*checked( 'pro', $license_key_type );*/ ?>>
1351 <span class="ph-onboarding__choice-check" aria-hidden="true"></span>
1352 <span class="ph-onboarding__choice-copy">
1353 <strong><?php esc_html_e( 'Pro subscription key', 'propertyhive' ); ?></strong>
1354 </span>
1355 </label>
1356 <label class="ph-onboarding__choice ph-onboarding__choice--compact">
1357 <input type="radio" name="license_key_type" value="old" <?php checked( 'old', $license_key_type ); ?>>
1358 <span class="ph-onboarding__choice-check" aria-hidden="true"></span>
1359 <span class="ph-onboarding__choice-copy">
1360 <strong><?php esc_html_e( 'Old-style key (purchased before October 2023)', 'propertyhive' ); ?></strong>
1361 </span>
1362 </label>
1363 </div>
1364
1365 <div class="ph-onboarding__license-entry" data-license-entry>
1366 <div class="ph-onboarding__field ph-onboarding__field--wide" data-validation-field="license_key">
1367 <label for="ph-onboarding-license-key"><span><?php esc_html_e( 'License key', 'propertyhive' ); ?></span></label>
1368 <div class="ph-onboarding__license-entry-row">
1369 <input id="ph-onboarding-license-key" type="text" name="license_key" autocomplete="off" aria-describedby="ph-onboarding-error-license_key">
1370 <button type="button" class="button button-primary" data-license-activate><?php esc_html_e( 'Activate', 'propertyhive' ); ?></button>
1371 </div>
1372 <?php $this->output_field_error( 'license_key' ); ?>
1373 <small><?php esc_html_e( 'You can find your license key in your order confirmation email or your account on the Property Hive website.', 'propertyhive' ); ?></small>
1374 </div>
1375 </div>
1376
1377 <p class="ph-onboarding__license-old-note" data-license-old-note><?php esc_html_e( "Old-style keys enable updates for add-ons you've already purchased. Pro features need a Pro subscription.", 'propertyhive' ); ?></p>
1378 <div class="ph-onboarding__license-state" data-license-state role="status" aria-live="polite"></div>
1379 </div>
1380
1381 <div class="ph-onboarding__license-panel <?php echo 'no' === $has_license_key ? 'is-open' : ''; ?>" data-license-panel="no">
1382 <div class="ph-onboarding__pro-note">
1383 <p>
1384 <strong><?php esc_html_e( 'Want to try the paid features?', 'propertyhive' ); ?></strong>
1385 <?php esc_html_e( 'Try all Property Hive features free for 7 days.', 'propertyhive' ); ?>
1386 </p>
1387 <span class="ph-onboarding__pro-note-actions">
1388 <a class="button button-primary" href="<?php echo esc_url( $this->get_url( '/pricing', 'license-step-trial' ) ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Start your free 7-day trial', 'propertyhive' ); ?></a>
1389 <a class="ph-onboarding__pro-note-link" href="<?php echo esc_url( $this->get_url( '/pricing', 'license-step-whats-included' ) ); ?>#compare" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'What\'s included?', 'propertyhive' ); ?></a>
1390 </span>
1391 </div>
1392 </div>
1393 <?php endif; ?>
1394
1395 <?php $this->output_settings_note( __( 'License', 'propertyhive' ) ); ?>
1396 </section>
1397
1398 <section class="ph-onboarding__panel" data-step="demo-data">
1399 <h2><?php esc_html_e( 'Would you like to import demo data?', 'propertyhive' ); ?></h2>
1400 <p><?php esc_html_e( 'Add sample properties, contacts and other data so you can explore how Property Hive works before using your own.', 'propertyhive' ); ?></p>
1401
1402 <div class="ph-onboarding__cards ph-onboarding__cards--radio" data-input-group="demo-data-choice" data-validation-field="demo_data_choice">
1403 <?php $this->output_radio_card( 'demo_data_choice', 'no', __( 'No', 'propertyhive' ), __( 'Skip the demo data and use my own.', 'propertyhive' ), 'no' === $demo_choice, false ); ?>
1404 <?php $this->output_radio_card( 'demo_data_choice', 'yes', __( 'Yes', 'propertyhive' ), __( 'Add example data so I can explore Property Hive first.', 'propertyhive' ), 'yes' === $demo_choice, false ); ?>
1405 </div>
1406 <?php $this->output_field_error( 'demo_data_choice' ); ?>
1407
1408 <div class="ph-onboarding__demo-box" data-demo-progress-box>
1409 <div class="ph-onboarding__demo-status" data-demo-status>
1410 <?php echo esc_html( $demo_active ? __( 'Demo data will import when you continue.', 'propertyhive' ) : __( 'The Demo Data feature will be activated and the data imported when you continue.', 'propertyhive' ) ); ?>
1411 </div>
1412 <div class="ph-onboarding__demo-progress" aria-hidden="true"><span data-demo-progress-bar></span></div>
1413 <div class="ph-onboarding__demo-results" data-demo-results></div>
1414 </div>
1415 <?php $this->output_settings_note( __( 'Demo Data', 'propertyhive' ) ); ?>
1416 </section>
1417
1418 <section class="ph-onboarding__panel" data-step="complete">
1419 <h2><?php esc_html_e( "You're all set", 'propertyhive' ); ?></h2>
1420 <p><?php esc_html_e( 'Property Hive is ready to go. What would you like to do next?', 'propertyhive' ); ?></p>
1421
1422 <?php /*if ( ! empty( $video_url ) ) : ?>
1423 <div class="ph-onboarding__video">
1424 <iframe src="<?php echo esc_url( $video_url ); ?>" title="<?php esc_attr_e( 'Property Hive getting started video', 'propertyhive' ); ?>" allowfullscreen></iframe>
1425 </div>
1426 <?php else : ?>
1427 <div class="ph-onboarding__video ph-onboarding__video--placeholder">
1428 <span><?php esc_html_e( 'Welcome video coming soon', 'propertyhive' ); ?></span>
1429 </div>
1430 <?php endif;*/ ?>
1431
1432 <div class="ph-onboarding__exits">
1433
1434 <a class="button button-primary ph-onboarding__exit" href="<?php echo esc_url( $this->get_url( '/import-properties', 'exit' ) ); ?>" target="_blank" rel="noopener noreferrer" data-onboarding-exit data-exit="import" data-recommendation="import" <?php echo ! $license_activated && in_array( 'import_properties', $usage, true ) ? '' : 'hidden'; ?>>
1435 <span class="ph-onboarding__exit-label"><?php esc_html_e( 'Learn about property imports', 'propertyhive' ); ?></span>
1436 <span class="ph-onboarding__exit-sub-label"><?php esc_html_e( 'See how it works and check if your CRM is supported.', 'propertyhive' ); ?></span>
1437 </a>
1438
1439 <a class="button button-primary ph-onboarding__exit" href="<?php echo esc_url( $this->get_url( '/export-properties', 'exit' ) ); ?>" target="_blank" rel="noopener noreferrer" data-onboarding-exit data-exit="import" data-recommendation="portal" <?php echo ! $license_activated && in_array( 'portal_uploads', $usage, true ) ? '' : 'hidden'; ?>>
1440 <span class="ph-onboarding__exit-label"><?php esc_html_e( 'Learn about portal exports', 'propertyhive' ); ?></span>
1441 <span class="ph-onboarding__exit-sub-label"><?php esc_html_e( 'See how it works and where you can send your properties.', 'propertyhive' ); ?></span>
1442 </a>
1443
1444 <a class="button button-primary ph-onboarding__exit" href="<?php echo esc_url( admin_url( 'admin.php?page=propertyhive_import_properties' ) ); ?>" data-onboarding-exit data-exit="import" data-import-setup <?php echo $import_feature_active ? '' : 'hidden'; ?>>
1445 <span class="ph-onboarding__exit-label"><?php esc_html_e( 'Create a property import', 'propertyhive' ); ?></span>
1446 <span class="ph-onboarding__exit-sub-label"><?php esc_html_e( 'Bring properties in from your CRM', 'propertyhive' ); ?></span>
1447 </a>
1448 <a class="button button-primary ph-onboarding__exit" href="<?php echo esc_url( admin_url( 'edit.php?post_type=property' ) ); ?>" data-onboarding-exit data-exit="properties">
1449 <span class="ph-onboarding__exit-label"><?php esc_html_e( 'View and manage properties', 'propertyhive' ); ?></span>
1450 <span class="ph-onboarding__exit-sub-label"><?php esc_html_e( 'Open the property list in the dashboard', 'propertyhive' ); ?></span>
1451 </a>
1452 <?php /*<a class="button button-primary ph-onboarding__exit" href="<?php echo esc_url( $site_preview_url ); ?>" data-onboarding-exit data-exit="site" <?php echo ! empty( $state['demo_data_imported'] ) ? '' : 'hidden'; ?>>
1453 <span class="ph-onboarding__exit-label"><?php esc_html_e( 'See properties on your site', 'propertyhive' ); ?></span>
1454 <span class="ph-onboarding__exit-sub-label"><?php esc_html_e( 'View the demo properties on your website', 'propertyhive' ); ?></span>
1455 </a>*/ ?>
1456 <a class="button button-primary ph-onboarding__exit" href="<?php echo esc_url( admin_url( 'admin.php?page=ph-settings' ) ); ?>" data-onboarding-exit data-exit="settings">
1457 <span class="ph-onboarding__exit-label"><?php esc_html_e( 'Explore more settings', 'propertyhive' ); ?></span>
1458 <span class="ph-onboarding__exit-sub-label"><?php esc_html_e( 'Take a look at your setup and make any changes', 'propertyhive' ); ?></span>
1459 </a>
1460 </div>
1461 </section>
1462
1463 <div class="ph-onboarding__message" data-message role="alert" aria-live="polite"></div>
1464
1465 <div class="ph-onboarding__actions">
1466 <button type="button" class="button ph-onboarding__back" data-back><?php esc_html_e( 'Back', 'propertyhive' ); ?></button>
1467 <button type="button" class="button button-primary ph-onboarding__next" data-next><?php esc_html_e( 'Continue', 'propertyhive' ); ?></button>
1468 </div>
1469
1470 <div class="ph-onboarding__tracking">
1471 <label class="ph-onboarding__tracking-label">
1472 <input type="checkbox" name="usage_tracking" value="yes" data-usage-tracking <?php checked( $usage_tracking ); ?>>
1473 <span class="ph-onboarding__tracking-copy">
1474 <strong><?php esc_html_e( 'Help improve Property Hive', 'propertyhive' ); ?></strong>
1475 <small><?php esc_html_e( 'Share non-sensitive setup and feature usage data to help us improve Property Hive. We don\'t collect personal, contact or property details.', 'propertyhive' ); ?></small>
1476 </span>
1477 </label>
1478 </div>
1479 </main>
1480 </div>
1481 </div>
1482 <?php
1483 }
1484
1485 /**
1486 * Output a validation error container.
1487 *
1488 * @param string $field Field key.
1489 */
1490 private function output_field_error( $field ) {
1491 ?>
1492 <span id="ph-onboarding-error-<?php echo esc_attr( $field ); ?>" class="ph-onboarding__field-error" data-field-error="<?php echo esc_attr( $field ); ?>" aria-live="polite"></span>
1493 <?php
1494 }
1495
1496 /**
1497 * Output a checkbox card.
1498 *
1499 * @param string $name Input name.
1500 * @param string $value Input value.
1501 * @param string $title Card title.
1502 * @param string $summary Card summary.
1503 * @param bool $checked Default checked state.
1504 * @param bool $pro Whether the option requires a Pro subscription.
1505 */
1506 private function output_choice_card( $name, $value, $title, $summary, $checked, $pro = false ) {
1507 ?>
1508 <label class="ph-onboarding__choice">
1509 <input type="checkbox" name="<?php echo esc_attr( $name ); ?>[]" value="<?php echo esc_attr( $value ); ?>" <?php checked( $checked ); ?>>
1510 <span class="ph-onboarding__choice-check" aria-hidden="true"></span>
1511 <span class="ph-onboarding__choice-copy">
1512 <strong>
1513 <?php echo esc_html( $title ); ?>
1514 <?php if ( $pro ) : ?>
1515 <span class="ph-onboarding__choice-pro"><?php esc_html_e( 'Pro', 'propertyhive' ); ?></span>
1516 <?php endif; ?>
1517 </strong>
1518 <small><?php echo esc_html( $summary ); ?></small>
1519 </span>
1520 </label>
1521 <?php
1522 }
1523
1524 /**
1525 * Output a radio card.
1526 *
1527 * @param string $name Input name.
1528 * @param string $value Input value.
1529 * @param string $title Card title.
1530 * @param string $summary Card summary.
1531 * @param bool $checked Default checked state.
1532 * @param bool $disabled Disabled state.
1533 */
1534 private function output_radio_card( $name, $value, $title, $summary, $checked, $disabled ) {
1535 ?>
1536 <label class="ph-onboarding__choice <?php echo $disabled ? 'is-disabled' : ''; ?>">
1537 <input type="radio" name="<?php echo esc_attr( $name ); ?>" value="<?php echo esc_attr( $value ); ?>" <?php checked( $checked ); ?> <?php disabled( $disabled ); ?>>
1538 <span class="ph-onboarding__choice-check" aria-hidden="true"></span>
1539 <span class="ph-onboarding__choice-copy">
1540 <strong><?php echo esc_html( $title ); ?></strong>
1541 <small><?php echo esc_html( $summary ); ?></small>
1542 </span>
1543 </label>
1544 <?php
1545 }
1546
1547 /**
1548 * Output a settings location note.
1549 *
1550 * @param string $location Settings location.
1551 */
1552 private function output_settings_note( $location ) {
1553 ?>
1554 <p class="ph-onboarding__settings-note">
1555 <?php
1556 printf(
1557 /* translators: %s: settings location, for example General > International. */
1558 esc_html__( "You can change this later under 'Property Hive > Settings > %s'.", 'propertyhive' ),
1559 esc_html( $location )
1560 );
1561 ?>
1562 </p>
1563 <?php
1564 }
1565
1566 /**
1567 * Localize onboarding script data.
1568 *
1569 * @param bool $demo_active Whether demo data add-on is active.
1570 * @param array $import_feature Cached Property Import feature data.
1571 * @param bool $import_feature_active Whether Property Import is active.
1572 * @param bool $license_activated Whether a license is active.
1573 */
1574 private function localize_script( $demo_active, $import_feature = array(), $import_feature_active = false, $license_activated = false ) {
1575 wp_localize_script(
1576 'propertyhive_admin_onboarding',
1577 'propertyhive_onboarding',
1578 array(
1579 'ajax_url' => admin_url( 'admin-ajax.php' ),
1580 'nonce' => wp_create_nonce( self::NONCE_ACTION ),
1581 'steps' => $this->steps,
1582 'settings_url' => admin_url( 'admin.php?page=ph-settings' ),
1583 'features_url' => admin_url( 'admin.php?page=ph-settings&tab=features&profilter=free' ),
1584 'import_features_url' => admin_url( 'admin.php?page=ph-settings&tab=features&profilter=import' ),
1585 'import_setup_url' => admin_url( 'admin.php?page=propertyhive_import_properties' ),
1586 'license_trial_url' => $this->get_url( '/pricing', 'license-step-trial' ),
1587 'demo_data_active' => $demo_active ? 'yes' : 'no',
1588 'demo_data_nonce' => wp_create_nonce( 'propertyhive_demo_data' ),
1589 'demo_data_feature_slug' => 'propertyhive-demo-data',
1590 'license_activated' => $license_activated ? 'yes' : 'no',
1591 'updates_nonce' => wp_create_nonce( 'updates' ),
1592 'can_install_plugins' => current_user_can( 'install_plugins' ) ? 'yes' : 'no',
1593 'import_feature_slug' => ! empty( $import_feature['slug'] ) ? $import_feature['slug'] : '',
1594 'import_feature_plugin' => ! empty( $import_feature['plugin_file'] ) ? $import_feature['plugin_file'] : '',
1595 'import_feature_active' => $import_feature_active ? 'yes' : 'no',
1596 'address_lookup_enabled' => ( function_exists( 'ph_is_development_environment' ) && ph_is_development_environment() && '' !== trim( (string) get_option( 'propertyhive_getaddress_api_key', '' ) ) ) ? 'yes' : 'no',
1597 'address_lookup_nonce' => wp_create_nonce( 'propertyhive_getaddress_lookup' ),
1598 'demo_base_sections' => array( 'applicant', 'property' ),
1599 'demo_crm_sub_sections' => apply_filters( 'propertyhive_onboarding_demo_crm_sub_sections', array( 'appraisal', 'viewing', 'offer', 'sale', 'tenancy', 'enquiry' ) ),
1600 'i18n' => array(
1601 'continue' => __( 'Continue', 'propertyhive' ),
1602 'saving' => __( 'Saving...', 'propertyhive' ),
1603 'importing' => __( 'Importing demo data...', 'propertyhive' ),
1604 'activatingDemoData' => __( 'Activating Demo Data...', 'propertyhive' ),
1605 'importComplete' => __( 'Demo data imported.', 'propertyhive' ),
1606 'importFailed' => __( 'Demo data could not be imported. You can continue setup and try again later.', 'propertyhive' ),
1607 'demoDataInactive' => __( 'The Demo Data feature could not be activated on this site.', 'propertyhive' ),
1608 'licenseKeyRequired' => __( 'Please enter your license key.', 'propertyhive' ),
1609 'licenseChecking' => __( 'Checking your key... Contacting wp-property-hive.com - a couple of seconds.', 'propertyhive' ),
1610 'licenseProSuccess' => __( 'Pro activated. The features in your plan are ready to use.', 'propertyhive' ),
1611 'licenseOldSuccess' => __( 'License valid - updates are enabled for your purchased add-ons.', 'propertyhive' ),
1612 'licenseError' => __( "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost.", 'propertyhive' ),
1613 'licenseOldTrial' => __( 'Want the Pro features too? Start a free 7-day trial.', 'propertyhive' ),
1614 'importFeatureEnabled' => __( 'Property Import feature enabled', 'propertyhive' ),
1615 'renewLicense' => __( 'Renew License', 'propertyhive' ),
1616 'chooseDepartment' => __( 'Please choose at least one property sector.', 'propertyhive' ),
1617 'chooseCountry' => __( 'Please choose a valid country.', 'propertyhive' ),
1618 'chooseUsage' => __( 'Please choose how you will use Property Hive.', 'propertyhive' ),
1619 'chooseDemoData' => __( 'Please choose whether to import demo data.', 'propertyhive' ),
1620 'officeNameTooLong' => __( 'Office name must be 120 characters or fewer.', 'propertyhive' ),
1621 'officeAddressTooLong' => __( 'Address Line 1 must be 120 characters or fewer.', 'propertyhive' ),
1622 'officeAddress2TooLong' => __( 'Address Line 2 must be 120 characters or fewer.', 'propertyhive' ),
1623 'officeAddress3TooLong' => __( 'Address Line 3 must be 120 characters or fewer.', 'propertyhive' ),
1624 'officeAddress4TooLong' => __( 'Address Line 4 must be 120 characters or fewer.', 'propertyhive' ),
1625 'officePostcodeTooLong' => __( 'Postcode must be 20 characters or fewer.', 'propertyhive' ),
1626 'officePhoneTooLong' => __( 'Phone number must be 30 characters or fewer.', 'propertyhive' ),
1627 'officePhoneInvalid' => __( 'Please enter a valid phone number.', 'propertyhive' ),
1628 'officeEmailTooLong' => __( 'Email address must be 100 characters or fewer.', 'propertyhive' ),
1629 'officeEmailInvalid' => __( 'Please enter a valid email address.', 'propertyhive' ),
1630 'addressLookupSearching' => __( 'Searching addresses...', 'propertyhive' ),
1631 'addressLookupNoResults' => __( 'No matching addresses found.', 'propertyhive' ),
1632 'addressLookupFailed' => __( 'Address lookup could not be completed. Please enter the address manually.', 'propertyhive' ),
1633 'addressLookupSelected' => __( 'Address selected.', 'propertyhive' ),
1634 'skipConfirm' => __( 'Skip setup? You can still configure Property Hive from settings later.', 'propertyhive' ),
1635 ),
1636 )
1637 );
1638 }
1639
1640 /**
1641 * Get available countries.
1642 *
1643 * @return array
1644 */
1645 private function get_countries() {
1646 $countries = new PH_Countries();
1647 return is_array( $countries->countries ) ? $countries->countries : array();
1648 }
1649
1650 /**
1651 * Infer the default country for onboarding.
1652 *
1653 * @return string
1654 */
1655 private function get_default_country() {
1656 $countries = $this->get_countries();
1657 $existing = get_option( 'propertyhive_default_country', '' );
1658 if ( isset( $countries[ $existing ] ) ) {
1659 return $existing;
1660 }
1661
1662 $locale = get_locale();
1663 if ( is_string( $locale ) && false !== strpos( $locale, '_' ) ) {
1664 $locale_country = strtoupper( substr( strrchr( $locale, '_' ), 1 ) );
1665 if ( isset( $countries[ $locale_country ] ) ) {
1666 return $locale_country;
1667 }
1668 }
1669
1670 $timezone = wp_timezone_string();
1671 $mapping = array(
1672 'Europe/London' => 'GB',
1673 'America/New_York' => 'US',
1674 'America/Chicago' => 'US',
1675 'America/Denver' => 'US',
1676 'America/Los_Angeles' => 'US',
1677 'Europe/Dublin' => 'IE',
1678 'Europe/Paris' => 'FR',
1679 'Europe/Madrid' => 'ES',
1680 'Europe/Berlin' => 'DE',
1681 'Australia/Sydney' => 'AU',
1682 'Pacific/Auckland' => 'NZ',
1683 );
1684
1685 if ( isset( $mapping[ $timezone ], $countries[ $mapping[ $timezone ] ] ) ) {
1686 return $mapping[ $timezone ];
1687 }
1688
1689 return isset( $countries['GB'] ) ? 'GB' : key( $countries );
1690 }
1691
1692 /**
1693 * Sanitize office details from the onboarding form.
1694 *
1695 * @return array
1696 */
1697 private function sanitize_office_payload() {
1698 return array(
1699 'name' => isset( $_POST['office_name'] ) ? sanitize_text_field( wp_unslash( $_POST['office_name'] ) ) : '',
1700 'address_1' => isset( $_POST['office_address_1'] ) ? sanitize_text_field( wp_unslash( $_POST['office_address_1'] ) ) : '',
1701 'address_2' => isset( $_POST['office_address_2'] ) ? sanitize_text_field( wp_unslash( $_POST['office_address_2'] ) ) : '',
1702 'address_3' => isset( $_POST['office_address_3'] ) ? sanitize_text_field( wp_unslash( $_POST['office_address_3'] ) ) : '',
1703 'address_4' => isset( $_POST['office_address_4'] ) ? sanitize_text_field( wp_unslash( $_POST['office_address_4'] ) ) : '',
1704 'postcode' => isset( $_POST['office_postcode'] ) ? sanitize_text_field( wp_unslash( $_POST['office_postcode'] ) ) : '',
1705 'telephone_number' => isset( $_POST['office_telephone_number'] ) ? sanitize_text_field( wp_unslash( $_POST['office_telephone_number'] ) ) : '',
1706 'email_address' => isset( $_POST['office_email_address'] ) ? sanitize_text_field( wp_unslash( $_POST['office_email_address'] ) ) : '',
1707 );
1708 }
1709
1710 /**
1711 * Validate office details.
1712 *
1713 * @param array $office Office payload.
1714 * @return array
1715 */
1716 private function validate_office_payload( $office ) {
1717 $errors = array();
1718
1719 $field_labels = array(
1720 'name' => __( 'Office name', 'propertyhive' ),
1721 'address_1' => __( 'Address Line 1', 'propertyhive' ),
1722 'address_2' => __( 'Address Line 2', 'propertyhive' ),
1723 'address_3' => __( 'Address Line 3', 'propertyhive' ),
1724 'address_4' => __( 'Address Line 4', 'propertyhive' ),
1725 'postcode' => __( 'Postcode', 'propertyhive' ),
1726 'telephone_number' => __( 'Phone number', 'propertyhive' ),
1727 'email_address' => __( 'Email address', 'propertyhive' ),
1728 );
1729
1730 $field_keys = array(
1731 'name' => 'office_name',
1732 'address_1' => 'office_address_1',
1733 'address_2' => 'office_address_2',
1734 'address_3' => 'office_address_3',
1735 'address_4' => 'office_address_4',
1736 'postcode' => 'office_postcode',
1737 'telephone_number' => 'office_telephone_number',
1738 'email_address' => 'office_email_address',
1739 );
1740
1741 $max_lengths = array(
1742 'name' => 120,
1743 'address_1' => 120,
1744 'address_2' => 120,
1745 'address_3' => 120,
1746 'address_4' => 120,
1747 'postcode' => 20,
1748 'telephone_number' => 30,
1749 'email_address' => 100,
1750 );
1751
1752 foreach ( $max_lengths as $field => $max_length ) {
1753 if ( isset( $office[ $field ] ) && strlen( $office[ $field ] ) > $max_length ) {
1754 $errors[ $field_keys[ $field ] ] = sprintf(
1755 /* translators: 1: field label, 2: maximum character count. */
1756 __( '%1$s must be %2$d characters or fewer.', 'propertyhive' ),
1757 $field_labels[ $field ],
1758 $max_length
1759 );
1760 }
1761 }
1762
1763 /*if ( ! empty( $office['telephone_number'] ) ) {
1764 $digits = preg_replace( '/\D+/', '', $office['telephone_number'] );
1765 if ( strlen( $digits ) < 7 || ! preg_match( '/^[0-9+\-\s().]+$/', $office['telephone_number'] ) ) {
1766 $errors['office_telephone_number'] = __( 'Please enter a valid phone number.', 'propertyhive' );
1767 }
1768 }*/
1769
1770 if ( ! empty( $office['email_address'] ) && ! is_email( $office['email_address'] ) ) {
1771 $errors['office_email_address'] = __( 'Please enter a valid email address.', 'propertyhive' );
1772 }
1773
1774 return $errors;
1775 }
1776
1777 /**
1778 * Get defaults for the office address step.
1779 *
1780 * @param array $state Onboarding state.
1781 * @return array
1782 */
1783 private function get_office_defaults( $state ) {
1784 $office = array(
1785 'name' => '',
1786 'address_1' => '',
1787 'address_2' => '',
1788 'address_3' => '',
1789 'address_4' => '',
1790 'postcode' => '',
1791 'telephone_number' => '',
1792 'email_address' => '',
1793 );
1794
1795 $office_id = $this->get_primary_office_id();
1796 if ( $office_id ) {
1797 $office = array(
1798 'name' => get_the_title( $office_id ),
1799 'address_1' => get_post_meta( $office_id, '_office_address_1', true ),
1800 'address_2' => get_post_meta( $office_id, '_office_address_2', true ),
1801 'address_3' => get_post_meta( $office_id, '_office_address_3', true ),
1802 'address_4' => get_post_meta( $office_id, '_office_address_4', true ),
1803 'postcode' => get_post_meta( $office_id, '_office_address_postcode', true ),
1804 'telephone_number' => $this->get_primary_office_contact_value( $office_id, 'telephone_number' ),
1805 'email_address' => $this->get_primary_office_contact_value( $office_id, 'email_address' ),
1806 );
1807 }
1808
1809 if ( ! empty( $state['office'] ) && is_array( $state['office'] ) ) {
1810 foreach ( array_keys( $office ) as $key ) {
1811 if ( isset( $state['office'][ $key ] ) && '' !== $state['office'][ $key ] ) {
1812 $office[ $key ] = $state['office'][ $key ];
1813 }
1814 }
1815 }
1816
1817 return $office;
1818 }
1819
1820 /**
1821 * Get the active departments that should receive shared office contact details.
1822 *
1823 * @return array
1824 */
1825 private function get_office_contact_departments() {
1826 $state = self::get_state();
1827 $allowed = array( 'residential-sales', 'residential-lettings', 'commercial' );
1828 $departments = ! empty( $state['departments'] ) && is_array( $state['departments'] ) ? array_values( array_intersect( $allowed, $state['departments'] ) ) : array();
1829
1830 if ( empty( $departments ) ) {
1831 foreach ( $allowed as $department ) {
1832 if ( 'yes' === get_option( 'propertyhive_active_departments_' . str_replace( 'residential-', '', $department ), 'no' ) ) {
1833 $departments[] = $department;
1834 }
1835 }
1836 }
1837
1838 return ! empty( $departments ) ? $departments : $allowed;
1839 }
1840
1841 /**
1842 * Get the first saved office contact value across active departments.
1843 *
1844 * @param int $office_id Office post id.
1845 * @param string $field Contact field name.
1846 * @return string
1847 */
1848 private function get_primary_office_contact_value( $office_id, $field ) {
1849 $meta_prefix = 'email_address' === $field ? '_office_email_address_' : '_office_telephone_number_';
1850
1851 foreach ( $this->get_office_contact_departments() as $department ) {
1852 $value = get_post_meta( $office_id, $meta_prefix . str_replace( 'residential-', '', $department ), true );
1853 if ( '' !== $value ) {
1854 return $value;
1855 }
1856 }
1857
1858 return '';
1859 }
1860
1861 /**
1862 * Get the primary office id, falling back to the first office.
1863 *
1864 * @return int
1865 */
1866 private function get_primary_office_id() {
1867 $office_ids = get_posts(
1868 array(
1869 'post_type' => 'office',
1870 'post_status' => 'any',
1871 'posts_per_page' => 1,
1872 'fields' => 'ids',
1873 'meta_key' => 'primary',
1874 'meta_value' => '1',
1875 )
1876 );
1877
1878 if ( ! empty( $office_ids ) ) {
1879 return absint( $office_ids[0] );
1880 }
1881
1882 $office_ids = get_posts(
1883 array(
1884 'post_type' => 'office',
1885 'post_status' => 'any',
1886 'posts_per_page' => 1,
1887 'fields' => 'ids',
1888 )
1889 );
1890
1891 return ! empty( $office_ids ) ? absint( $office_ids[0] ) : 0;
1892 }
1893
1894 /**
1895 * Get current step.
1896 *
1897 * @param array $state Onboarding state.
1898 * @return string
1899 */
1900 private function get_current_step( $state ) {
1901 if ( isset( $state['status'] ) && 'completed' === $state['status'] ) {
1902 return 'complete';
1903 }
1904
1905 if ( ! empty( $state['last_step'] ) && in_array( $state['last_step'], $this->steps, true ) && 'completed' !== $state['status'] ) {
1906 return $state['last_step'];
1907 }
1908
1909 return 'intro';
1910 }
1911
1912 /**
1913 * Get pricing page URL.
1914 *
1915 * @param string $utm_content UTM content value identifying which link was clicked.
1916 * @return string
1917 */
1918 private function get_url( $url, $utm_content = '' ) {
1919 $args = array(
1920 'src' => 'plugin-onboarding',
1921 'utm_source' => 'propertyhive-plugin',
1922 'utm_medium' => 'onboarding',
1923 'utm_campaign' => 'setup-wizard',
1924 );
1925
1926 if ( '' !== $utm_content ) {
1927 $args['utm_content'] = $utm_content;
1928 }
1929
1930 $url = add_query_arg( $args, 'https://wp-property-hive.com' . $url );
1931
1932 return apply_filters( 'propertyhive_onboarding_external_url', $url, $utm_content );
1933 }
1934 }
1935
1936 endif;
1937
1938 return new PH_Admin_Onboarding();
1939