PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.1.1
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.1.1
7.2.2 7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 All 37 releases
mlsimport / includes / standalone / class-mlsimport-standalone-cpt.php

class-mlsimport-standalone-cpt.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.1.1, at includes/standalone/class-mlsimport-standalone-cpt.php

216 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Standalone (theme_id 990) custom post types and taxonomies.
4 *
5 * Registers the two standalone CPTs (mlsimport_property, mlsimport_agent) and
6 * the flat mlsimport_-prefixed taxonomies (§7). Per ADR-0003 these are ALWAYS
7 * registered with show_in_rest so the data layer works in every mode — but the
8 * admin UI AND the public URL surface (public/has_archive/rewrite) follow
9 * standalone mode. Outside 990 the CPTs must not own front-end URLs at all: a
10 * CPT archive rewrite out-ranks WordPress's page rule, so any base they claim
11 * is taken away from a page or theme CPT already using it (#206). Inside 990
12 * the property base is property_slug() — 'listing' by default and admin-
13 * settable, never the heavily contested 'properties'. maybe_flush_rewrites()
14 * keeps the cached rewrite_rules option in sync when the mode or base changes.
15 *
16 * @package Mlsimport
17 */
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Registers the standalone CPTs and taxonomies.
25 */
26 class Mlsimport_Standalone_Cpt {
27
28 /**
29 * The default property URL base. See property_slug() for why it is not
30 * 'properties'.
31 */
32 private const DEFAULT_PROPERTY_SLUG = 'listing';
33
34 /**
35 * The flat taxonomies (§7), all attached to mlsimport_property. Flat
36 * (hierarchical=false) — term trees confuse non-technical realtors.
37 */
38 private const TAXONOMIES = array(
39 'mlsimport_property_type' => 'Property Types',
40 'mlsimport_listing_type' => 'Listing Types',
41 'mlsimport_status' => 'Statuses',
42 'mlsimport_city' => 'Cities',
43 'mlsimport_area' => 'Areas',
44 'mlsimport_county' => 'Counties',
45 'mlsimport_state' => 'States',
46 'mlsimport_zip' => 'ZIP Codes',
47 'mlsimport_high_school_district' => 'High School Districts',
48 'mlsimport_feature' => 'Features',
49 'mlsimport_label' => 'Labels',
50 );
51
52 /**
53 * The plugin taxonomy slugs, after the mlsimport_taxonomies filter — the
54 * single source of truth for anything that needs to iterate the taxonomies
55 * (e.g. per-term settings).
56 *
57 * @return string[]
58 */
59 public static function taxonomy_slugs(): array {
60 return array_keys( (array) apply_filters( 'mlsimport_taxonomies', self::TAXONOMIES ) );
61 }
62
63 /**
64 * The plugin taxonomies as slug => label, after the mlsimport_taxonomies
65 * filter — the single source for anything needing a labelled taxonomy list
66 * (e.g. the Category widgets' taxonomy picker).
67 *
68 * @return array<string,string>
69 */
70 public static function taxonomy_labels(): array {
71 return (array) apply_filters( 'mlsimport_taxonomies', self::TAXONOMIES );
72 }
73
74 /**
75 * The URL base the property archive and single permalinks sit on.
76 *
77 * Deliberately NOT 'properties' (#206): a CPT archive rewrite out-ranks
78 * WordPress's page rule, so that base silently swallows any page or theme
79 * property CPT already using it. 'listing' is the default; the admin can
80 * move it from the standalone settings when even that collides.
81 *
82 * @return string The rewrite slug, never empty.
83 */
84 public static function property_slug(): string {
85 // Step 1 - read the admin's choice, defaulting to the safe base.
86 $slug = (string) mlsimport_standalone_option( 'property_url_slug', self::DEFAULT_PROPERTY_SLUG );
87
88 // Step 2 - normalise it the way WordPress normalises any permalink part,
89 // so 'Homes For Sale' becomes a working base instead of a broken one.
90 $slug = sanitize_title( $slug );
91
92 // Step 3 - a setting that normalises to nothing would leave the archive
93 // with no base at all, so fall back rather than register it.
94 return '' !== $slug ? $slug : self::DEFAULT_PROPERTY_SLUG;
95 }
96
97 /**
98 * Register the CPTs and taxonomies. Hook to init in production.
99 *
100 * @return void
101 */
102 public static function register(): void {
103 // ADR-0003: always registered (REST/data layer), but both the admin UI
104 // and the front-end URL surface follow standalone mode. Outside 990 the
105 // CPTs must not be public and must pass rewrite => false: an archive
106 // rewrite beats WordPress's page rule and silently hijacks whatever
107 // page or theme CPT already sits on that base (#206).
108 $standalone = function_exists( 'mlsimport_is_standalone_mode' ) && mlsimport_is_standalone_mode();
109 $show_ui = $standalone;
110
111 // Same brand icon as the Import Tasks menu.
112 $menu_icon = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL . 'img/mlsimport_menu.png' : 'dashicons-admin-home';
113
114 /** Filter the property CPT registration args. @since 6.3 */
115 register_post_type(
116 'mlsimport_property',
117 apply_filters(
118 'mlsimport_property_post_type_args',
119 array(
120 'public' => $standalone,
121 'show_ui' => $show_ui,
122 'show_in_menu' => $show_ui,
123 'show_in_rest' => true,
124 'menu_icon' => $menu_icon,
125 'has_archive' => $standalone,
126 'rewrite' => $standalone ? array( 'slug' => self::property_slug() ) : false,
127 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'comments', 'author' ),
128 'labels' => array( 'name' => 'MLS Properties' ),
129 // Group above core Comments (25) so all MLSImport menus stay together.
130 'menu_position' => 22,
131 )
132 )
133 );
134
135 /** Filter the agent CPT registration args. @since 6.3 */
136 register_post_type(
137 'mlsimport_agent',
138 apply_filters(
139 'mlsimport_agent_post_type_args',
140 array(
141 'public' => $standalone,
142 'show_ui' => $show_ui,
143 'show_in_menu' => $show_ui,
144 'show_in_rest' => true,
145 'menu_icon' => $menu_icon,
146 'has_archive' => $standalone,
147 // Core keys rewrite generation off this arg, not off 'public'.
148 'rewrite' => $standalone,
149 'supports' => array( 'title', 'editor', 'thumbnail' ),
150 'labels' => array( 'name' => 'Real Estate Agents' ),
151 'menu_position' => 23,
152 )
153 )
154 );
155
156 /** Filter the standalone taxonomy list (slug => label). @since 6.3 */
157 $taxonomies = (array) apply_filters( 'mlsimport_taxonomies', self::TAXONOMIES );
158 // Register each taxonomy against the property CPT with the shared arg set.
159 foreach ( $taxonomies as $taxonomy => $label ) {
160 /** Filter a taxonomy's registration args. @since 6.3 */
161 register_taxonomy(
162 $taxonomy,
163 'mlsimport_property',
164 apply_filters(
165 'mlsimport_taxonomy_args',
166 array(
167 'public' => $standalone,
168 'show_ui' => $show_ui,
169 'show_in_menu' => $show_ui,
170 'show_in_rest' => true,
171 // Core keys rewrite generation off this arg, not off 'public'.
172 'rewrite' => $standalone,
173 'hierarchical' => false,
174 'labels' => array( 'name' => $label ),
175 ),
176 $taxonomy
177 )
178 );
179 }
180
181 /** Fires after the standalone CPTs + taxonomies are registered. @since 6.3 */
182 do_action( 'mlsimport_registered_cpts' );
183 }
184
185 /**
186 * Drop the cached rewrite rules whenever the stored mode signature no
187 * longer matches the current standalone mode (#206).
188 *
189 * The CPT archive rule only exists in standalone mode since the fix above,
190 * and its base is now a setting, but WordPress caches compiled rules in the
191 * rewrite_rules option — so a mode switch, a base change, or updating to
192 * this version on a site where the stale rule already hijacked a
193 * /properties/ page all leave wrong rules in the DB. Deleting the option makes WordPress lazily
194 * rebuild the rules on the next request, after init has registered the
195 * CPTs with the correct args for the current mode. Hooked to init after
196 * register(); on matching signatures (every ordinary request) it is a
197 * single get_option and does nothing.
198 *
199 * @return void
200 */
201 public static function maybe_flush_rewrites(): void {
202 // Signature = fix revision + current mode + the archive base. The slug is
203 // part of it because an admin changing the base from the settings screen
204 // changes the rules register() produces, exactly like a mode switch does.
205 // Bump 'v2' if rewrite-affecting args change again.
206 $standalone = function_exists( 'mlsimport_is_standalone_mode' ) && mlsimport_is_standalone_mode();
207 $signature = 'v2:' . ( $standalone ? '990' : 'other' ) . ':' . self::property_slug();
208
209 if ( get_option( 'mlsimport_rewrite_mode' ) === $signature ) {
210 return;
211 }
212 delete_option( 'rewrite_rules' );
213 update_option( 'mlsimport_rewrite_mode', $signature );
214 }
215 }
216