PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
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 1.4.61 All 261 releases
propertyhive / includes / admin / post-types / class-ph-admin-cpt-property.php

class-ph-admin-cpt-property.php in Property Hive 2.3.1, at includes/admin/post-types/class-ph-admin-cpt-property.php

887 lines 30.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 /**
6 * Admin functions for the property post type
7 *
8 * @author PropertyHive
9 * @category Admin
10 * @package PropertyHive/Admin/Post Types
11 * @version 1.0.0
12 */
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly
16 }
17
18 if ( ! class_exists( 'PH_Admin_CPT' ) ) {
19 include( 'class-ph-admin-cpt.php' );
20 }
21
22 if ( ! class_exists( 'PH_Admin_CPT_Property' ) ) :
23
24 /**
25 * PH_Admin_CPT_Property Class
26 */
27 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_CPT_Property; preserving the existing PH_* class name is required for plugin and extension compatibility.
28 class PH_Admin_CPT_Property extends PH_Admin_CPT {
29
30 /**
31 * Constructor
32 */
33 public function __construct() {
34 $this->type = 'property';
35
36 // Admin notices
37 add_action( 'admin_notices', array( $this, 'property_admin_notices') );
38
39 // Post title fields
40 add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
41
42 // Featured image text
43 //add_filter( 'gettext', array( $this, 'featured_image_gettext' ) );
44 //add_filter( 'media_view_strings', array( $this, 'media_view_strings' ), 10, 2 );
45
46 // Visibility option
47 //add_action( 'post_submitbox_misc_actions', array( $this, 'property_data_visibility' ) );
48
49 // Before data updates
50 add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
51 add_filter( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ) );
52
53 // Admin Columns
54 add_filter( 'manage_edit-property_columns', array( $this, 'edit_columns' ) );
55 add_action( 'manage_property_posts_custom_column', array( $this, 'custom_columns' ), 2 );
56 add_filter( 'list_table_primary_column', array( $this, 'set_primary_column' ), 10, 2 );
57 add_filter( 'post_row_actions', array( $this, 'remove_actions' ), 10, 2 );
58 add_filter( 'manage_edit-property_sortable_columns', array( $this, 'custom_columns_sort' ) );
59 add_filter( 'request', array( $this, 'custom_columns_orderby' ) );
60 add_filter( 'display_post_states', array( $this, 'flag_search_results_page' ), 10, 2 );
61
62 // Sort link
63 add_filter( 'views_edit-property', array( $this, 'remove_mine' ) );
64
65 // Maintain hierarchy of terms
66 /*add_filter( 'wp_terms_checklist_args', array( $this, 'disable_checked_ontop' ) );*/
67
68 // Bulk / quick edit
69 add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
70 /*add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 );*/
71 add_action( 'save_post', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 );
72
73 // Uploads
74 add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
75 add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) );
76 //add_filter( 'mod_rewrite_rules', array( $this, 'ms_protect_download_rewite_rules' ) );
77
78 // Download permissions
79 //add_action( 'propertyhive_process_product_file_download_paths', array( $this, 'process_product_file_download_paths' ), 10, 3 );*/
80
81 add_action( 'load-edit.php', array( $this, 'render_blank' ) );
82
83 // Call PH_Admin_CPT constructor
84 parent::__construct();
85 }
86
87 public function flag_search_results_page( $post_states, $post )
88 {
89 // Get the page ID set in your option
90 $search_results_page_id = get_option( 'propertyhive_search_results_page_id' );
91
92 // Check if this is that page
93 if ( $post->ID == $search_results_page_id )
94 {
95 $post_states['property_search_results'] = __( 'Property Search Results', 'propertyhive' );
96 }
97
98 return $post_states;
99 }
100
101 public function render_blank()
102 {
103 // Check if we are on the 'property' post type listing page
104 $screen = get_current_screen();
105 if ($screen->post_type !== 'property') {
106 return;
107 }
108
109 // Check if there are any properties
110 $query = new WP_Query([
111 'post_type' => 'property',
112 'post_status' => ['any', 'trash'],
113 'posts_per_page' => 1
114 ]);
115
116 if ($query->have_posts()) {
117 return; // Let the default table load
118 }
119
120 // No properties found, replace the table with a custom splash screen
121 add_filter('views_edit-property', function ($views) {
122 // Clear default view filters (All, Published, etc.)
123 return [];
124 });
125
126 add_action('manage_posts_extra_tablenav', function ($which) {
127 if ( $which == 'bottom' )
128 {
129 return;
130 }
131 echo '
132 <div style="padding: 50px; text-align: center;">
133
134 <img style="max-width:400px; margin-bottom:45px;" src="' . esc_url(PH()->plugin_url()) . '/assets/images/no-properties.png" alt="' . esc_attr( __( 'Your property journey begins here!', 'propertyhive' ) ) . '">
135
136 <h2 style="font-size:1.8em; color:#444; margin:0 0 1.5em">' . esc_html( __( 'Your property journey begins here!', 'propertyhive' ) ) . '</h2>
137 <a href="' . esc_url(admin_url('post-new.php?post_type=property&tutorial=yes')) . '" class="button button-primary button-hero" style="font-size:1.2em; padding:0 24px;">
138 ' . esc_html( __( 'Add Your First Property', 'propertyhive' ) ) . '
139 </a>&nbsp;
140 <a href="' . esc_url(admin_url('admin.php?page=ph-settings&tab=demo_data')) . '" class="button button-hero" style="font-size:1.2em; padding:0 24px;">
141 ' . esc_html( __( 'Create Demo Data', 'propertyhive' ) ) . '
142 </a>&nbsp; ';
143
144 if ( apply_filters( 'propertyhive_no_properties_property_import_button', true ) === true )
145 {
146 $button_to_output = false;
147
148 if ( class_exists('PH_Property_Import') )
149 {
150 // Already activated. Check can be used
151 if ( apply_filters( 'propertyhive_add_on_can_be_used', true, 'propertyhive-property-import' ) === true )
152 {
153 $button_to_output = 'normal';
154 }
155 }
156
157 if ( !$button_to_output )
158 {
159 $license_type = get_option( 'propertyhive_license_type', '' );
160
161 switch ( $license_type )
162 {
163 case "": { $button_to_output = 'dummy'; break; }
164 case "pro":
165 {
166 if ( PH()->license->is_valid_pro_license_key() )
167 {
168 // It should never get this far if import add on already activated, that's why show activate page
169 $button_to_output = 'activate';
170 }
171 else
172 {
173 $button_to_output = 'dummy';
174 }
175 break;
176 }
177 }
178 }
179
180 // only show dummy button to administrators
181 if ( $button_to_output == 'dummy' || $button_to_output == 'activate' )
182 {
183 if ( !current_user_can( 'manage_options' ) )
184 {
185 // not an admin
186 $button_to_output = false;
187 }
188 }
189
190 // only show dummy button to people with it installed eyond 1st nov 2023 (when PRO was introduced)
191 if ( $button_to_output == 'dummy' )
192 {
193 $propertyhive_install_timestamp = get_option( 'propertyhive_install_timestamp', '' );
194 if ( !empty($propertyhive_install_timestamp) )
195 {
196 $november_first_2023 = strtotime('2023-11-01 00:00:00');
197 if ( $propertyhive_install_timestamp < $november_first_2023 )
198 {
199 $button_to_output = false;
200 }
201 }
202 }
203
204 switch ( $button_to_output )
205 {
206 case "normal":
207 {
208 echo '<a href="' . esc_url(admin_url('admin.php?page=propertyhive_import_properties')) . '" class="button button-hero" style="font-size:1.2em; padding:0 24px;">
209 ' . esc_html( __( 'Automatically Import Properties', 'propertyhive' ) ) . '
210 </a>';
211 break;
212 }
213 case "dummy":
214 {
215 echo '<a href="' . esc_url(admin_url('admin.php?page=ph-import_properties_dummy')) . '" class="button button-hero" style="font-size:1.2em; padding:0 24px;">
216 ' . esc_html( __( 'Automatically Import Properties', 'propertyhive' ) ) . ' <span style="color:#FFF; font-size:10px; font-weight:500; border-radius:12px; padding:2px 8px; letter-spacing:1px; background:#00a32a;">PRO</span>
217 </a>';
218 break;
219 }
220 case "activate":
221 {
222 echo '<a href="' . esc_url(admin_url('admin.php?page=ph-settings&tab=features&profilter=import')) . '" class="button button-hero" style="font-size:1.2em; padding:0 24px;">
223 ' . esc_html( __( 'Activate Property Imports', 'propertyhive' ) ) . '
224 </a>';
225 break;
226 }
227 }
228 }
229
230 echo '</div>';
231 }, 99);
232
233 // Remove the filters
234 remove_all_actions('restrict_manage_posts');
235
236 // Hide the search box
237 add_action('admin_head', function () {
238 echo '<style>
239 .page-title-action,
240 .wrap .wp-list-table,
241 .search-box { display: none !important; }
242 </style>';
243 });
244
245 add_filter('bulk_actions-edit-property', function ($bulk_actions) {
246 // Clear all bulk actions
247 return [];
248 });
249 }
250
251 /**
252 * Output admin notices relating to property
253 */
254 public function property_admin_notices()
255 {
256 global $post;
257
258 $screen = get_current_screen();
259 if ($screen && $post instanceof WP_Post && $screen->id == 'property' && $post->post_type == 'property' && $post->post_parent != 0 && $post->post_parent != '')
260 {
261 $property = new PH_Property((int)$post->post_parent);
262 $message = __( "This property is a unit belonging to", 'propertyhive' ) . ' <a href="' . esc_url(get_edit_post_link( $post->post_parent )) . '">' . esc_html($property->get_formatted_full_address()) . '</a>';
263 echo '<div class="notice notice-info"><p>' . wp_kses_post( $message ) . '</p></div>';
264 }
265 }
266
267 /**
268 * Check if we're editing or adding a property
269 * @return boolean
270 */
271 private function is_editing_property() {
272 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen detection; mutations have separate save guards.
273 $post_type = isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '';
274 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen detection.
275 $post_id = isset( $_GET['post'] ) && is_string( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
276 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen detection for AJAX requests.
277 $request_id = isset( $_REQUEST['post_id'] ) && is_string( $_REQUEST['post_id'] ) ? absint( $_REQUEST['post_id'] ) : 0;
278 return 'property' === $post_type || ( $post_id > 0 && 'property' === get_post_type( $post_id ) ) || ( $request_id > 0 && 'property' === get_post_type( $request_id ) );
279 }
280
281 /**
282 * Change title boxes in admin.
283 * @param string $text
284 * @param object $post
285 * @return string
286 */
287 public function enter_title_here( $text, $post ) {
288 if ( is_admin() && $post->post_type == 'property' ) {
289 return __( 'Enter Display Address', 'propertyhive' );
290 }
291
292 return $text;
293 }
294
295 /**
296 * Some functions, like the term recount, require the visibility to be set prior. Lets save that here.
297 *
298 * @param int $post_id
299 */
300 public function pre_post_update( $post_id ) {
301
302 }
303
304 /**
305 * Forces certain product data based on the product's type, e.g. grouped products cannot have a parent.
306 *
307 * @param array $data
308 * @return array
309 */
310 public function wp_insert_post_data( $data ) {
311
312
313 return $data;
314 }
315
316 /**
317 * Change the columns shown in admin.
318 */
319 public function edit_columns( $existing_columns ) {
320
321 if ( empty( $existing_columns ) && ! is_array( $existing_columns ) ) {
322 $existing_columns = array();
323 }
324
325 unset( $existing_columns['title'], $existing_columns['comments'], $existing_columns['date'] );
326
327 $columns = array();
328 $columns['cb'] = '<input type="checkbox" />';
329 $columns['thumb'] = '<span class="ph-image tips" data-tip="' . __( 'Image', 'propertyhive' ) . '">' . __( 'Image', 'propertyhive' ) . '</span>';
330
331 $columns['address'] = __( 'Address', 'propertyhive' );
332
333 $commercial_department_active = false;
334 if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
335 {
336 $commercial_department_active = true;
337 }
338 else
339 {
340 $custom_departments = ph_get_custom_departments();
341 if ( !empty($custom_departments) )
342 {
343 foreach ( $custom_departments as $key => $department )
344 {
345 if ( isset($department['based_on']) && $department['based_on'] == 'commercial' )
346 {
347 $commercial_department_active = true;
348 }
349 }
350 }
351 }
352 if ( $commercial_department_active )
353 {
354 $columns['size'] = __( 'Size', 'propertyhive' );
355 }
356
357 $columns['price'] = __( 'Price', 'propertyhive' );
358
359 $columns['status'] = __( 'Marketing Status', 'propertyhive' );
360
361 if ( get_option('propertyhive_module_disabled_contacts', '') != 'yes' )
362 {
363 $columns['owner'] = __( 'Owner / Landlord', 'propertyhive' );
364 }
365
366 $columns['negotiator_office'] = __( 'Neg / Office', 'propertyhive' );
367
368 return array_merge( $columns, $existing_columns );
369 }
370
371 /**
372 * Define our custom columns shown in admin.
373 * @param string $column
374 */
375 public function custom_columns( $column ) {
376 global $post, $propertyhive, $the_property;
377
378 if ( empty( $the_property ) || $the_property->ID != $post->ID )
379 {
380 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Retain the legacy global name for compatibility with external admin column callbacks.
381 $the_property = new PH_Property( $post->ID );
382 }
383
384 switch ( $column ) {
385 case 'thumb' :
386
387 $thumb_src = $the_property->get_main_photo_src();
388
389 echo '<a href="' . esc_url(get_edit_post_link( $post->ID )) . '">';
390 if ($thumb_src !== FALSE)
391 {
392 echo '<img src="' . esc_url($thumb_src) . '" alt="" width="50">';
393 }
394 else
395 {
396 // placeholder image
397 }
398 echo '</a>';
399 break;
400 case 'address' :
401
402 $edit_link = get_edit_post_link( $post->ID );
403 //$title = _draft_or_post_title();
404 $title = $the_property->get_formatted_summary_address();
405 if ( empty($title) )
406 {
407 $title = '(' . __( 'no address entered', 'propertyhive' ) . ')';
408 }
409 $post_type_object = get_post_type_object( $post->post_type );
410 $can_edit_post = current_user_can( $post_type_object->cap->edit_post, $post->ID );
411
412 echo '<strong><a class="row-title" href="' . esc_url( $edit_link ) .'">' . esc_html($title) . '</a>';
413
414 $post_status = get_post_status( $post->ID );
415 $post_title_output = '';
416 if ( $post_status == 'draft' || $post_status == 'private' )
417 {
418 $post_title_output = ucfirst($post_status);
419 }
420 $post_title_output = apply_filters( 'propertyhive_admin_property_column_post_address_output', $post_title_output );
421 if ( $post_title_output != '' )
422 {
423 echo ' - ' . esc_html($post_title_output);
424 }
425
426 echo '</strong>';
427
428 // Excerpt view
429 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
430 if ( isset( $_GET['mode'] ) && 'excerpt' == $_GET['mode'] ) {
431 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped -- WordPress core the_excerpt hook must keep its name; stored excerpt is sanitized before trusted PHP presentation filters.
432 echo apply_filters( 'the_excerpt', wp_kses_post( $post->post_excerpt ) );
433 }
434
435 if ( $the_property->bedrooms != '' || $the_property->property_type != '' || $the_property->reference_number != '' )
436 {
437 echo '<br>';
438
439 $details = array();
440
441 if ( $the_property->bedrooms != '' || $the_property->property_type != '' )
442 {
443 $details[] = (
444 (
445 (
446 $the_property->department == 'residential-sales' ||
447 $the_property->department == 'residential-lettings' ||
448 ph_get_custom_department_based_on($the_property->department) == 'residential-sales' ||
449 ph_get_custom_department_based_on($the_property->department) == 'residential-lettings'
450 )
451 &&
452 $the_property->bedrooms != ''
453 ) ? esc_html($the_property->bedrooms . ' ' . __( 'bedroom', 'propertyhive' )) . ' ' : ''
454 ) . esc_html($the_property->property_type);
455 }
456
457 if ( $the_property->reference_number )
458 {
459 $details[] = '<span style="opacity:0.6">' . esc_html(__( 'Ref', 'propertyhive' )) . ': ' . esc_html($the_property->reference_number) . '</span>';
460 }
461
462 $details = apply_filters( 'propertyhive_admin_property_column_address_details', $details, $post->ID );
463
464 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Base detail values are escaped above before the trusted PHP HTML extension filter.
465 echo implode("<br>", $details);
466 }
467
468 get_inline_data( $post );
469
470 /* Custom inline data for propertyhive */
471 /*echo '
472 <div class="hidden" id="propertyhive_inline_' . $post->ID . '">
473 <div class="on_market">' . $the_property->on_market . '</div>
474 <div class="featured">' . $the_property->featured . '</div>
475 </div>
476 ';*/
477
478 break;
479 case 'size' :
480
481 $floor_area = $the_property->get_formatted_floor_area();
482 if ( $floor_area != '' )
483 {
484 echo 'Floor Area: ' . wp_kses_post( $floor_area ) . '<br>';
485 }
486 $site_area = $the_property->get_formatted_site_area();
487 if ( $site_area != '' )
488 {
489 echo 'Site Area: ' . wp_kses_post( $site_area );
490 }
491
492 if ( $floor_area == '' && $site_area == '' )
493 {
494 echo '-';
495 }
496
497 break;
498 case 'price' :
499
500 $price = $the_property->get_formatted_price();
501 if ( $price == '' )
502 {
503 $price = '-';
504 }
505 else
506 {
507 if (
508 ( $the_property->_department == 'residential-sales' || ph_get_custom_department_based_on($the_property->_department) == 'residential-sales' )
509 &&
510 $the_property->price_qualifier != ''
511 )
512 {
513 $price .= '<br>' . esc_html($the_property->price_qualifier);
514 }
515 }
516 echo wp_kses_post( $price );
517
518 break;
519 case 'status' :
520
521 $term_list = wp_get_post_terms($post->ID, 'availability', array("fields" => "names"));
522
523 if ( !is_wp_error($term_list) && is_array($term_list) && !empty($term_list) )
524 {
525 echo esc_html($term_list[0]). '<br>';
526 }
527
528 if (isset($the_property->_on_market) && $the_property->_on_market == 'yes')
529 {
530 echo esc_html(__( 'On The Market', 'propertyhive' ));
531 }
532 else
533 {
534 echo esc_html(__( 'Not On The Market', 'propertyhive' ));
535 }
536
537 if (isset($the_property->_featured) && $the_property->_featured == 'yes')
538 {
539 echo '<br>' . esc_html(__( 'Featured', 'propertyhive' ));
540 }
541
542 $marketing_flags = $the_property->marketing_flag;
543 if ( $marketing_flags != '' )
544 {
545 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Marketing text is escaped before adding literal br separators.
546 echo '<br>' . implode( "<br>", explode( ",", esc_html($marketing_flags) ) );
547 }
548
549 break;
550 case 'owner' :
551
552 $owner_contact_ids = $the_property->_owner_contact_id;
553 if (
554 ( !is_array($owner_contact_ids) && $owner_contact_ids != '' && $owner_contact_ids != 0 )
555 ||
556 ( is_array($owner_contact_ids) && !empty($owner_contact_ids) )
557 )
558 {
559 if ( !is_array($owner_contact_ids) )
560 {
561 $owner_contact_ids = array($owner_contact_ids);
562 }
563
564 foreach ( $owner_contact_ids as $owner_contact_id )
565 {
566 echo esc_html(get_the_title($owner_contact_id)) . '<br>';
567 if ( count($owner_contact_ids) == 1 )
568 {
569 echo '<div class="row-actions">';
570 echo 'T: ' . esc_html(get_post_meta($owner_contact_id, '_telephone_number', TRUE)) . '<br>';
571 echo 'E: ' . esc_html(get_post_meta($owner_contact_id, '_email_address', TRUE));
572 echo '</div>';
573 }
574 }
575 }
576 else
577 {
578 echo '-';
579 }
580 break;
581 case 'negotiator_office' :
582
583 $user_info = get_userdata($the_property->_negotiator_id);
584
585 if ($user_info !== FALSE)
586 {
587 echo esc_html($user_info->display_name) . '<br>';
588 }
589
590 if ($the_property->_office_id != '')
591 {
592 echo esc_html(get_the_title($the_property->_office_id));
593 }
594
595 break;
596 default :
597 break;
598 }
599 }
600
601 public function set_primary_column( $default, $screen ) {
602
603 if ( 'edit-property' === $screen )
604 {
605 $default = 'address';
606 }
607
608 return $default;
609 }
610
611 public function remove_actions($actions, $post) {
612
613 if ( $post->post_type !== 'property' )
614 {
615 return $actions;
616 }
617
618 // Remove 'Quick Edit' link
619 if ( isset($actions['inline hide-if-no-js']) )
620 {
621 unset($actions['inline hide-if-no-js']);
622 }
623
624 if ( isset($actions['trash']) )
625 {
626 unset($actions['trash']);
627 }
628
629 return $actions;
630 }
631
632 /**
633 * Make property columns sortable
634 *
635 * https://gist.github.com/906872
636 *
637 * @access public
638 * @param mixed $columns
639 * @return array
640 */
641 public function custom_columns_sort( $columns ) {
642 $custom = array(
643 'price' => '_price_actual',
644 'size' => '_floor_area_from_sqft'
645 );
646 return wp_parse_args( $custom, $columns );
647 }
648
649 /**
650 * Property column orderby
651 *
652 * @access public
653 * @param mixed $vars
654 * @return array
655 */
656 public function custom_columns_orderby( $vars ) {
657 if ( isset( $vars['orderby'] ) ) {
658 if ( '_price_actual' == $vars['orderby'] ) {
659 $vars = array_merge( $vars, array(
660 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- All these meta_key values are literal, supported CPT date/status/price keys used by paginated WordPress admin list ordering. Core admin post queries provide pagination; no arbitrary request key is copied into these lines.
661 'meta_key' => '_price_actual',
662 'orderby' => 'meta_value_num'
663 ) );
664 }
665 elseif ( '_floor_area_from_sqft' == $vars['orderby'] ) {
666 $vars = array_merge( $vars, array(
667 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- All these meta_key values are literal, supported CPT date/status/price keys used by paginated WordPress admin list ordering. Core admin post queries provide pagination; no arbitrary request key is copied into these lines.
668 'meta_key' => '_floor_area_from_sqft',
669 'orderby' => 'meta_value_num'
670 ) );
671 }
672 }
673
674 return $vars;
675 }
676
677 /**
678 * Remove 'Mine' view option
679 *
680 * @param array $views
681 * @return array
682 */
683 public function remove_mine( $views ) {
684 global $post_type, $wp_query;
685
686 if ( ! current_user_can('edit_others_pages') ) {
687 return $views;
688 }
689
690 if( isset( $views['mine'] ) )
691 unset( $views['mine'] );
692
693 return $views;
694 }
695
696 /**
697 * Maintain term hierarchy when editing a property.
698 * @param array $args
699 * @return array
700 */
701 public function disable_checked_ontop( $args ) {
702 if ( 'product_cat' == $args['taxonomy'] ) {
703 $args['checked_ontop'] = false;
704 }
705
706 return $args;
707 }
708
709 /**
710 * Custom bulk edit - form
711 *
712 * @access public
713 * @param mixed $column_name
714 * @param mixed $post_type
715 */
716 public function bulk_edit( $column_name, $post_type ) {
717 if ( 'price' != $column_name || 'property' != $post_type ) {
718 return;
719 }
720
721 include( PH()->plugin_path() . '/includes/admin/views/html-bulk-edit-property.php' );
722 }
723
724 /**
725 * Custom quick edit - form
726 *
727 * @access public
728 * @param mixed $column_name
729 * @param mixed $post_type
730 */
731 public function quick_edit( $column_name, $post_type ) {
732 if ( 'price' != $column_name || 'property' != $post_type ) {
733 return;
734 }
735
736 include( PH()->plugin_path() . '/includes/admin/views/html-quick-edit-property.php' );
737 }
738
739 /**
740 * Quick and bulk edit saving
741 *
742 * @access public
743 * @param int $post_id
744 * @param WP_Post $post
745 * @return int
746 */
747 public function bulk_and_quick_edit_save_post( $post_id, $post ) {
748 // If this is an autosave, our form has not been submitted, so we don't want to do anything.
749 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
750 return $post_id;
751 }
752
753 // Don't save revisions and autosaves
754 if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
755 return $post_id;
756 }
757
758 // Check post type is product
759 if ( 'property' != $post->post_type ) {
760 return $post_id;
761 }
762
763 // Check user permission
764 if ( ! current_user_can( 'edit_post', $post_id ) ) {
765 return $post_id;
766 }
767
768 // Check nonces
769 if ( ! isset( $_REQUEST['propertyhive_quick_edit_nonce'] ) && ! isset( $_REQUEST['propertyhive_bulk_edit_nonce'] ) ) {
770 return $post_id;
771 }
772 if ( isset( $_REQUEST['propertyhive_quick_edit_nonce'] ) && ! wp_verify_nonce( ( isset( $_REQUEST['propertyhive_quick_edit_nonce'] ) && is_string( $_REQUEST['propertyhive_quick_edit_nonce'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['propertyhive_quick_edit_nonce'] ) ) : '', 'propertyhive_quick_edit_nonce' ) ) {
773 return $post_id;
774 }
775 if ( isset( $_REQUEST['propertyhive_bulk_edit_nonce'] ) && ! wp_verify_nonce( ( isset( $_REQUEST['propertyhive_bulk_edit_nonce'] ) && is_string( $_REQUEST['propertyhive_bulk_edit_nonce'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['propertyhive_bulk_edit_nonce'] ) ) : '', 'propertyhive_bulk_edit_nonce' ) ) {
776 return $post_id;
777 }
778
779 // Get the product and save
780 $property = get_property( $post );
781
782 if ( ! empty( $_REQUEST['propertyhive_quick_edit'] ) ) {
783 $this->quick_edit_save( $post_id, $property );
784 } else {
785 $this->bulk_edit_save( $post_id, $property );
786 }
787
788 // Clear transient
789 //ph_delete_property_transients( $post_id );
790
791 return $post_id;
792 }
793
794 /**
795 * Quick edit
796 */
797 private function quick_edit_save( $post_id, $property ) {
798 global $wpdb;
799
800 /*
801 // Save fields
802 if ( isset( $_REQUEST['_address_name_number'] ) ) {
803 update_post_meta( $post_id, '_address_name_number', ph_clean( $_REQUEST['_address_name_number'] ) );
804 }*/
805
806 do_action( 'propertyhive_property_quick_edit_save', $property );
807 }
808
809 /**
810 * Bulk edit
811 */
812 public function bulk_edit_save( $post_id, $property ) {
813 if ( ! current_user_can( 'edit_post', $post_id ) || 'property' !== get_post_type( $post_id ) ) { return; }
814 if ( ! isset( $_REQUEST['propertyhive_bulk_edit_nonce'] ) || ! is_string( $_REQUEST['propertyhive_bulk_edit_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['propertyhive_bulk_edit_nonce'] ) ), 'propertyhive_bulk_edit_nonce' ) ) { return; }
815 $request_input = wp_unslash( $_REQUEST );
816 $input = array();
817 foreach ( array( '_on_market', '_featured', '_availability', '_negotiator_id', '_office_id' ) as $key )
818 {
819 if ( isset( $request_input[ $key ] ) && ! is_string( $request_input[ $key ] ) ) { return; }
820 $input[ $key ] = isset( $request_input[ $key ] ) ? sanitize_text_field( $request_input[ $key ] ) : '';
821 }
822
823
824 // Save fields
825 if ( ! empty( $input['_on_market'] ) )
826 {
827 $on_market = ph_clean( $input['_on_market'] );
828 if ( $input['_on_market'] != 'yes' ) { $on_market = ''; } // can only be 'yes' or blank
829 update_post_meta( $post_id, '_on_market', ph_clean( $on_market ) );
830 }
831
832 if ( ! empty( $input['_featured'] ) )
833 {
834 $featured = ph_clean( $input['_featured'] );
835 if ( $input['_featured'] != 'yes' ) { $featured = ''; } // can only be 'yes' or blank
836 update_post_meta( $post_id, '_featured', ph_clean( $featured ) );
837 }
838
839 if ( ! empty( $input['_availability'] ) && is_numeric( $input['_availability'] ) )
840 {
841 wp_set_post_terms( $post_id, (int)$input['_availability'], 'availability' );
842 }
843
844 if ( ! empty( $input['_negotiator_id'] ) && is_numeric( $input['_negotiator_id'] ) && $input['_negotiator_id'] != '-1' )
845 {
846 update_post_meta( $post_id, '_negotiator_id', (int)$input['_negotiator_id'] );
847 }
848
849 if ( ! empty( $input['_office_id'] ) && is_numeric( $input['_office_id'] ) )
850 {
851 update_post_meta( $post_id, '_office_id', (int)$input['_office_id'] );
852 }
853
854 do_action( 'propertyhive_property_bulk_edit_save', $property );
855 }
856
857 /**
858 * Filter the directory for uploads.
859 *
860 * @param array $pathdata
861 * @return array
862 */
863 public function upload_dir( $pathdata ) {
864 // Change upload dir for downloadable files
865 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Upload API validates the upload request; this filter only chooses a fixed subdirectory and never accepts a path.
866 if ( isset( $_POST['type'] ) && is_string( $_POST['type'] ) && 'downloadable_product' === $_POST['type'] ) {
867 if ( empty( $pathdata['subdir'] ) ) {
868 $pathdata['path'] = $pathdata['path'] . '/propertyhive_uploads';
869 $pathdata['url'] = $pathdata['url']. '/propertyhive_uploads';
870 $pathdata['subdir'] = '/propertyhive_uploads';
871 } else {
872 $new_subdir = '/propertyhive_uploads' . $pathdata['subdir'];
873
874 $pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
875 $pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
876 $pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
877 }
878 }
879
880 return $pathdata;
881 }
882 }
883
884 endif;
885
886 return new PH_Admin_CPT_Property();
887