| 1 |
<?php |
| 2 |
// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean |
| 3 |
// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate. |
| 4 |
|
| 5 |
if ( ! defined( 'ABSPATH' ) ) { |
| 6 |
exit; // Exit if accessed directly |
| 7 |
} |
| 8 |
|
| 9 |
/** |
| 10 |
* PropertyHive Admin. |
| 11 |
* |
| 12 |
* @class PH_Admin |
| 13 |
* @author PropertyHive |
| 14 |
* @category Admin |
| 15 |
* @package PropertyHive/Admin |
| 16 |
* @version 1.0.0 |
| 17 |
*/ |
| 18 |
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin; preserving the existing PH_* class name is required for plugin and extension compatibility. |
| 19 |
class PH_Admin { |
| 20 |
|
| 21 |
/** |
| 22 |
* Constructor |
| 23 |
*/ |
| 24 |
public function __construct() |
| 25 |
{ |
| 26 |
add_action( 'init', array( $this, 'includes' ) ); |
| 27 |
add_action( 'current_screen', array( $this, 'conditional_includes' ) ); |
| 28 |
add_action( 'current_screen', array( $this, 'disable_propertyhive_meta_box_dragging' ) ); |
| 29 |
add_action( 'current_screen', array( $this, 'remove_propertyhive_meta_boxes_from_screen_options' ) ); |
| 30 |
add_action( 'admin_notices', array( $this, 'review_admin_notices') ); |
| 31 |
add_action( 'admin_notices', array( $this, 'archive_admin_notices' ) ); |
| 32 |
add_action( 'admin_menu', array( $this, 'admin_dashboard_pages' ) ); |
| 33 |
add_action( 'admin_head', array( $this, 'admin_head' ) ); |
| 34 |
add_action( 'admin_init', array( $this, 'admin_redirects' ) ); |
| 35 |
add_action( 'admin_init', array( $this, 'prevent_access_to_admin' ) ); |
| 36 |
add_action( 'admin_init', array( $this, 'view_email' ) ); |
| 37 |
add_action( 'admin_init', array( $this, 'preview_emails' ) ); |
| 38 |
add_action( 'admin_init', array( $this, 'record_recently_viewed' ) ); |
| 39 |
add_action( 'admin_init', array( $this, 'export_applicant_list' ) ); |
| 40 |
add_action( 'admin_init', array( $this, 'export_sub_grid' ) ); |
| 41 |
add_action( 'admin_init', array( $this, 'check_hide_demo_data_tab' ) ); |
| 42 |
add_action( 'admin_init', array( $this, 'check_install_add_on' ) ); |
| 43 |
add_filter( 'propertyhive_screen_ids', array( $this, 'crm_only_mode_screen_id' ) ); |
| 44 |
} |
| 45 |
|
| 46 |
public function archive_admin_notices() |
| 47 |
{ |
| 48 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 49 |
if ( isset($_GET['bulk_archived_posts']) && !empty($_GET['bulk_archived_posts'])) |
| 50 |
{ |
| 51 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 52 |
$post_type = ( isset($_GET['post_type']) && is_string($_GET['post_type']) ) ? sanitize_key( wp_unslash($_GET['post_type']) ) : ''; |
| 53 |
if ( $post_type ) |
| 54 |
{ |
| 55 |
$post_type_object = get_post_type_object($post_type); |
| 56 |
if ( ! $post_type_object ) { |
| 57 |
return; |
| 58 |
} |
| 59 |
|
| 60 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 61 |
$count = is_string($_GET['bulk_archived_posts']) ? absint($_GET['bulk_archived_posts']) : 0; |
| 62 |
|
| 63 |
if ( $post_type_object ) |
| 64 |
{ |
| 65 |
$message = sprintf( |
| 66 |
/* translators: 1: number of items, 2: post type label */ |
| 67 |
_n( |
| 68 |
'%1$s %2$s moved to archive.', |
| 69 |
'%1$s %2$s moved to archive.', |
| 70 |
$count, |
| 71 |
'propertyhive' |
| 72 |
), |
| 73 |
number_format_i18n( $count ), |
| 74 |
$count === 1 |
| 75 |
? $post_type_object->labels->singular_name |
| 76 |
: $post_type_object->labels->name |
| 77 |
); |
| 78 |
|
| 79 |
printf( |
| 80 |
'<div id="message" class="notice is-dismissible updated"><p>%s</p></div>', |
| 81 |
esc_html( $message ) |
| 82 |
); |
| 83 |
} |
| 84 |
} |
| 85 |
} |
| 86 |
|
| 87 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 88 |
if ( isset($_GET['bulk_unarchived_posts']) && !empty($_GET['bulk_unarchived_posts']) ) |
| 89 |
{ |
| 90 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 91 |
$post_type = ( isset($_GET['post_type']) && is_string($_GET['post_type']) ) ? sanitize_key( wp_unslash($_GET['post_type']) ) : ''; |
| 92 |
if ( $post_type ) |
| 93 |
{ |
| 94 |
$post_type_object = get_post_type_object($post_type); |
| 95 |
if ( ! $post_type_object ) { |
| 96 |
return; |
| 97 |
} |
| 98 |
|
| 99 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 100 |
$count = is_string($_GET['bulk_unarchived_posts']) ? absint($_GET['bulk_unarchived_posts']) : 0; |
| 101 |
|
| 102 |
if ( $post_type_object ) |
| 103 |
{ |
| 104 |
$message = sprintf( |
| 105 |
/* translators: 1: number of items, 2: post type label */ |
| 106 |
_n( |
| 107 |
'%1$s %2$s removed from archive.', |
| 108 |
'%1$s %2$s removed from archive.', |
| 109 |
$count, |
| 110 |
'propertyhive' |
| 111 |
), |
| 112 |
number_format_i18n( $count ), |
| 113 |
$count === 1 |
| 114 |
? $post_type_object->labels->singular_name |
| 115 |
: $post_type_object->labels->name |
| 116 |
); |
| 117 |
|
| 118 |
printf( |
| 119 |
'<div id="message" class="notice is-dismissible updated"><p>%s</p></div>', |
| 120 |
esc_html( $message ) |
| 121 |
); |
| 122 |
} |
| 123 |
} |
| 124 |
} |
| 125 |
} |
| 126 |
|
| 127 |
public function crm_only_mode_screen_id( $screen_ids ) |
| 128 |
{ |
| 129 |
$current_user = wp_get_current_user(); |
| 130 |
|
| 131 |
$user_id = $current_user->ID; |
| 132 |
|
| 133 |
$crm_only_mode = get_user_meta( $user_id, 'crm_only_mode', TRUE ); |
| 134 |
|
| 135 |
if ( $crm_only_mode == '1' ) |
| 136 |
{ |
| 137 |
$screen_ids[] = 'toplevel_page_ph-settings'; |
| 138 |
} |
| 139 |
|
| 140 |
return $screen_ids; |
| 141 |
} |
| 142 |
|
| 143 |
public function check_install_add_on() |
| 144 |
{ |
| 145 |
$request_get = wp_unslash( $_GET ); |
| 146 |
$ph_action = isset( $request_get['ph_action'] ) && is_string( $request_get['ph_action'] ) ? sanitize_key( $request_get['ph_action'] ) : ''; |
| 147 |
$encoded_slug = isset( $request_get['ph_add_on_slug'] ) && is_string( $request_get['ph_add_on_slug'] ) ? sanitize_text_field( $request_get['ph_add_on_slug'] ) : ''; |
| 148 |
$encoded_plugin = isset( $request_get['ph_add_on_plugin'] ) && is_string( $request_get['ph_add_on_plugin'] ) ? sanitize_text_field( $request_get['ph_add_on_plugin'] ) : ''; |
| 149 |
|
| 150 |
if ( 'install_add_on' === $ph_action && '' !== $encoded_slug && '' !== $encoded_plugin ) |
| 151 |
{ |
| 152 |
if ( ! current_user_can( 'manage_propertyhive' ) || ! current_user_can( 'install_plugins' ) ) { |
| 153 |
wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) ); |
| 154 |
} |
| 155 |
check_admin_referer( 'propertyhive-install-add-on' ); |
| 156 |
|
| 157 |
$installed_plugins = get_option( 'propertyhive_pre_pro_add_ons', array()); |
| 158 |
|
| 159 |
if ( empty($installed_plugins) ) |
| 160 |
{ |
| 161 |
$installed_plugins = array(); |
| 162 |
} |
| 163 |
|
| 164 |
$decoded_slug = base64_decode( $encoded_slug, true ); |
| 165 |
$decoded_plugin = base64_decode( $encoded_plugin, true ); |
| 166 |
if ( false === $decoded_slug || false === $decoded_plugin ) { |
| 167 |
wp_die( esc_html__( 'Invalid add-on request.', 'propertyhive' ), '', array( 'response' => 400 ) ); |
| 168 |
} |
| 169 |
|
| 170 |
$installed_plugins[] = array( |
| 171 |
'slug' => ph_clean( $decoded_slug ), |
| 172 |
'plugin' => ph_clean( $decoded_plugin ) |
| 173 |
); |
| 174 |
|
| 175 |
update_option( 'propertyhive_pre_pro_add_ons', $installed_plugins ); |
| 176 |
|
| 177 |
wp_safe_redirect( admin_url('admin.php?page=ph-settings&tab=features') ); |
| 178 |
die(); |
| 179 |
} |
| 180 |
} |
| 181 |
|
| 182 |
public function check_hide_demo_data_tab() |
| 183 |
{ |
| 184 |
$request_get = wp_unslash( $_GET ); |
| 185 |
$tab = isset( $request_get['tab'] ) && is_string( $request_get['tab'] ) ? sanitize_key( $request_get['tab'] ) : ''; |
| 186 |
$hide_tab = isset( $request_get['hidetab'] ) && is_scalar( $request_get['hidetab'] ) ? (string) $request_get['hidetab'] : ''; |
| 187 |
|
| 188 |
if ( 'demo_data' === $tab && '' !== $hide_tab ) |
| 189 |
{ |
| 190 |
if ( ! current_user_can( 'manage_propertyhive' ) ) { |
| 191 |
wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) ); |
| 192 |
} |
| 193 |
check_admin_referer( 'propertyhive-hide-demo-data' ); |
| 194 |
|
| 195 |
update_option( 'propertyhive_hide_demo_data_tab', 'yes' ); |
| 196 |
wp_safe_redirect( admin_url('admin.php?page=ph-settings') ); |
| 197 |
die(); |
| 198 |
} |
| 199 |
} |
| 200 |
|
| 201 |
public function export_sub_grid() |
| 202 |
{ |
| 203 |
$request_get = wp_unslash( $_GET ); |
| 204 |
$sub_grid = isset( $request_get['sub_grid'] ) && is_string( $request_get['sub_grid'] ) ? sanitize_key( $request_get['sub_grid'] ) : ''; |
| 205 |
$raw_record_ids = isset( $request_get['record_ids'] ) && is_string( $request_get['record_ids'] ) ? sanitize_text_field( $request_get['record_ids'] ) : ''; |
| 206 |
|
| 207 |
if ( '' !== $sub_grid ) |
| 208 |
{ |
| 209 |
if ( ! current_user_can( 'manage_propertyhive' ) ) { |
| 210 |
wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) ); |
| 211 |
} |
| 212 |
check_admin_referer( 'propertyhive-export-sub-grid', 'ph_export_nonce' ); |
| 213 |
|
| 214 |
$export_types = array( |
| 215 |
'property-viewings-grid' => 'viewing', |
| 216 |
'contact-viewings-grid' => 'viewing', |
| 217 |
'property-offers-grid' => 'offer', |
| 218 |
'contact-offers-grid' => 'offer', |
| 219 |
'property-sales-grid' => 'sale', |
| 220 |
'contact-sales-grid' => 'sale', |
| 221 |
); |
| 222 |
$record_ids = '' !== $raw_record_ids |
| 223 |
? array_values( array_filter( array_map( 'absint', explode( '|', $raw_record_ids ) ) ) ) |
| 224 |
: array(); |
| 225 |
|
| 226 |
if ( ! isset( $export_types[ $sub_grid ] ) || empty( $record_ids ) ) { |
| 227 |
wp_die( esc_html__( 'Invalid export request', 'propertyhive' ), '', array( 'response' => 400 ) ); |
| 228 |
} |
| 229 |
foreach ( $record_ids as $record_id ) { |
| 230 |
if ( get_post_type( $record_id ) !== $export_types[ $sub_grid ] || ! current_user_can( 'edit_post', $record_id ) ) { |
| 231 |
wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) ); |
| 232 |
} |
| 233 |
} |
| 234 |
|
| 235 |
ob_start(); |
| 236 |
|
| 237 |
$df = fopen("php://output", 'w'); |
| 238 |
|
| 239 |
$columns = array( 'id' => __( 'ID', 'propertyhive' ) ); |
| 240 |
|
| 241 |
if ( strpos( $sub_grid, 'viewings' ) !== false ) |
| 242 |
{ |
| 243 |
$columns['datetime'] = __( 'Date/Time', 'propertyhive' ); |
| 244 |
$columns['property'] = __( 'Property', 'propertyhive' ); |
| 245 |
//$columns['owner'] = __( 'Owner/Landlord', 'propertyhive' ); |
| 246 |
$columns['applicant'] = __( 'Applicant(s)', 'propertyhive' ); |
| 247 |
$columns['negotiator'] = __( 'Attending Negotiator(s)', 'propertyhive' ); |
| 248 |
$columns['status'] = __( 'Status', 'propertyhive' ); |
| 249 |
$columns['feedback'] = __( 'Feedback', 'propertyhive' ); |
| 250 |
} |
| 251 |
elseif ( strpos( $sub_grid, 'offers' ) !== false ) |
| 252 |
{ |
| 253 |
$columns['datetime'] = __( 'Date/Time', 'propertyhive' ); |
| 254 |
$columns['property'] = __( 'Property', 'propertyhive' ); |
| 255 |
//$columns['owner'] = __( 'Owner/Landlord', 'propertyhive' ); |
| 256 |
$columns['applicant'] = __( 'Applicant(s)', 'propertyhive' ); |
| 257 |
$columns['status'] = __( 'Status', 'propertyhive' ); |
| 258 |
$columns['amount'] = __( 'Offer Amount', 'propertyhive' ); |
| 259 |
} |
| 260 |
elseif ( strpos( $sub_grid, 'sales' ) !== false ) |
| 261 |
{ |
| 262 |
$columns['date'] = __( 'Date', 'propertyhive' ); |
| 263 |
$columns['property'] = __( 'Property', 'propertyhive' ); |
| 264 |
//$columns['owner'] = __( 'Owner/Landlord', 'propertyhive' ); |
| 265 |
$columns['applicant'] = __( 'Applicant(s)', 'propertyhive' ); |
| 266 |
$columns['status'] = __( 'Status', 'propertyhive' ); |
| 267 |
$columns['amount'] = __( 'Sale Amount', 'propertyhive' ); |
| 268 |
} |
| 269 |
|
| 270 |
fputcsv($df, $columns); |
| 271 |
|
| 272 |
if ( ! empty( $record_ids ) ) |
| 273 |
{ |
| 274 |
if ( !empty($record_ids) ) |
| 275 |
{ |
| 276 |
if ( strpos( $sub_grid, 'viewings' ) !== false ) |
| 277 |
{ |
| 278 |
$args = array( |
| 279 |
'post_type' => 'viewing', |
| 280 |
'nopaging' => TRUE, |
| 281 |
'fields' => 'ids', |
| 282 |
'post__in' => $record_ids, |
| 283 |
'order' => 'ASC', |
| 284 |
'orderby' => 'meta_value', |
| 285 |
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- The export sorts a bounded, capability-checked viewing list by its fixed date-time metadata key. |
| 286 |
'meta_key' => '_start_date_time', |
| 287 |
); |
| 288 |
|
| 289 |
$records_query = new WP_Query( $args ); |
| 290 |
|
| 291 |
if ( $records_query->have_posts() ) |
| 292 |
{ |
| 293 |
while ( $records_query->have_posts() ) |
| 294 |
{ |
| 295 |
$records_query->the_post(); |
| 296 |
|
| 297 |
$viewing = new PH_Viewing( get_the_ID() ); |
| 298 |
|
| 299 |
$property_id = (int)$viewing->_property_id; |
| 300 |
$property_address = ''; |
| 301 |
if ( !empty($property_id) ) |
| 302 |
{ |
| 303 |
$property = new PH_Property( $property_id ); |
| 304 |
$property_address = $property->get_formatted_full_address(); |
| 305 |
} |
| 306 |
|
| 307 |
$columns = array( |
| 308 |
get_the_ID(), |
| 309 |
gmdate("H:i jS F Y", strtotime($viewing->_start_date_time)), |
| 310 |
$property_address, |
| 311 |
str_replace("<br>", "\n", $viewing->get_applicants()), |
| 312 |
$viewing->get_negotiators(), |
| 313 |
str_replace("<br>", "\n", $viewing->get_status()), |
| 314 |
$viewing->_feedback |
| 315 |
); |
| 316 |
|
| 317 |
fputcsv($df, $columns); |
| 318 |
} |
| 319 |
} |
| 320 |
} |
| 321 |
elseif ( strpos( $sub_grid, 'offers' ) !== false ) |
| 322 |
{ |
| 323 |
$args = array( |
| 324 |
'post_type' => 'offer', |
| 325 |
'nopaging' => TRUE, |
| 326 |
'fields' => 'ids', |
| 327 |
'post__in' => $record_ids, |
| 328 |
'order' => 'ASC', |
| 329 |
'orderby' => 'meta_value', |
| 330 |
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- The export sorts a bounded, capability-checked offer list by its fixed date-time metadata key. |
| 331 |
'meta_key' => '_offer_date_time', |
| 332 |
); |
| 333 |
|
| 334 |
$records_query = new WP_Query( $args ); |
| 335 |
|
| 336 |
if ( $records_query->have_posts() ) |
| 337 |
{ |
| 338 |
while ( $records_query->have_posts() ) |
| 339 |
{ |
| 340 |
$records_query->the_post(); |
| 341 |
|
| 342 |
$offer = new PH_Offer( get_the_ID() ); |
| 343 |
|
| 344 |
$property_id = (int)$offer->_property_id; |
| 345 |
$property_address = ''; |
| 346 |
if ( !empty($property_id) ) |
| 347 |
{ |
| 348 |
$property = new PH_Property( $property_id ); |
| 349 |
$property_address = $property->get_formatted_full_address(); |
| 350 |
} |
| 351 |
|
| 352 |
$columns = array( |
| 353 |
get_the_ID(), |
| 354 |
gmdate("H:i jS F Y", strtotime($offer->_offer_date_time)), |
| 355 |
$property_address, |
| 356 |
str_replace("<br>", "\n", $offer->get_applicants()), |
| 357 |
$offer->_status, |
| 358 |
html_entity_decode($offer->get_formatted_amount()) |
| 359 |
); |
| 360 |
|
| 361 |
fputcsv($df, $columns); |
| 362 |
} |
| 363 |
} |
| 364 |
} |
| 365 |
elseif ( strpos( $sub_grid, 'sales' ) !== false ) |
| 366 |
{ |
| 367 |
$args = array( |
| 368 |
'post_type' => 'sale', |
| 369 |
'nopaging' => TRUE, |
| 370 |
'fields' => 'ids', |
| 371 |
'post__in' => $record_ids, |
| 372 |
'order' => 'ASC', |
| 373 |
'orderby' => 'meta_value', |
| 374 |
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- The export sorts a bounded, capability-checked sale list by its fixed date-time metadata key. |
| 375 |
'meta_key' => '_sale_date_time', |
| 376 |
); |
| 377 |
|
| 378 |
$records_query = new WP_Query( $args ); |
| 379 |
|
| 380 |
if ( $records_query->have_posts() ) |
| 381 |
{ |
| 382 |
while ( $records_query->have_posts() ) |
| 383 |
{ |
| 384 |
$records_query->the_post(); |
| 385 |
|
| 386 |
$sale = new PH_Sale( get_the_ID() ); |
| 387 |
|
| 388 |
$property_id = (int)$sale->_property_id; |
| 389 |
$property_address = ''; |
| 390 |
if ( !empty($property_id) ) |
| 391 |
{ |
| 392 |
$property = new PH_Property( $property_id ); |
| 393 |
$property_address = $property->get_formatted_full_address(); |
| 394 |
} |
| 395 |
|
| 396 |
$columns = array( |
| 397 |
get_the_ID(), |
| 398 |
gmdate("jS F Y", strtotime($sale->_sale_date_time)), |
| 399 |
$property_address, |
| 400 |
str_replace("<br>", "\n", $sale->get_applicants()), |
| 401 |
$sale->_status, |
| 402 |
html_entity_decode($sale->get_formatted_amount()) |
| 403 |
); |
| 404 |
|
| 405 |
fputcsv($df, $columns); |
| 406 |
} |
| 407 |
} |
| 408 |
} |
| 409 |
} |
| 410 |
} |
| 411 |
|
| 412 |
fclose($df); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closes the php://output CSV stream. |
| 413 |
|
| 414 |
$output = ob_get_clean(); |
| 415 |
|
| 416 |
$filename = sanitize_title( $sub_grid ) . '-' . gmdate("YmdHis") . '.csv'; |
| 417 |
|
| 418 |
// disable caching |
| 419 |
$now = gmdate("D, d M Y H:i:s"); |
| 420 |
header("Expires: Tue, 03 Jul 2001 06:00:00 GMT"); |
| 421 |
header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate"); |
| 422 |
header("Last-Modified: {$now} GMT"); |
| 423 |
|
| 424 |
// force download |
| 425 |
header("Content-Type: application/force-download"); |
| 426 |
header("Content-Type: application/octet-stream"); |
| 427 |
header("Content-Type: application/download"); |
| 428 |
|
| 429 |
// disposition / encoding on response body |
| 430 |
header("Content-Disposition: attachment;filename={$filename}"); |
| 431 |
header("Content-Transfer-Encoding: binary"); |
| 432 |
|
| 433 |
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV download produced by fputcsv, not HTML; HTML escaping would corrupt exported field values. |
| 434 |
echo $output; |
| 435 |
|
| 436 |
die(); |
| 437 |
} |
| 438 |
} |
| 439 |
|
| 440 |
public function export_applicant_list() |
| 441 |
{ |
| 442 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- These flags only trigger PH_Admin_Applicant_List::export(), which verifies ph_applicant_export_nonce and manage_propertyhive before generating the CSV. |
| 443 |
$request_post = wp_unslash( $_POST ); |
| 444 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- These flags only trigger PH_Admin_Applicant_List::export(), which verifies ph_applicant_export_nonce and manage_propertyhive before generating the CSV. |
| 445 |
$submitted_applicant_list = isset( $request_post['submitted_applicant_list'] ) && '1' === (string) $request_post['submitted_applicant_list']; |
| 446 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- These flags only trigger PH_Admin_Applicant_List::export(), which verifies ph_applicant_export_nonce and manage_propertyhive before generating the CSV. |
| 447 |
$export_applicant_list_results = isset( $request_post['export_applicant_list_results'] ) && '1' === (string) $request_post['export_applicant_list_results']; |
| 448 |
|
| 449 |
if ( $submitted_applicant_list && $export_applicant_list_results ) |
| 450 |
{ |
| 451 |
include_once( 'class-ph-admin-applicant-list.php' ); |
| 452 |
$ph_admin_applicant_list = new PH_Admin_Applicant_List(); |
| 453 |
$ph_admin_applicant_list->export(); |
| 454 |
} |
| 455 |
} |
| 456 |
|
| 457 |
public function record_recently_viewed() |
| 458 |
{ |
| 459 |
global $pagenow; |
| 460 |
|
| 461 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This records the current user's own read-only navigation history; it performs no cross-user or CRM state change. |
| 462 |
$request_get = wp_unslash( $_GET ); |
| 463 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This records the current user's own read-only navigation history; it performs no cross-user or CRM state change. |
| 464 |
$recent_post_id = isset( $request_get['post'] ) && is_scalar( $request_get['post'] ) ? absint( $request_get['post'] ) : 0; |
| 465 |
|
| 466 |
if ( |
| 467 |
'post.php' === $pagenow && |
| 468 |
$recent_post_id > 0 && |
| 469 |
in_array( |
| 470 |
get_post_type( $recent_post_id ), |
| 471 |
apply_filters( 'propertyhive_post_types_with_tabs', array('property', 'contact', 'enquiry', 'appraisal', 'viewing', 'offer', 'sale') ) |
| 472 |
) |
| 473 |
) |
| 474 |
{ |
| 475 |
$recently_viewed = get_user_meta( get_current_user_id(), '_propertyhive_recently_viewed', TRUE ); |
| 476 |
|
| 477 |
if ( !is_array($recently_viewed) ) |
| 478 |
{ |
| 479 |
$recently_viewed = array(); |
| 480 |
} |
| 481 |
|
| 482 |
foreach ( $recently_viewed as $time => $post ) |
| 483 |
{ |
| 484 |
if ( $recent_post_id == $post['id'] ) |
| 485 |
{ |
| 486 |
unset($recently_viewed[$time]); |
| 487 |
} |
| 488 |
} |
| 489 |
|
| 490 |
$title = get_the_title( $recent_post_id ); |
| 491 |
|
| 492 |
switch ( get_post_type( $recent_post_id ) ) |
| 493 |
{ |
| 494 |
case "appraisal": |
| 495 |
{ |
| 496 |
$appraisal = new PH_Appraisal( $recent_post_id ); |
| 497 |
$title = $appraisal->get_formatted_summary_address(); |
| 498 |
break; |
| 499 |
} |
| 500 |
case "property": |
| 501 |
{ |
| 502 |
$property = new PH_Property( $recent_post_id ); |
| 503 |
$title = $property->get_formatted_summary_address(); |
| 504 |
break; |
| 505 |
} |
| 506 |
case "enquiry": |
| 507 |
case "viewing": |
| 508 |
case "offer": |
| 509 |
case "sale": |
| 510 |
{ |
| 511 |
$property_id = get_post_meta( $recent_post_id, '_property_id', TRUE ); |
| 512 |
if ( $property_id != '' ) |
| 513 |
{ |
| 514 |
$property = new PH_Property( (int)$property_id ); |
| 515 |
$title = $property->get_formatted_summary_address(); |
| 516 |
} |
| 517 |
break; |
| 518 |
} |
| 519 |
} |
| 520 |
|
| 521 |
$title = ucfirst( get_post_type( $recent_post_id ) ) . ' - ' . $title; |
| 522 |
|
| 523 |
$recently_viewed = array(time() => array( |
| 524 |
'id' => $recent_post_id, |
| 525 |
'title' => $title, |
| 526 |
'post_type' => get_post_type( $recent_post_id ), |
| 527 |
'edit_link' => get_edit_post_link( $recent_post_id ), |
| 528 |
)) + $recently_viewed; |
| 529 |
|
| 530 |
$recently_viewed = array_slice($recently_viewed, 0, 10, TRUE); |
| 531 |
|
| 532 |
update_user_meta( get_current_user_id(), '_propertyhive_recently_viewed', $recently_viewed ); |
| 533 |
} |
| 534 |
} |
| 535 |
|
| 536 |
public function admin_dashboard_pages() |
| 537 |
{ |
| 538 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This registers a read-only admin dashboard page and does not change state. |
| 539 |
$request_get = wp_unslash( $_GET ); |
| 540 |
$admin_page = isset( $request_get['page'] ) && is_string( $request_get['page'] ) ? sanitize_title( $request_get['page'] ) : ''; |
| 541 |
|
| 542 |
if ( '' !== $admin_page ) |
| 543 |
{ |
| 544 |
switch ( $admin_page ) |
| 545 |
{ |
| 546 |
case 'ph-installed': |
| 547 |
{ |
| 548 |
add_dashboard_page( |
| 549 |
__( 'Welcome to Property Hive', 'propertyhive' ), |
| 550 |
__( 'Welcome to Property Hive', 'propertyhive' ), |
| 551 |
'manage_propertyhive', |
| 552 |
$admin_page, |
| 553 |
array( $this, 'installed_screen' ) |
| 554 |
); |
| 555 |
|
| 556 |
break; |
| 557 |
} |
| 558 |
} |
| 559 |
} |
| 560 |
} |
| 561 |
|
| 562 |
public function installed_screen() |
| 563 |
{ |
| 564 |
?> |
| 565 |
<div class="wrap propertyhive-installed-screen"> |
| 566 |
|
| 567 |
<h1><?php echo esc_html(__( 'Welcome to Property Hive', 'propertyhive' )); ?></h1> |
| 568 |
|
| 569 |
<div class="intro-text"> |
| 570 |
<p>Thank you choosing Property Hive to power your next property website. Below you'll find useful links, tips on getting started, and more.</p> |
| 571 |
</div> |
| 572 |
|
| 573 |
<div class="panels"> |
| 574 |
|
| 575 |
<div class="panel"> |
| 576 |
|
| 577 |
<h2>Getting Started</h2> |
| 578 |
|
| 579 |
<p>Now that you've installed Property Hive you'll notice a new 'Property Hive' item in the left hand menu of WordPress.</p> |
| 580 |
|
| 581 |
<img src="<?php echo esc_url(PH()->plugin_url()); ?>/assets/images/admin/installed-screen/wordpress-menu.png" style="margin:0 auto; display:block; max-width:100%;" alt="Property Hive menu in WordPress"> |
| 582 |
|
| 583 |
<p><strong>Configure Property Hive:</strong> We recommend that you start by navigating to the '<a href="<?php echo esc_url(admin_url( 'admin.php?page=ph-settings' )); ?>" target="_blank">Settings</a>' area of Property Hive and configuring the options available.</p> |
| 584 |
|
| 585 |
<p><strong>Add Your First Property:</strong> See for yourself how easy it is to use Property Hive by <a href="<?php echo esc_url(admin_url( 'post-new.php?post_type=property' )); ?>" target="_blank">adding your first property</a>.</p> |
| 586 |
|
| 587 |
</div> |
| 588 |
|
| 589 |
<div class="panel"> |
| 590 |
|
| 591 |
<h2>Extending Property Hive</h2> |
| 592 |
|
| 593 |
<p>We have a <a href="https://wp-property-hive.com/add-ons/" target="_blank">wide range of add ons</a> available to add extra functionality to your website.</p> |
| 594 |
|
| 595 |
<a href="https://wp-property-hive.com/add-ons/" target="_blank"><img src="<?php echo esc_url(PH()->plugin_url()); ?>/assets/images/admin/installed-screen/add-ons.png" style="margin:0 auto; border:1px solid #CCC; display:block; max-width:100%;" alt="Property Hive Free Add Ons"></a> |
| 596 |
|
| 597 |
<p><strong style="font-size:14px;"><a href="https://wp-property-hive.com/add-ons/?category=free" target="_blank">Free Add Ons</a></strong><br> |
| 598 |
From our template assistant add on to a variety of calculators, these free add ons are great additions to any property website.</p> |
| 599 |
|
| 600 |
<p><strong style="font-size:14px;"><a href="https://wp-property-hive.com/add-ons/?category=enhancements" target="_blank">Website Enhancements</a></strong><br> |
| 601 |
Map View, Radial Search, Property Shortlist, Infinite Scroll, and lots more. Wow your users with the functionality provided with these add ons.</p> |
| 602 |
|
| 603 |
<p><strong style="font-size:14px;"><a href="https://wp-property-hive.com/add-ons/?category=tools" target="_blank">Internal Tools</a></strong><br> |
| 604 |
Add ons aimed to make your life easier and to save you time. Includes Digital Window Displays, Address Lookup and more.</p> |
| 605 |
|
| 606 |
<p><strong style="font-size:14px;"><a href="https://wp-property-hive.com/add-ons/?category=import" target="_blank">Import and Export</a></strong><br> |
| 607 |
Import properties from third party software or send your properties to portals like Rightmove, Zoopla and more. These add ons automate the import and export of property data.</p> |
| 608 |
|
| 609 |
</div> |
| 610 |
|
| 611 |
<div class="panel"> |
| 612 |
|
| 613 |
<h2>Support</h2> |
| 614 |
|
| 615 |
We pride ourselves on great support at Property Hive and will always do what we can to help you make create the best site possible. Please find below some useful links relating to our support: |
| 616 |
|
| 617 |
<p><strong style="font-size:14px;">Documentation</strong><br> |
| 618 |
We have documentation <a href="https://docs.wp-property-hive.com" target="_blank">available on our website</a> covering setup advice, help with theming, and more.</p> |
| 619 |
|
| 620 |
<p><strong style="font-size:14px;">Our Support Policy</strong><br> |
| 621 |
Our <a href="https://wp-property-hive.com/support-policy/" target="_blank">Support Policy is available to view here</a> and outlines how you can get in touch, how we will (and won't) help, and how to report bugs.</p> |
| 622 |
|
| 623 |
</div> |
| 624 |
|
| 625 |
<div class="panel"> |
| 626 |
|
| 627 |
<h2>Additional Information</h2> |
| 628 |
|
| 629 |
<p><strong style="font-size:14px;">Need a Theme?</strong><br> |
| 630 |
Property Hive does <a href="https://wp-property-hive.com/which-wordpress-themes-work-with-property-hive/" target="_blank">integrate with any new or existing theme</a>. If however you need to get up and running quickly, or just want to have a play before committing, then our free <a href="https://wp-property-hive.com/honeycomb" target="_blank">Honeycomb theme</a> might be right for you.</p> |
| 631 |
|
| 632 |
<a href="https://wp-property-hive.com/honeycomb" target="_blank"><img src="<?php echo esc_url(PH()->plugin_url()); ?>/assets/images/admin/installed-screen/honeycomb-screenshot.png" style="margin:0 auto; display:block; max-width:80%;" alt="Property Hive Free Honeycomb Theme"></a> |
| 633 |
|
| 634 |
<p><strong style="font-size:14px;">Leave a Review</strong><br> |
| 635 |
If you've found Property Hive useful we'd love it if you could spare a moment to tell others just how great we are by <a href="https://wordpress.org/support/plugin/propertyhive/reviews/" target="_blank">leaving a review</a>.</p> |
| 636 |
|
| 637 |
<p><strong style="font-size:14px;">Contribute</strong><br> |
| 638 |
Property Hive is completely open-source meaning anyone can access and contribute to the code. Fixing bugs and adding functionality can be done by anyone with coding knowledge. <a href="https://github.com/propertyhive/WP-Property-Hive" target="_blank">Visit us on GitHub</a> to get started.</p> |
| 639 |
|
| 640 |
<p><strong style="font-size:14px;">Our Feature Roadmap</strong><br> |
| 641 |
View our <a href="https://trello.com/b/jb7bjB6j/property-hive-roadmap" target="_blank">feature roadmap</a> to see what's coming up, vote on feature, or submit your own ideas.</p> |
| 642 |
|
| 643 |
|
| 644 |
</div> |
| 645 |
|
| 646 |
</div> |
| 647 |
|
| 648 |
</div> |
| 649 |
<?php |
| 650 |
} |
| 651 |
|
| 652 |
/** |
| 653 |
* Hide Individual Dashboard Pages |
| 654 |
* |
| 655 |
* @access public |
| 656 |
* @since 1.0 |
| 657 |
* @return void |
| 658 |
*/ |
| 659 |
public function admin_head() |
| 660 |
{ |
| 661 |
remove_submenu_page( 'index.php', 'ph-installed' ); |
| 662 |
} |
| 663 |
|
| 664 |
/** |
| 665 |
* Include any classes we need within admin. |
| 666 |
*/ |
| 667 |
public function includes() { |
| 668 |
// Functions |
| 669 |
include_once( 'ph-admin-functions.php' ); |
| 670 |
include_once( 'ph-meta-box-functions.php' ); |
| 671 |
|
| 672 |
// Classes |
| 673 |
include_once( 'class-ph-admin-post-types.php' ); |
| 674 |
include_once( dirname(PH_PLUGIN_FILE) . '/includes/class-ph-ai-service.php' ); |
| 675 |
|
| 676 |
// Classes we only need if the ajax is not-ajax |
| 677 |
if ( ! is_ajax() ) { |
| 678 |
include( 'class-ph-admin-menus.php' ); |
| 679 |
include( 'class-ph-admin-assets.php' ); |
| 680 |
|
| 681 |
// Help Tab |
| 682 |
if ( apply_filters( 'propertyhive_enable_admin_help_tab', true ) ) |
| 683 |
{ |
| 684 |
include_once( 'class-ph-admin-help.php' ); |
| 685 |
} |
| 686 |
} |
| 687 |
} |
| 688 |
|
| 689 |
/** |
| 690 |
* Include admin files conditionally. |
| 691 |
*/ |
| 692 |
public function conditional_includes() { |
| 693 |
if ( ! $screen = get_current_screen() ) { |
| 694 |
return; |
| 695 |
} |
| 696 |
|
| 697 |
switch ( $screen->id ) { |
| 698 |
case 'dashboard' : |
| 699 |
include( 'class-ph-admin-dashboard.php' ); |
| 700 |
break; |
| 701 |
case 'plugins' : |
| 702 |
include( 'class-ph-admin-plugin-updates.php' ); |
| 703 |
break; |
| 704 |
case 'users': |
| 705 |
case 'user': |
| 706 |
case 'profile': |
| 707 |
case 'user-edit': |
| 708 |
include( 'class-ph-admin-profile.php' ); |
| 709 |
break; |
| 710 |
} |
| 711 |
} |
| 712 |
|
| 713 |
/** |
| 714 |
* Include admin files conditionally |
| 715 |
*/ |
| 716 |
public function disable_propertyhive_meta_box_dragging() |
| 717 |
{ |
| 718 |
$screen = get_current_screen(); |
| 719 |
|
| 720 |
if ( in_array( $screen->id, ph_get_screen_ids() ) ) |
| 721 |
{ |
| 722 |
//wp_deregister_script('postbox'); |
| 723 |
} |
| 724 |
} |
| 725 |
|
| 726 |
/** |
| 727 |
* Remove PropertyHive meta boxes |
| 728 |
*/ |
| 729 |
public function remove_propertyhive_meta_boxes_from_screen_options() |
| 730 |
{ |
| 731 |
global $wp_meta_boxes; |
| 732 |
|
| 733 |
$screen = get_current_screen(); |
| 734 |
|
| 735 |
if ( in_array( $screen->id, array( 'property' ) ) ) |
| 736 |
{ |
| 737 |
//wp_deregister_script('postbox'); |
| 738 |
} |
| 739 |
} |
| 740 |
|
| 741 |
public function review_admin_notices() |
| 742 |
{ |
| 743 |
global $wpdb; |
| 744 |
|
| 745 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This method only renders read-only admin notices. |
| 746 |
$request_get = wp_unslash( $_GET ); |
| 747 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- This method only checks whether a settings POST is present to suppress a duplicate read-only notice; it does not process or save the value. |
| 748 |
$request_post = wp_unslash( $_POST ); |
| 749 |
$admin_page_present = isset( $request_get['page'] ); |
| 750 |
$admin_page = $admin_page_present && is_string( $request_get['page'] ) ? sanitize_title( $request_get['page'] ) : ''; |
| 751 |
$plugin_status_present = isset( $request_get['plugin_status'] ); |
| 752 |
$maps_api_key_submitted = isset( $request_post['propertyhive_google_maps_api_key'] ); |
| 753 |
|
| 754 |
if ( current_user_can( 'manage_options' ) ) |
| 755 |
{ |
| 756 |
$propertyhive_review_prompt_due_timestamp = get_option( 'propertyhive_review_prompt_due_timestamp', 0 ); |
| 757 |
if ( $propertyhive_review_prompt_due_timestamp != '' && $propertyhive_review_prompt_due_timestamp != 0 ) |
| 758 |
{ |
| 759 |
if ( $propertyhive_review_prompt_due_timestamp < time() ) |
| 760 |
{ |
| 761 |
echo "<div class=\"notice notice-info\" id=\"ph_notice_leave_review\"> |
| 762 |
<p> |
| 763 |
" . wp_kses_post( __( '<strong>Finding Property Hive useful?</strong> Please take a minute to <a href="https://wordpress.org/support/plugin/propertyhive/reviews/#new-post" target="_blank">leave us a review</a>', 'propertyhive' ) ) . " |
| 764 |
</p> |
| 765 |
<p> |
| 766 |
<a href=\"https://wordpress.org/support/plugin/propertyhive/reviews/#new-post\" target=\"_blank\" class=\"button-primary\">Leave a Review</a> |
| 767 |
<a href=\"\" class=\"button\" id=\"ph_dismiss_notice_leave_review\">No Thanks</a> |
| 768 |
</p> |
| 769 |
</div>"; |
| 770 |
} |
| 771 |
} |
| 772 |
|
| 773 |
if ( |
| 774 |
class_exists('Easy_Property_Listings') && |
| 775 |
! $plugin_status_present && |
| 776 |
get_option( 'epl_notice_dismissed', '' ) != 'yes' |
| 777 |
) |
| 778 |
{ |
| 779 |
echo "<div class=\"notice notice-error\" id=\"ph_notice_epl\"> |
| 780 |
<p> |
| 781 |
" . wp_kses_post( __( '<strong>It looks like you\'re also running Easy Property Listings.</strong> This will cause conflicts with Property Hive and should be deactivated.', 'propertyhive' ) ) . " |
| 782 |
</p> |
| 783 |
<p> |
| 784 |
<a href=\"". esc_url(admin_url('plugins.php?s=easy%20property%20listings&plugin_status=all')) . "\" class=\"button-primary\">Deactivate Easy Property Listings</a> |
| 785 |
<a href=\"\" class=\"button\" id=\"ph_dismiss_notice_epl\">Dismiss</a> |
| 786 |
</p> |
| 787 |
|
| 788 |
</div>"; |
| 789 |
} |
| 790 |
|
| 791 |
if ( |
| 792 |
!class_exists('PH_Demo_Data') && |
| 793 |
get_option( 'propertyhive_install_timestamp', '' ) >= 1618268400 && |
| 794 |
get_option( 'propertyhive_hide_demo_data_tab', '' ) != 'yes' && |
| 795 |
( |
| 796 |
! $admin_page_present |
| 797 |
|| |
| 798 |
( |
| 799 |
$admin_page_present && 'ph-installed' !== $admin_page && 'ph-settings' !== $admin_page |
| 800 |
) |
| 801 |
) |
| 802 |
) |
| 803 |
{ |
| 804 |
echo "<div class=\"notice notice-info\" id=\"ph_notice_demo_data\"> |
| 805 |
<p> |
| 806 |
" . wp_kses_post( __( '<strong>New To Property Hive?</strong> Did you know that you can quickly import demo data to get a feel for how Property Hive works?', 'propertyhive' ) ) . " |
| 807 |
</p> |
| 808 |
<p> |
| 809 |
<a href=\"". esc_url(admin_url('admin.php?page=ph-settings&tab=demo_data')) . "\" class=\"button-primary\">Import Demo Data</a> |
| 810 |
<a href=\"\" class=\"button\" id=\"ph_dismiss_notice_demo_data\">Dismiss</a> |
| 811 |
</p> |
| 812 |
|
| 813 |
</div>"; |
| 814 |
} |
| 815 |
|
| 816 |
if ( |
| 817 |
get_option('propertyhive_search_results_page_id', '') == '' && |
| 818 |
( |
| 819 |
! $admin_page_present |
| 820 |
|| |
| 821 |
( |
| 822 |
$admin_page_present && 'ph-installed' !== $admin_page && 'ph-settings' !== $admin_page |
| 823 |
) |
| 824 |
) && |
| 825 |
get_option( 'missing_search_results_notice_dismissed', '' ) != 'yes' |
| 826 |
) |
| 827 |
{ |
| 828 |
echo "<div class=\"notice notice-info\" id=\"ph_notice_missing_search_results\"> |
| 829 |
<p> |
| 830 |
" . esc_html__( 'We noticed that you haven\'t assigned a page to be your \'Search Results\' page yet. We recommend that you do this in order to display properties on your site.', 'propertyhive' ) . " |
| 831 |
</p> |
| 832 |
<p> |
| 833 |
<a href=\"". esc_url(admin_url('admin.php?page=ph-settings&tab=general')) . "\" class=\"button-primary\">" . esc_html(__( 'Go To Property Hive Settings', 'propertyhive' )) . "</a> |
| 834 |
<a href=\"\" class=\"button\" id=\"ph_dismiss_notice_missing_search_results\">" . esc_html(__( 'Dismiss', 'propertyhive' )) . "</a> |
| 835 |
</p> |
| 836 |
|
| 837 |
</div>"; |
| 838 |
} |
| 839 |
|
| 840 |
if ( |
| 841 |
get_option('propertyhive_maps_provider') !== 'osm' && |
| 842 |
get_option('propertyhive_maps_provider') !== 'mapbox' && |
| 843 |
get_option('propertyhive_google_maps_api_key', '') == '' && |
| 844 |
! $maps_api_key_submitted && |
| 845 |
( |
| 846 |
! $admin_page_present |
| 847 |
|| |
| 848 |
( |
| 849 |
$admin_page_present && 'ph-installed' !== $admin_page |
| 850 |
) |
| 851 |
) && |
| 852 |
get_option( 'missing_google_maps_api_key_notice_dismissed', '' ) != 'yes' |
| 853 |
) |
| 854 |
{ |
| 855 |
echo "<div class=\"notice notice-info\" id=\"ph_notice_missing_google_maps_api_key\"> |
| 856 |
<p> |
| 857 |
" . sprintf( |
| 858 |
/* translators: %s: URL to plugin settings page where the Google Maps API key can be entered */ |
| 859 |
wp_kses_post( __( 'We noticed that you haven\'t entered a Google Maps API key. If wishing to display a map on your website it\'s recommended that you <a href="https://developers.google.com/maps/documentation/javascript/get-api-key" target="_blank">create one</a> and <a href="%s">enter it</a>.', 'propertyhive' ) ), |
| 860 |
esc_url( admin_url('admin.php?page=ph-settings&tab=general§ion=map') ) |
| 861 |
) . " |
| 862 |
</p> |
| 863 |
<p> |
| 864 |
<a href=\"". esc_url(admin_url('admin.php?page=ph-settings&tab=general§ion=map')) . "\" class=\"button-primary\">" . esc_html(__( 'Enter Google Maps API Key', 'propertyhive' )) . "</a> |
| 865 |
<a href=\"\" class=\"button\" id=\"ph_dismiss_notice_missing_google_maps_api_key\">" . esc_html(__( 'Dismiss', 'propertyhive' )) . "</a> |
| 866 |
</p> |
| 867 |
|
| 868 |
</div>"; |
| 869 |
} |
| 870 |
|
| 871 |
if ( |
| 872 |
get_option('propertyhive_license_key', '') != '' && |
| 873 |
get_option( 'missing_invalid_expired_license_key_notice_dismissed', '' ) != 'yes' && |
| 874 |
( |
| 875 |
! $admin_page_present |
| 876 |
|| |
| 877 |
( |
| 878 |
$admin_page_present && 'ph-installed' !== $admin_page && 'ph-settings' !== $admin_page |
| 879 |
) |
| 880 |
) |
| 881 |
) |
| 882 |
{ |
| 883 |
$license = PH()->license->get_current_license(); |
| 884 |
$output = ''; |
| 885 |
|
| 886 |
if ( isset($license['active']) && $license['active'] != '1' ) |
| 887 |
{ |
| 888 |
$output = __( 'Your Property Hive license key is inactive.', 'propertyhive' ); |
| 889 |
} |
| 890 |
else |
| 891 |
{ |
| 892 |
|
| 893 |
} |
| 894 |
|
| 895 |
if ( $output != '' ) |
| 896 |
{ |
| 897 |
echo "<div class=\"notice notice-info\" id=\"ph_notice_invalid_expired_license_key\"> |
| 898 |
<p> |
| 899 |
" . esc_html($output) . " |
| 900 |
</p> |
| 901 |
<p> |
| 902 |
<a href=\"". esc_url(admin_url('admin.php?page=ph-settings&tab=licensekey')) . "\" class=\"button-primary\">" . esc_html(__( 'Go To License Key Settings', 'propertyhive' )) . "</a> |
| 903 |
<a href=\"\" class=\"button\" id=\"ph_dismiss_notice_invalid_expired_license_key\">" . esc_html(__( 'Dismiss', 'propertyhive' )) . "</a> |
| 904 |
</p> |
| 905 |
|
| 906 |
</div>"; |
| 907 |
} |
| 908 |
} |
| 909 |
|
| 910 |
$screen = get_current_screen(); |
| 911 |
if ( in_array( $screen->id, array( 'dashboard' ) ) ) |
| 912 |
{ |
| 913 |
// Email Cron Warning |
| 914 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The email queue is a custom plugin table; this read-only dashboard notice has no WordPress API equivalent. |
| 915 |
$queuedEmailsExist = (bool)$wpdb->get_var("SELECT 1 FROM " . $wpdb->prefix . "ph_email_log WHERE status = '' LIMIT 1"); |
| 916 |
$cronIsNextScheduled = wp_next_scheduled('propertyhive_process_email_log'); |
| 917 |
if ( $queuedEmailsExist && ( $cronIsNextScheduled === false || $cronIsNextScheduled < strtotime('24 hours ago') ) ) |
| 918 |
{ |
| 919 |
echo ' |
| 920 |
<div class="notice notice-error" id="ph_notice_email_cron_not_running"> |
| 921 |
<p>' . esc_html(__( 'The Property Hive email queue does not appear to be running', 'propertyhive' )) . ' |
| 922 |
</p> |
| 923 |
<p> |
| 924 |
<a href="'. esc_url(admin_url('admin.php?page=ph-settings&tab=email§ion=log&status=queued')) . '" class="button-primary">' . esc_html(__( 'Go To Email Queue', 'propertyhive' )) . '</a> |
| 925 |
</p> |
| 926 |
</div> |
| 927 |
'; |
| 928 |
} |
| 929 |
} |
| 930 |
} |
| 931 |
|
| 932 |
if ( isset( $request_get['propertyhive_contacts_merged'] ) ) |
| 933 |
{ |
| 934 |
echo ' |
| 935 |
<div class="notice notice-info"> |
| 936 |
<p>' . esc_html(__( 'Contacts merged successfully', 'propertyhive' )) . '</p> |
| 937 |
</div> |
| 938 |
'; |
| 939 |
} |
| 940 |
} |
| 941 |
|
| 942 |
/** |
| 943 |
* Handle redirects to welcome page after install. |
| 944 |
*/ |
| 945 |
public function admin_redirects() |
| 946 |
{ |
| 947 |
// Setup wizard redirect |
| 948 |
if ( get_transient( '_ph_activation_redirect' ) ) |
| 949 |
{ |
| 950 |
delete_transient( '_ph_activation_redirect' ); |
| 951 |
|
| 952 |
// Don't do redirect if part of multisite, doing batch-activate, or if no permission |
| 953 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change. |
| 954 |
if ( is_network_admin() || isset( $_GET['activate-multi'] ) || ! current_user_can( 'manage_propertyhive' ) ) { |
| 955 |
return; |
| 956 |
} |
| 957 |
|
| 958 |
wp_safe_redirect( admin_url( 'index.php?page=ph-installed' ) ); |
| 959 |
exit; |
| 960 |
} |
| 961 |
} |
| 962 |
|
| 963 |
public function prevent_access_to_admin() |
| 964 |
{ |
| 965 |
global $current_user; |
| 966 |
|
| 967 |
$user_roles = $current_user->roles; |
| 968 |
$user_role = array_shift($user_roles); |
| 969 |
|
| 970 |
// Check role, but also AJAX as request to admin-ajax.php will still need to be made |
| 971 |
if ( !defined( 'DOING_AJAX' ) && $user_role === 'property_hive_contact' ) |
| 972 |
{ |
| 973 |
wp_safe_redirect( home_url( '/' ) ); |
| 974 |
exit; |
| 975 |
} |
| 976 |
} |
| 977 |
|
| 978 |
/** |
| 979 |
* View previously sent email |
| 980 |
* |
| 981 |
* @return string |
| 982 |
*/ |
| 983 |
public function view_email() { |
| 984 |
|
| 985 |
global $wpdb; |
| 986 |
|
| 987 |
if ( isset( $_GET['view_propertyhive_email'] ) ) |
| 988 |
{ |
| 989 |
if ( ! current_user_can( 'manage_propertyhive' ) ) { |
| 990 |
wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) ); |
| 991 |
} |
| 992 |
if ( ! wp_verify_nonce( ( isset( $_REQUEST['_wpnonce'] ) && is_string( $_REQUEST['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '', 'view-email' ) ) |
| 993 |
{ |
| 994 |
wp_die( 'Security check' ); |
| 995 |
} |
| 996 |
|
| 997 |
if ( ! current_user_can( 'manage_propertyhive' ) ) |
| 998 |
{ |
| 999 |
wp_die( esc_html__( 'Insufficient permissions.', 'propertyhive' ) ); |
| 1000 |
} |
| 1001 |
|
| 1002 |
if ( isset( $_GET['email_id'] ) ) |
| 1003 |
{ |
| 1004 |
$email_id = is_string( $_GET['email_id'] ) ? absint( $_GET['email_id'] ) : 0; |
| 1005 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Email logs are stored in a custom plugin table and this is a single protected administrative lookup. |
| 1006 |
$email_log = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ph_email_log WHERE email_id = %d", $email_id ) ); |
| 1007 |
if ( null !== $email_log ) |
| 1008 |
{ |
| 1009 |
$body = $email_log->body; |
| 1010 |
|
| 1011 |
if ( extension_loaded('zlib') && @gzuncompress($body) !== false ) |
| 1012 |
{ |
| 1013 |
$body = gzuncompress($body); |
| 1014 |
} |
| 1015 |
|
| 1016 |
$message = apply_filters( 'propertyhive_mail_content', PH()->email->style_inline( PH()->email->wrap_message( $body ) ) ); |
| 1017 |
|
| 1018 |
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is the rendered HTML email viewer. The body was sanitized before entering the email log; propertyhive_mail_content and email templates are intentional trusted HTML extension points. |
| 1019 |
echo $message; |
| 1020 |
|
| 1021 |
} |
| 1022 |
else |
| 1023 |
{ |
| 1024 |
die("Email not found"); |
| 1025 |
} |
| 1026 |
} |
| 1027 |
|
| 1028 |
exit; |
| 1029 |
} |
| 1030 |
} |
| 1031 |
|
| 1032 |
/** |
| 1033 |
* Preview email template. |
| 1034 |
* |
| 1035 |
* @return string |
| 1036 |
*/ |
| 1037 |
public function preview_emails() { |
| 1038 |
if ( isset( $_GET['preview_propertyhive_email'] ) ) |
| 1039 |
{ |
| 1040 |
if ( ! current_user_can( 'manage_propertyhive' ) ) { |
| 1041 |
wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) ); |
| 1042 |
} |
| 1043 |
if ( ! wp_verify_nonce( ( isset( $_REQUEST['_wpnonce'] ) && is_string( $_REQUEST['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '', 'propertyhive-matching-properties' ) && ! wp_verify_nonce( ( isset( $_REQUEST['_wpnonce'] ) && is_string( $_REQUEST['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '', 'propertyhive-matching-applicants' ) ) |
| 1044 |
{ |
| 1045 |
die( 'Security check' ); |
| 1046 |
} |
| 1047 |
|
| 1048 |
$current_user = wp_get_current_user(); |
| 1049 |
$request_get = wp_unslash( $_GET ); |
| 1050 |
$request_post = wp_unslash( $_POST ); |
| 1051 |
|
| 1052 |
// get the preview email content |
| 1053 |
$email_property_ids = array(); |
| 1054 |
if ( isset( $request_get['property_id'] ) && is_scalar( $request_get['property_id'] ) ) |
| 1055 |
{ |
| 1056 |
$email_property_ids = array( absint( $request_get['property_id'] ) ); |
| 1057 |
} |
| 1058 |
elseif ( isset( $request_post['email_property_id'] ) && is_string( $request_post['email_property_id'] ) ) |
| 1059 |
{ |
| 1060 |
$email_property_ids = array_values( array_filter( array_map( 'absint', explode( ',', sanitize_text_field( $request_post['email_property_id'] ) ) ) ) ); |
| 1061 |
} |
| 1062 |
|
| 1063 |
$allowed_tags = array( |
| 1064 |
'strong' => array(), |
| 1065 |
'span' => array(), |
| 1066 |
'em' => array(), |
| 1067 |
'h1' => array(), |
| 1068 |
'h2' => array(), |
| 1069 |
'h3' => array(), |
| 1070 |
'h4' => array(), |
| 1071 |
'h5' => array(), |
| 1072 |
'h6' => array(), |
| 1073 |
'i' => array(), |
| 1074 |
'u' => array(), |
| 1075 |
'b' => array(), |
| 1076 |
'a' => array( |
| 1077 |
'href' => array(), |
| 1078 |
'target' => array(), |
| 1079 |
), |
| 1080 |
); |
| 1081 |
$allowed_tags = apply_filters( 'propertyhive_match_email_allowed_tags', $allowed_tags ); |
| 1082 |
|
| 1083 |
$raw_body = ( isset( $request_post['body'] ) && is_string( $request_post['body'] ) ) ? $request_post['body'] : ''; |
| 1084 |
$body = wp_kses( $raw_body, $allowed_tags ); |
| 1085 |
|
| 1086 |
if ( isset( $request_get['contact_id'] ) && is_scalar( $request_get['contact_id'] ) ) |
| 1087 |
{ |
| 1088 |
$contact = new PH_Contact( absint( $request_get['contact_id'] ) ); |
| 1089 |
$body = str_replace( '[contact_name]', esc_html( $contact->post_title ), $body ); |
| 1090 |
$body = str_replace( '[contact_dear]', esc_html( $contact->dear() ), $body ); |
| 1091 |
} |
| 1092 |
$body = str_replace( '[property_count]', count( $email_property_ids ) . ' propert' . ( ( count( $email_property_ids ) != 1 ) ? 'ies' : 'y' ), $body ); |
| 1093 |
|
| 1094 |
$office_counts = array(); |
| 1095 |
|
| 1096 |
if ( strpos($body, '[properties]') !== FALSE ) |
| 1097 |
{ |
| 1098 |
ob_start(); |
| 1099 |
|
| 1100 |
if ( !empty($email_property_ids) ) |
| 1101 |
{ |
| 1102 |
foreach ( $email_property_ids as $email_property_id ) |
| 1103 |
{ |
| 1104 |
$property = new PH_Property((int)$email_property_id); |
| 1105 |
|
| 1106 |
if ( $property->office_id != '' && $property->office_id != 0 ) |
| 1107 |
{ |
| 1108 |
if ( !isset($office_counts[$property->office_id]) ) { $office_counts[$property->office_id] = 0; } |
| 1109 |
++$office_counts[$property->office_id]; |
| 1110 |
} |
| 1111 |
|
| 1112 |
ph_get_template( 'emails/applicant-match-property.php', array( 'property' => $property ) ); |
| 1113 |
} |
| 1114 |
} |
| 1115 |
$body = str_replace("[properties]", ob_get_clean(), $body); |
| 1116 |
} |
| 1117 |
|
| 1118 |
$office_name = ''; |
| 1119 |
$office_email_address = ''; |
| 1120 |
|
| 1121 |
$office_id = get_user_meta($current_user->ID, 'office_id', TRUE); |
| 1122 |
if ($office_id == '') |
| 1123 |
{ |
| 1124 |
// No office against user. Use email address of office with most properties |
| 1125 |
if ( !empty($office_counts) ) |
| 1126 |
{ |
| 1127 |
arsort($office_counts); |
| 1128 |
reset($office_counts); |
| 1129 |
$office_id = key($office_counts); |
| 1130 |
} |
| 1131 |
} |
| 1132 |
|
| 1133 |
if ( !empty($office_id) ) |
| 1134 |
{ |
| 1135 |
$office_name = get_the_title( (int) $office_id ); |
| 1136 |
$office_email_address = get_post_meta( (int) $office_id, '_office_email_address_sales', TRUE ); |
| 1137 |
} |
| 1138 |
|
| 1139 |
$body = str_replace( '[office_name]', esc_html( $office_name ), $body ); |
| 1140 |
$body = str_replace( '[office_email_address]', esc_html( $office_email_address ), $body ); |
| 1141 |
|
| 1142 |
$body = str_replace( '[negotiator_name]', esc_html( $current_user->display_name ), $body ); |
| 1143 |
$body = str_replace( '[negotiator_email_address]', esc_html( $current_user->user_email ), $body ); |
| 1144 |
|
| 1145 |
// wrap the content with the email template and then add styles |
| 1146 |
$message = apply_filters( 'propertyhive_mail_content', PH()->email->style_inline( PH()->email->wrap_message( $body ) ) ); |
| 1147 |
|
| 1148 |
// print the preview email |
| 1149 |
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is the rendered HTML email preview. The request body was passed through the explicit match allowlist; templates and propertyhive_mail_content are intentional trusted HTML extension points. |
| 1150 |
echo $message; |
| 1151 |
exit; |
| 1152 |
} |
| 1153 |
} |
| 1154 |
} |
| 1155 |
|
| 1156 |
return new PH_Admin(); |
| 1157 |
|