| @@ -13,14 +13,52 @@ | ||
| 13 | 13 | * @package PropertyHive/Classes |
| 14 | 14 | * @category Class |
| 15 | 15 | * @author PropertyHive |
| 16 | 16 | */ |
| 17 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public PH_Post_types class; preserve its existing name for plugin and extension compatibility. | |
| 17 | 18 | class PH_Post_types { |
| 18 | 19 | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * Keep native CRM screens separate from ordinary WordPress post access. | |
| 23 | + * Existing post permissions remain required, alongside Property Hive access. | |
| 24 | + * | |
| 25 | + * @return array Dedicated primitive capabilities. | |
| 26 | + */ | |
| 27 | + public static function record_capabilities() { | |
| 28 | + $capabilities = array(); | |
| 29 | + foreach ( array( 'edit_posts', 'create_posts', 'edit_others_posts', 'edit_private_posts', 'edit_published_posts', 'publish_posts', 'delete_posts', 'delete_others_posts', 'delete_private_posts', 'delete_published_posts', 'read_private_posts' ) as $capability ) { | |
| 30 | + $capabilities[$capability] = 'propertyhive_' . $capability; | |
| 31 | + } | |
| 32 | + return $capabilities; | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * Resolve both direct checks and capabilities produced by core's post mapper. | |
| 37 | + * This preserves custom roles' existing edit/publish/delete restrictions. | |
| 38 | + * | |
| 39 | + * @param array $caps Required primitive capabilities. | |
| 40 | + * @return array Required capabilities. | |
| 41 | + */ | |
| 42 | + public static function map_record_capabilities( $caps ) { | |
| 43 | + $mapped = array(); | |
| 44 | + $record_caps = array_flip( self::record_capabilities() ); | |
| 45 | + foreach ( $caps as $capability ) { | |
| 46 | + if ( isset( $record_caps[$capability] ) ) { | |
| 47 | + $mapped[] = 'manage_propertyhive'; | |
| 48 | + $mapped[] = 'create_posts' === $record_caps[$capability] ? 'edit_posts' : $record_caps[$capability]; | |
| 49 | + } else { | |
| 50 | + $mapped[] = $capability; | |
| 51 | + } | |
| 52 | + } | |
| 53 | + return array_values( array_unique( $mapped ) ); | |
| 54 | + } | |
| 55 | + | |
| 19 | 56 | /** |
| 20 | 57 | * Constructor |
| 21 | 58 | */ |
| 22 | 59 | public function __construct() { |
| 60 | + add_filter( 'map_meta_cap', array( __CLASS__, 'map_record_capabilities' ) ); | |
| 23 | 61 | add_action( 'init', array( __CLASS__, 'register_taxonomies' ), 5 ); |
| 24 | 62 | add_action( 'init', array( __CLASS__, 'register_post_types' ), 5 ); |
| 25 | 63 | add_action( 'init', array( __CLASS__, 'register_post_statuses' ), 5 ); |
| 26 | 64 | |
| @@ -67,10 +105,10 @@ | ||
| 67 | 105 | 'public' => false, |
| 68 | 106 | 'exclude_from_search' => true, |
| 69 | 107 | 'show_in_admin_all_list' => false, |
| 70 | 108 | 'show_in_admin_status_list' => true, |
| 109 | + /* translators: %s: Number of archived posts. */ | |
| 71 | 110 | 'label_count' => _n_noop( |
| 72 | - /* translators: %s: number of posts in "Archived" status */ | |
| 73 | 111 | 'Archived <span class="count">(%s)</span>', |
| 74 | 112 | 'Archived <span class="count">(%s)</span>', |
| 75 | 113 | 'propertyhive' |
| 76 | 114 | ), |
| @@ -136,8 +174,9 @@ | ||
| 136 | 174 | { |
| 137 | 175 | // Find all properties linked to this contact |
| 138 | 176 | $args = array( |
| 139 | 177 | 'post_type' => 'property', |
| 178 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Owner search metadata must be refreshed on properties linked through the existing serialized owner IDs; retain the relationship match. | |
| 140 | 179 | 'meta_query' => array( |
| 141 | 180 | array( |
| 142 | 181 | 'key' => '_owner_contact_id', |
| 143 | 182 | 'value' => '"' . $post_id . '"', |
| @@ -517,8 +556,9 @@ | ||
| 517 | 556 | ) |
| 518 | 557 | ) |
| 519 | 558 | ); |
| 520 | 559 | |
| 560 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Existing public Property Hive extension hook do_action_after_register_taxonomies; changing the established name would detach installed callbacks. | |
| 521 | 561 | do_action( 'do_action_after_register_taxonomies' ); |
| 522 | 562 | } |
| 523 | 563 | |
| 524 | 564 | /** |
| @@ -556,8 +596,9 @@ | ||
| 556 | 596 | 'description' => __( 'This is where you can add new properties to your site.', 'propertyhive' ), |
| 557 | 597 | 'public' => true, |
| 558 | 598 | 'show_ui' => true, |
| 559 | 599 | 'capability_type' => 'post', |
| 600 | + 'capabilities' => self::record_capabilities(), | |
| 560 | 601 | 'map_meta_cap' => true, |
| 561 | 602 | 'publicly_queryable' => true, |
| 562 | 603 | 'exclude_from_search' => false, |
| 563 | 604 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -595,8 +636,9 @@ | ||
| 595 | 636 | 'description' => __( 'This is where you can add new contacts to your site.', 'propertyhive' ), |
| 596 | 637 | 'public' => false, |
| 597 | 638 | 'show_ui' => true, |
| 598 | 639 | 'capability_type' => 'post', |
| 640 | + 'capabilities' => self::record_capabilities(), | |
| 599 | 641 | 'map_meta_cap' => true, |
| 600 | 642 | 'publicly_queryable' => false, |
| 601 | 643 | 'exclude_from_search' => true, |
| 602 | 644 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -629,8 +671,9 @@ | ||
| 629 | 671 | ), |
| 630 | 672 | 'public' => true, |
| 631 | 673 | 'show_ui' => false, |
| 632 | 674 | 'capability_type' => 'post', |
| 675 | + 'capabilities' => self::record_capabilities(), | |
| 633 | 676 | 'map_meta_cap' => true, |
| 634 | 677 | 'publicly_queryable' => false, |
| 635 | 678 | 'exclude_from_search' => true, |
| 636 | 679 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -664,8 +707,9 @@ | ||
| 664 | 707 | ), |
| 665 | 708 | 'public' => false, |
| 666 | 709 | 'show_ui' => true, |
| 667 | 710 | 'capability_type' => 'post', |
| 711 | + 'capabilities' => self::record_capabilities(), | |
| 668 | 712 | 'map_meta_cap' => true, |
| 669 | 713 | 'publicly_queryable' => false, |
| 670 | 714 | 'exclude_from_search' => true, |
| 671 | 715 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -699,8 +743,9 @@ | ||
| 699 | 743 | ), |
| 700 | 744 | 'public' => false, |
| 701 | 745 | 'show_ui' => true, |
| 702 | 746 | 'capability_type' => 'post', |
| 747 | + 'capabilities' => self::record_capabilities(), | |
| 703 | 748 | 'map_meta_cap' => true, |
| 704 | 749 | 'publicly_queryable' => false, |
| 705 | 750 | 'exclude_from_search' => true, |
| 706 | 751 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -733,8 +778,9 @@ | ||
| 733 | 778 | ), |
| 734 | 779 | 'public' => false, |
| 735 | 780 | 'show_ui' => true, |
| 736 | 781 | 'capability_type' => 'post', |
| 782 | + 'capabilities' => self::record_capabilities(), | |
| 737 | 783 | 'map_meta_cap' => true, |
| 738 | 784 | 'publicly_queryable' => false, |
| 739 | 785 | 'exclude_from_search' => true, |
| 740 | 786 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -767,8 +813,9 @@ | ||
| 767 | 813 | ), |
| 768 | 814 | 'public' => false, |
| 769 | 815 | 'show_ui' => true, |
| 770 | 816 | 'capability_type' => 'post', |
| 817 | + 'capabilities' => self::record_capabilities(), | |
| 771 | 818 | 'map_meta_cap' => true, |
| 772 | 819 | 'publicly_queryable' => false, |
| 773 | 820 | 'exclude_from_search' => true, |
| 774 | 821 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -801,11 +848,11 @@ | ||
| 801 | 848 | ), |
| 802 | 849 | 'public' => false, |
| 803 | 850 | 'show_ui' => true, |
| 804 | 851 | 'capability_type' => 'post', |
| 805 | - 'capabilities' => array( | |
| 852 | + 'capabilities' => array_merge( self::record_capabilities(), array( | |
| 806 | 853 | 'create_posts' => false |
| 807 | - ), | |
| 854 | + ) ), | |
| 808 | 855 | 'map_meta_cap' => true, |
| 809 | 856 | 'publicly_queryable' => false, |
| 810 | 857 | 'exclude_from_search' => true, |
| 811 | 858 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -838,8 +885,9 @@ | ||
| 838 | 885 | ), |
| 839 | 886 | 'public' => false, |
| 840 | 887 | 'show_ui' => true, |
| 841 | 888 | 'capability_type' => 'post', |
| 889 | + 'capabilities' => self::record_capabilities(), | |
| 842 | 890 | 'map_meta_cap' => true, |
| 843 | 891 | 'publicly_queryable' => false, |
| 844 | 892 | 'exclude_from_search' => true, |
| 845 | 893 | 'hierarchical' => false, // Hierarchical causes memory issues - WP loads all records! |
| @@ -880,11 +928,11 @@ | ||
| 880 | 928 | 'query_var' => true, |
| 881 | 929 | 'supports' => false, |
| 882 | 930 | 'show_in_nav_menus' => false, |
| 883 | 931 | 'show_in_menu' => false, |
| 884 | - 'capabilities' => array( | |
| 932 | + 'capabilities' => array_merge( self::record_capabilities(), array( | |
| 885 | 933 | 'create_posts' => 'do_not_allow' |
| 886 | - ), | |
| 934 | + ) ), | |
| 887 | 935 | ) |
| 888 | 936 | ) |
| 889 | 937 | ); |
| 890 | 938 | do_action( 'propertyhive_after_register_post_types' ); |
| @@ -925,8 +973,9 @@ | ||
| 925 | 973 | $args = array( |
| 926 | 974 | 'post_type' => 'enquiry', |
| 927 | 975 | 'nopaging' => true, |
| 928 | 976 | 'post_status' => array('publish', 'pending', 'private', 'draft', 'auto-draft', 'future', 'trash'), |
| 977 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Both legacy and current property relationship metadata must match when the explicit enquiry-trash option is enabled. | |
| 929 | 978 | 'meta_query' => array( |
| 930 | 979 | 'relation' => 'OR', |
| 931 | 980 | array( |
| 932 | 981 | 'key' => '_property_id', |
| @@ -985,9 +1034,11 @@ | ||
| 985 | 1034 | 'post_type' => 'property', |
| 986 | 1035 | 'fields' => 'ids', |
| 987 | 1036 | 'suppress_filters' => TRUE, |
| 988 | 1037 | 'posts_per_page' => 1, |
| 1038 | + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Excludes only the property being deleted so its own media reference cannot prevent attachment cleanup. | |
| 989 | 1039 | 'post__not_in' => array( $post_id ), |
| 1040 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Before deleting an attachment, check all four serialized property media relationships; the query stops at one matching property to preserve shared media. | |
| 990 | 1041 | 'meta_query' => array( |
| 991 | 1042 | 'relation' => 'OR', |
| 992 | 1043 | array( |
| 993 | 1044 | 'key' => '_photos', |
| @@ -1078,8 +1129,9 @@ | ||
| 1078 | 1129 | $args = array( |
| 1079 | 1130 | 'post_type' => 'enquiry', |
| 1080 | 1131 | 'nopaging' => true, |
| 1081 | 1132 | 'post_status' => array('publish', 'pending', 'private', 'draft', 'auto-draft', 'future', 'trash'), |
| 1133 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Both legacy and current property relationship metadata must match when the explicit enquiry-delete option is enabled. | |
| 1082 | 1134 | 'meta_query' => array( |
| 1083 | 1135 | 'relation' => 'OR', |
| 1084 | 1136 | array( |
| 1085 | 1137 | 'key' => '_property_id', |
| @@ -1135,8 +1187,9 @@ | ||
| 1135 | 1187 | |
| 1136 | 1188 | $args = array( |
| 1137 | 1189 | 'post_type' => 'contact', |
| 1138 | 1190 | 'nopaging' => true, |
| 1191 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Remove only contacts linked to the deleted user through their stored user ID. | |
| 1139 | 1192 | 'meta_query' => array( |
| 1140 | 1193 | array( |
| 1141 | 1194 | 'key' => '_user_id', |
| 1142 | 1195 | 'value' => (int)$user_id, |
| @@ -1257,8 +1310,10 @@ | ||
| 1257 | 1310 | $args = array( |
| 1258 | 1311 | 'post_type' => 'property', |
| 1259 | 1312 | 'fields' => 'ids', |
| 1260 | 1313 | 'post_status' => array( 'publish', 'draft'), |
| 1314 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- This maintenance backfill must select records missing the destination metadata; removing the predicate would overwrite existing values. | |
| 1315 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Owner search metadata must be refreshed on properties linked through the existing serialized owner IDs; retain the relationship match. | |
| 1261 | 1316 | 'meta_query' => array( |
| 1262 | 1317 | array( |
| 1263 | 1318 | 'key' => '_address_concatenated', |
| 1264 | 1319 | 'compare' => 'NOT EXISTS' |
| @@ -1265,8 +1320,9 @@ | ||
| 1265 | 1320 | ) |
| 1266 | 1321 | ), |
| 1267 | 1322 | 'nopaging' => true, |
| 1268 | 1323 | 'orderby' => 'rand', |
| 1324 | + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Maintenance must cover every language; front-end language filters would omit records from this backfill. | |
| 1269 | 1325 | 'suppress_filters' => true, |
| 1270 | 1326 | ); |
| 1271 | 1327 | $property_query = new WP_Query($args); |
| 1272 | 1328 | |
| @@ -1288,8 +1344,10 @@ | ||
| 1288 | 1344 | $args = array( |
| 1289 | 1345 | 'post_type' => 'contact', |
| 1290 | 1346 | 'fields' => 'ids', |
| 1291 | 1347 | 'post_status' => 'publish', |
| 1348 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- This maintenance backfill must select records missing the destination metadata; removing the predicate would overwrite existing values. | |
| 1349 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Owner search metadata must be refreshed on properties linked through the existing serialized owner IDs; retain the relationship match. | |
| 1292 | 1350 | 'meta_query' => array( |
| 1293 | 1351 | array( |
| 1294 | 1352 | 'key' => '_address_concatenated', |
| 1295 | 1353 | 'compare' => 'NOT EXISTS' |
| @@ -1296,8 +1354,9 @@ | ||
| 1296 | 1354 | ) |
| 1297 | 1355 | ), |
| 1298 | 1356 | 'nopaging' => true, |
| 1299 | 1357 | 'orderby' => 'rand', |
| 1358 | + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Maintenance must cover every language; front-end language filters would omit records from this backfill. | |
| 1300 | 1359 | 'suppress_filters' => true, |
| 1301 | 1360 | ); |
| 1302 | 1361 | $contact_query = new WP_Query($args); |
| 1303 | 1362 | |
| @@ -1359,8 +1418,9 @@ | ||
| 1359 | 1418 | 'fields' => 'ids', |
| 1360 | 1419 | 'post_type' => 'viewing', |
| 1361 | 1420 | 'nopaging' => true, |
| 1362 | 1421 | 'post_status' => 'publish', |
| 1422 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Recompute related-viewing metadata only for viewings linked to the changed property or applicant; retain existing relationship and status semantics. | |
| 1363 | 1423 | 'meta_query' => $meta_query, |
| 1364 | 1424 | 'orderby' => 'none' |
| 1365 | 1425 | ); |
| 1366 | 1426 | |
| @@ -1393,8 +1453,9 @@ | ||
| 1393 | 1453 | 'fields' => 'ids', |
| 1394 | 1454 | 'post_type' => 'viewing', |
| 1395 | 1455 | 'nopaging' => true, |
| 1396 | 1456 | 'post_status' => 'publish', |
| 1457 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Recompute related-viewing metadata only for viewings linked to the changed property or applicant; retain existing relationship and status semantics. | |
| 1397 | 1458 | 'meta_query' => $meta_query, |
| 1398 | 1459 | 'orderby' => 'none' |
| 1399 | 1460 | ); |
| 1400 | 1461 | |