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.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 6.0.5 All 36 releases
mlsimport / includes / standalone / class-mlsimport-property-columns.php

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

292 lines 10.9 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) property list-table columns.
4 *
5 * Makes wp-admin edit.php?post_type=mlsimport_property mirror the WPResidence
6 * property list: Thumbnail + Title + Location, Type, Status and Price. The
7 * WPResidence dashboard list also has an Actions column — intentionally omitted
8 * here, since wp-admin already provides row actions under the title.
9 *
10 * Columns map to the standalone taxonomies (Mlsimport_Standalone_Cpt) and the
11 * ListPrice meta written by the metabox; Price is formatted with the same
12 * mlsimport_format_price() helper the front end uses.
13 *
14 * @package Mlsimport
15 */
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Registers and renders the property list-table columns.
23 */
24 class Mlsimport_Property_Columns {
25
26 /**
27 * Register the admin hooks. Hooked on init; only wires up in the admin.
28 *
29 * @return void
30 */
31 public static function register(): void {
32 if ( ! is_admin() ) {
33 return;
34 }
35 add_filter( 'manage_mlsimport_property_posts_columns', array( __CLASS__, 'columns' ) );
36 add_filter( 'manage_edit-mlsimport_property_sortable_columns', array( __CLASS__, 'sortable_columns' ) );
37 add_action( 'manage_mlsimport_property_posts_custom_column', array( __CLASS__, 'render' ), 10, 2 );
38 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) );
39 add_action( 'pre_get_posts', array( __CLASS__, 'orderby_price' ) );
40 add_filter( 'posts_clauses', array( __CLASS__, 'orderby_status' ), 10, 2 );
41 }
42
43 /**
44 * Build the column set, mirroring the WPResidence property list order:
45 * Property (thumb + title), Location, Type, Status, Price — then the native
46 * Date. The native checkbox and title are kept; everything else is replaced.
47 *
48 * @param array $columns Default columns (cb, title, taxonomies, date).
49 * @return array
50 */
51 public static function columns( $columns ): array {
52 return array(
53 'cb' => isset( $columns['cb'] ) ? $columns['cb'] : '',
54 'mlsimport_thumb' => esc_html__( 'Photo', 'mlsimport' ),
55 'title' => esc_html__( 'Property', 'mlsimport' ),
56 'mlsimport_location' => esc_html__( 'Location', 'mlsimport' ),
57 'mlsimport_type' => esc_html__( 'Type', 'mlsimport' ),
58 'mlsimport_status' => esc_html__( 'Status', 'mlsimport' ),
59 'mlsimport_price' => esc_html__( 'Price', 'mlsimport' ),
60 'mlsimport_ids' => esc_html__( 'IDs', 'mlsimport' ),
61 'date' => isset( $columns['date'] ) ? $columns['date'] : esc_html__( 'Date', 'mlsimport' ),
62 );
63 }
64
65 /**
66 * Mark the ID, Price and Status columns sortable. The map values become the
67 * `orderby` query var when a header is clicked: `ID` is handled natively by
68 * WP_Query, while `mlsimport_price` and `mlsimport_status` are resolved by
69 * orderby_price()/orderby_status() below.
70 *
71 * @param array $columns Default sortable columns.
72 * @return array
73 */
74 public static function sortable_columns( $columns ): array {
75 $columns['mlsimport_ids'] = 'ID';
76 $columns['mlsimport_price'] = 'mlsimport_price';
77 $columns['mlsimport_status'] = 'mlsimport_status';
78 return $columns;
79 }
80
81 /**
82 * Sort the property list by ListPrice when the Price header is clicked.
83 * Uses meta_value_num so prices order numerically, not as strings.
84 *
85 * @param WP_Query $query Current query.
86 * @return void
87 */
88 public static function orderby_price( $query ): void {
89 if ( ! is_admin() || ! $query->is_main_query() ) {
90 return;
91 }
92 if ( 'mlsimport_property' !== $query->get( 'post_type' ) ) {
93 return;
94 }
95 if ( 'mlsimport_price' !== $query->get( 'orderby' ) ) {
96 return;
97 }
98 $query->set( 'meta_key', 'mlsimport_ListPrice' );
99 $query->set( 'orderby', 'meta_value_num' );
100 }
101
102 /**
103 * Sort the property list by the mlsimport_status term name when the Status
104 * header is clicked. Taxonomy terms can't be ordered via WP_Query's orderby,
105 * so join the term tables and order by term name in the SQL clauses.
106 *
107 * @param array $clauses SQL clause fragments.
108 * @param WP_Query $query Current query.
109 * @return array
110 */
111 public static function orderby_status( $clauses, $query ): array {
112 if ( ! is_admin() || ! $query->is_main_query() ) {
113 return $clauses;
114 }
115 if ( 'mlsimport_property' !== $query->get( 'post_type' ) ) {
116 return $clauses;
117 }
118 if ( 'mlsimport_status' !== $query->get( 'orderby' ) ) {
119 return $clauses;
120 }
121 global $wpdb;
122 $clauses['join'] .= " LEFT JOIN {$wpdb->term_relationships} mlsimport_status_tr ON ( {$wpdb->posts}.ID = mlsimport_status_tr.object_id )"
123 . " LEFT JOIN {$wpdb->term_taxonomy} mlsimport_status_tt ON ( mlsimport_status_tr.term_taxonomy_id = mlsimport_status_tt.term_taxonomy_id AND mlsimport_status_tt.taxonomy = 'mlsimport_status' )"
124 . " LEFT JOIN {$wpdb->terms} mlsimport_status_t ON ( mlsimport_status_tt.term_id = mlsimport_status_t.term_id )";
125 $order = ( 'ASC' === strtoupper( (string) $query->get( 'order' ) ) ) ? 'ASC' : 'DESC';
126 $clauses['orderby'] = "mlsimport_status_t.name $order";
127 $clauses['groupby'] = "{$wpdb->posts}.ID";
128 return $clauses;
129 }
130
131 /**
132 * Render one custom column cell for a property.
133 *
134 * @param string $column Column key.
135 * @param int $post_id Property post ID.
136 * @return void
137 */
138 public static function render( $column, $post_id ): void {
139 switch ( $column ) {
140 case 'mlsimport_thumb':
141 if ( has_post_thumbnail( $post_id ) ) {
142 echo get_the_post_thumbnail( $post_id, array( 56, 56 ), array( 'class' => 'mlsimport-col-thumb__img' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- core markup.
143 } else {
144 echo '<span class="mlsimport-col-thumb__ph" aria-hidden="true"></span>';
145 }
146 break;
147
148 case 'mlsimport_location':
149 self::tax_rows(
150 $post_id,
151 array(
152 'mlsimport_area' => __( 'Area', 'mlsimport' ),
153 'mlsimport_city' => __( 'City', 'mlsimport' ),
154 'mlsimport_zip' => __( 'ZIP', 'mlsimport' ),
155 'mlsimport_county' => __( 'County', 'mlsimport' ),
156 'mlsimport_state' => __( 'State', 'mlsimport' ),
157 )
158 );
159 break;
160
161 case 'mlsimport_type':
162 self::tax_rows(
163 $post_id,
164 array(
165 'mlsimport_property_type' => __( 'Type', 'mlsimport' ),
166 'mlsimport_listing_type' => __( 'Listing Type', 'mlsimport' ),
167 )
168 );
169 break;
170
171 case 'mlsimport_status':
172 $status = self::terms( $post_id, array( 'mlsimport_status' ) );
173 echo '' !== $status
174 ? '<span class="mlsimport-col-status">' . wp_kses_post( $status ) . '</span>'
175 : '<span class="mlsimport-col-empty">&mdash;</span>';
176 break;
177
178 case 'mlsimport_price':
179 $price = get_post_meta( $post_id, 'mlsimport_ListPrice', true );
180 if ( '' !== $price && null !== $price && function_exists( 'mlsimport_format_price' ) ) {
181 echo '<strong class="mlsimport-col-price">' . esc_html( mlsimport_format_price( $price ) ) . '</strong>';
182 } else {
183 echo '<span class="mlsimport-col-empty">&mdash;</span>';
184 }
185 break;
186
187 case 'mlsimport_ids':
188 self::id_row( __( 'Local ID', 'mlsimport' ), (string) absint( $post_id ) );
189 self::id_row( __( 'MLS ID', 'mlsimport' ), (string) get_post_meta( $post_id, 'mlsimport_ListingId', true ) );
190 self::id_row( __( 'Listing Key', 'mlsimport' ), (string) get_post_meta( $post_id, 'mlsimport_ListingKey', true ) );
191 break;
192 }
193 }
194
195 /**
196 * Render one labelled identifier row inside the IDs column. An empty value
197 * shows an em-dash so the row stays aligned with its label.
198 *
199 * @param string $label Row label (e.g. "MLS ID").
200 * @param string $value Stored identifier.
201 * @return void
202 */
203 private static function id_row( string $label, string $value ): void {
204 echo '<span class="mlsimport-col-id-row"><span class="mlsimport-col-id-label">'
205 . esc_html( $label ) . '</span>'
206 . ( '' !== $value
207 ? '<code class="mlsimport-col-id">' . esc_html( $value ) . '</code>'
208 : '<span class="mlsimport-col-empty">&mdash;</span>' )
209 . '</span>';
210 }
211
212 /**
213 * Comma-join the term names a property has across one or more taxonomies,
214 * preserving the given taxonomy order. Empty taxonomies are skipped.
215 *
216 * @param int $post_id Property post ID.
217 * @param string[] $taxonomies Taxonomy slugs, in display order.
218 * @return string Escaped-safe comma list, or '' when none.
219 */
220 private static function terms( int $post_id, array $taxonomies ): string {
221 return implode( ', ', self::term_links( $post_id, $taxonomies ) );
222 }
223
224 /**
225 * Render one labelled row per taxonomy (label + that taxonomy's term names),
226 * preserving the given order. A taxonomy with no terms shows an em-dash so
227 * each row stays aligned with its label.
228 *
229 * @param int $post_id Property post ID.
230 * @param array<string,string> $map Taxonomy slug => row label, in order.
231 * @return void
232 */
233 private static function tax_rows( int $post_id, array $map ): void {
234 // One labelled row per taxonomy, in the map's order.
235 foreach ( $map as $taxonomy => $label ) {
236 // Comma-join this taxonomy's term names ('' when the post has none).
237 $value = implode( ', ', self::term_links( $post_id, array( $taxonomy ) ) );
238 echo '<span class="mlsimport-col-id-row"><span class="mlsimport-col-id-label">'
239 . esc_html( $label ) . '</span>'
240 . ( '' !== $value
241 ? '<span class="mlsimport-col-tax">' . wp_kses_post( $value ) . '</span>'
242 : '<span class="mlsimport-col-empty">&mdash;</span>' )
243 . '</span>';
244 }
245 }
246
247 /**
248 * Collect the term names a property has across one or more taxonomies, each
249 * linked to its public term archive, preserving the given taxonomy order.
250 * Empty taxonomies are skipped, as is any term whose link can't be built.
251 *
252 * @param int $post_id Property post ID.
253 * @param string[] $taxonomies Taxonomy slugs, in display order.
254 * @return string[] Anchor markup, one per term.
255 */
256 private static function term_links( int $post_id, array $taxonomies ): array {
257 $links = array();
258 // Walk each taxonomy in order, appending its linked term names.
259 foreach ( $taxonomies as $taxonomy ) {
260 $terms = get_the_terms( $post_id, $taxonomy );
261 // get_the_terms() returns false/WP_Error when the post has no terms — skip.
262 if ( is_array( $terms ) ) {
263 foreach ( $terms as $term ) {
264 $url = get_term_link( $term );
265 $links[] = is_wp_error( $url )
266 ? esc_html( $term->name )
267 : '<a href="' . esc_url( $url ) . '">' . esc_html( $term->name ) . '</a>';
268 }
269 }
270 }
271 return $links;
272 }
273
274 /**
275 * Enqueue the tiny column stylesheet on the property list screen only.
276 *
277 * @param string $hook Current admin page hook.
278 * @return void
279 */
280 public static function assets( $hook ): void {
281 if ( 'edit.php' !== $hook ) {
282 return;
283 }
284 if ( 'mlsimport_property' !== get_current_screen()->post_type ) {
285 return;
286 }
287 $base = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' );
288 $ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : false;
289 wp_enqueue_style( 'mlsimport-property-columns', $base . 'admin/css/mlsimport-property-columns.css', array(), $ver );
290 }
291 }
292