PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / trunk
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings vtrunk
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-property-featured-admin.php

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

211 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) — "Featured" on the Properties admin list (issue #288).
4 *
5 * The property edit screen already has a "Featured listing" checkbox (post meta
6 * 'mlsimport_featured' = '1'). This file adds the two list-screen conveniences:
7 *
8 * 1. Bulk actions "Mark featured" / "Remove featured", so an owner can flag many
9 * listings without opening each one.
10 * 2. A "Featured" column, so they can see at a glance which listings are flagged.
11 *
12 * The front end reads the flag from the fast listings table, not from post meta, so
13 * every change made here re-indexes the post — Mlsimport_Standalone_Row::upsert()
14 * then copies the meta into the row's `featured` column. Writing the meta alone
15 * would change nothing a visitor sees.
16 *
17 * Kept apart from Mlsimport_Property_Columns (which owns the column SET) so neither
18 * file outgrows the 300-line limit; this class only appends its one column.
19 *
20 * @package Mlsimport
21 */
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 /**
28 * Bulk actions + list column for the "Featured listing" flag.
29 */
30 class Mlsimport_Property_Featured_Admin {
31
32 /** Post meta the "Featured listing" checkbox saves ('1' when ticked). */
33 private const META = 'mlsimport_featured';
34
35 /**
36 * Register the admin hooks. Hooked on init; only wires up in the admin.
37 *
38 * @return void
39 */
40 public static function register(): void {
41 if ( ! is_admin() ) {
42 return;
43 }
44 add_filter( 'bulk_actions-edit-mlsimport_property', array( __CLASS__, 'bulk_actions' ) );
45 add_filter( 'handle_bulk_actions-edit-mlsimport_property', array( __CLASS__, 'handle_bulk' ), 10, 3 );
46 add_action( 'admin_notices', array( __CLASS__, 'notice' ) );
47 // Priority 20: Mlsimport_Property_Columns::columns() (priority 10) rebuilds the
48 // whole column set, so the Featured column is appended after it has run.
49 add_filter( 'manage_mlsimport_property_posts_columns', array( __CLASS__, 'columns' ), 20 );
50 add_action( 'manage_mlsimport_property_posts_custom_column', array( __CLASS__, 'render' ), 10, 2 );
51 // Priority 20, after Mlsimport_Property_Columns::sortable_columns() has run.
52 add_filter( 'manage_edit-mlsimport_property_sortable_columns', array( __CLASS__, 'sortable' ), 20 );
53 add_filter( 'posts_clauses', array( __CLASS__, 'orderby_featured' ), 10, 2 );
54 }
55
56 /**
57 * Make the Featured header clickable. `true` = the first click sorts DESC, so
58 * featured listings land on top straight away.
59 *
60 * @param array $columns Sortable columns.
61 * @return array
62 */
63 public static function sortable( $columns ): array {
64 $columns = (array) $columns;
65 $columns['mlsimport_featured'] = array( 'mlsimport_featured', true );
66 return $columns;
67 }
68
69 /**
70 * Sort by the featured flag when the header is clicked. A LEFT JOIN, not a
71 * meta_key orderby: most listings have no meta row at all, and meta_key would
72 * drop every one of them from the list. Newest first inside each group.
73 *
74 * @param array $clauses SQL clause fragments.
75 * @param WP_Query $query Current query.
76 * @return array
77 */
78 public static function orderby_featured( $clauses, $query ): array {
79 if ( ! is_admin() || ! $query->is_main_query() ) {
80 return $clauses;
81 }
82 if ( 'mlsimport_property' !== $query->get( 'post_type' ) || 'mlsimport_featured' !== $query->get( 'orderby' ) ) {
83 return $clauses;
84 }
85 global $wpdb;
86 $clauses['join'] .= $wpdb->prepare( " LEFT JOIN {$wpdb->postmeta} mlsimport_featured_pm ON ( {$wpdb->posts}.ID = mlsimport_featured_pm.post_id AND mlsimport_featured_pm.meta_key = %s )", self::META );
87 $order = ( 'ASC' === strtoupper( (string) $query->get( 'order' ) ) ) ? 'ASC' : 'DESC';
88 $clauses['orderby'] = "( mlsimport_featured_pm.meta_value = '1' ) $order, {$wpdb->posts}.post_date DESC";
89 return $clauses;
90 }
91
92 /**
93 * Offer the two bulk actions in the Properties list dropdown.
94 *
95 * @param array $actions Bulk actions (key => label).
96 * @return array
97 */
98 public static function bulk_actions( $actions ): array {
99 $actions = (array) $actions;
100 $actions['mlsimport_mark_featured'] = __( 'Mark featured', 'mlsimport' );
101 $actions['mlsimport_remove_featured'] = __( 'Remove featured', 'mlsimport' );
102 return $actions;
103 }
104
105 /**
106 * Apply "Mark featured" / "Remove featured" to the ticked listings.
107 *
108 * WordPress has already verified the bulk nonce and that the user may edit
109 * this post type before calling the filter; each post is still checked
110 * individually so a listing the user cannot edit is skipped.
111 *
112 * @param string $redirect Where WordPress sends the browser afterwards.
113 * @param string $action The chosen bulk action key.
114 * @param array $post_ids The ticked post IDs.
115 * @return string The redirect URL, carrying the changed count for notice().
116 */
117 public static function handle_bulk( $redirect, $action, $post_ids ) {
118 // Step 1: leave every bulk action that is not ours untouched.
119 if ( 'mlsimport_mark_featured' !== $action && 'mlsimport_remove_featured' !== $action ) {
120 return $redirect;
121 }
122 $value = 'mlsimport_mark_featured' === $action ? '1' : '';
123 $changed = 0;
124
125 foreach ( (array) $post_ids as $post_id ) {
126 $post_id = (int) $post_id;
127 // Step 2: only our post type, and only listings this user may edit. The
128 // capability check is skipped under WP-CLI, which has no current user.
129 if ( 'mlsimport_property' !== get_post_type( $post_id ) ) {
130 continue;
131 }
132 if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) && ! current_user_can( 'edit_post', $post_id ) ) {
133 continue;
134 }
135
136 // Step 3: write the same meta the edit-screen checkbox writes...
137 update_post_meta( $post_id, self::META, $value );
138 // Step 4: ...then re-index, which is what moves the flag into the fast
139 // listings table the front end sorts and badges from.
140 if ( class_exists( 'Mlsimport_Standalone_Reindex' ) ) {
141 Mlsimport_Standalone_Reindex::rebuild_post( $post_id );
142 }
143 ++$changed;
144
145 /** Fires after a listing's featured flag changed from the list screen. @since 7.2.2 */
146 do_action( 'mlsimport_property_featured_changed', $post_id, '1' === $value );
147 }
148
149 // Step 5: hand the count to notice() through the redirect URL.
150 return add_query_arg( 'mlsimport_featured_changed', $changed, (string) $redirect );
151 }
152
153 /**
154 * Confirm a finished bulk action on the Properties list.
155 *
156 * @return void
157 */
158 public static function notice(): void {
159 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only count for a notice.
160 if ( ! isset( $_GET['mlsimport_featured_changed'] ) ) {
161 return;
162 }
163 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
164 $count = absint( wp_unslash( $_GET['mlsimport_featured_changed'] ) );
165 /* translators: %s: number of listings. */
166 $text = sprintf( _n( '%s listing updated.', '%s listings updated.', $count, 'mlsimport' ), number_format_i18n( $count ) );
167 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $text ) . '</p></div>';
168 }
169
170 /**
171 * Add the Featured column just before the native Date column.
172 *
173 * @param array $columns Columns (key => label).
174 * @return array
175 */
176 public static function columns( $columns ): array {
177 $out = array();
178 foreach ( (array) $columns as $key => $label ) {
179 // Slot ours in immediately ahead of Date.
180 if ( 'date' === $key ) {
181 $out['mlsimport_featured'] = esc_html__( 'Featured', 'mlsimport' );
182 }
183 $out[ $key ] = $label;
184 }
185 // No Date column on this screen (hidden by another plugin): append at the end.
186 if ( ! isset( $out['mlsimport_featured'] ) ) {
187 $out['mlsimport_featured'] = esc_html__( 'Featured', 'mlsimport' );
188 }
189 return $out;
190 }
191
192 /**
193 * Render the Featured cell: a star + "Featured" for a flagged listing, a dash
194 * (WordPress's usual "nothing here") otherwise.
195 *
196 * @param string $column Column key.
197 * @param int $post_id Property post ID.
198 * @return void
199 */
200 public static function render( $column, $post_id ): void {
201 if ( 'mlsimport_featured' !== $column ) {
202 return;
203 }
204 if ( '1' === (string) get_post_meta( $post_id, self::META, true ) ) {
205 echo '<span class="dashicons dashicons-star-filled" style="color:#dba617" aria-hidden="true"></span> ' . esc_html__( 'Featured', 'mlsimport' );
206 return;
207 }
208 echo '<span aria-hidden="true">&#8212;</span>';
209 }
210 }
211