PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
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
← All changes | includes/class-ph-yoast-seo.php +215 -5 1.4.552.3.0 View file →
@@ -4,19 +4,20 @@
4 4 exit; // Exit if accessed directly
5 5 }
6 6
7 7 /**
8 - * Serve properties using the REST API
8 + * Yoast SEO Compatibility
9 9 *
10 - * @class PH_Rest_Api
10 + * @class PH_Yoast_SEO
11 11 * @version 1.0.0
12 12 * @package PropertyHive/Classes/
13 13 * @category Class
14 14 * @author PropertyHive
15 15 */
16 -class PH_Yoast_API {
16 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Preserve the existing public PH_Yoast_SEO class name for plugin and extension compatibility.
17 +class PH_Yoast_SEO {
17 18
18 - /** @var PH_Rest_Api The single instance of the class */
19 + /** @var PH_Yoast_SEO The single instance of the class */
19 20 protected static $_instance = null;
20 21
21 22 /**
22 23 * Hook in methods
@@ -25,10 +26,218 @@
25 26 add_filter( 'wpseo_sitemap_exclude_taxonomy', array( __CLASS__, 'sitemap_exclude_taxonomy' ), 10, 2 );
26 27 add_filter( 'wpseo_metabox_prio', array( __CLASS__, 'yoast_meta_box_to_bottom') );
27 28 add_filter( 'manage_edit-property_columns', array( __CLASS__, 'yoast_remove_columns') );
28 29 add_filter( 'wpseo_exclude_from_sitemap_by_post_ids', array( __CLASS__, 'sitemap_exclude_off_market') );
30 +
31 + add_filter( 'wpseo_schema_webpage_type', array( __CLASS__, 'yoast_schema_webpage_type') );
32 + add_filter( 'wpseo_schema_webpage', array( __CLASS__, 'yoast_schema_webpage') );
33 + add_filter( 'wpseo_schema_graph', array( __CLASS__, 'yoast_schema_graph'), 20 );
34 +
35 + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
29 36 }
30 37
38 + public static function enqueue_scripts()
39 + {
40 + $screen = get_current_screen();
41 +
42 + if ( in_array( $screen->id, array( 'property' ) ) )
43 + {
44 + wp_enqueue_script( 'propertyhive-yoast', PH()->plugin_url() . '/assets/js/admin/yoast.js', array( 'jquery' ), PH_VERSION, true );
45 + }
46 + }
47 +
48 + public static function yoast_schema_webpage_type( $type )
49 + {
50 + global $post;
51 +
52 + if ( isset($post->post_type) && $post->post_type == 'property' )
53 + {
54 + if ( !is_array($type) )
55 + {
56 + $type = array($type);
57 + }
58 + $type[] = 'RealEstateListing';
59 + $type = array_filter( array_values( array_unique( $type ) ) );
60 + }
61 +
62 + return $type;
63 + }
64 +
65 + public static function yoast_schema_webpage( $data )
66 + {
67 + // Enrich the WebPage piece with RealEstateListing props (datePosted, offers, mainEntity)
68 +
69 + if ( ! is_singular( 'property' ) ) {
70 + return $data;
71 + }
72 +
73 + $post_id = get_the_ID();
74 +
75 + $property = new PH_Property($post_id);
76 +
77 + $url = get_permalink( $post_id );
78 +
79 + // IDs so everything links together nicely in the graph
80 + $residence_id = $url . '#/residence/' . $post_id;
81 + $offer_id = $url . '#/offer/' . $post_id;
82 +
83 + // Core listing bits
84 + $department = $property->_department;
85 +
86 + $data['datePosted'] = gmdate("Y-m-d\TH:i:s", strtotime($property->_on_market_change_date)) . "+00:00";
87 +
88 + // Attach an Offer (price, currency, availability) that points at the Residence
89 + if ( $department == 'residential-sales' || ph_get_custom_department_based_on($department) == 'residential-sales' )
90 + {
91 + $data['offers'] = [
92 + '@type' => 'Offer',
93 + '@id' => $offer_id,
94 + 'price' => $property->_poa != 'yes' ? (float)$property->_price : '',
95 + 'priceCurrency' => $property->_currency,
96 + // Use a schema.org ItemAvailability URL if you have one; default to InStock
97 + //'availability' => $availability ?: 'https://schema.org/InStock',
98 + 'businessFunction' => 'https://purl.org/goodrelations/v1#Sell',
99 + 'itemOffered' => [ '@id' => $residence_id ],
100 + 'url' => $url
101 + ];
102 + }
103 + elseif ( $department == 'residential-lettings' || ph_get_custom_department_based_on($department) == 'residential-lettings' )
104 + {
105 + $data['offers'] = [
106 + '@type' => 'Offer',
107 + '@id' => $offer_id,
108 + 'price' => $property->_poa != 'yes' ? (float)$property->_rent : '',
109 + 'priceCurrency' => $property->_currency,
110 + 'priceSpecification' => [
111 + '@type' => 'UnitPriceSpecification',
112 + 'price' => $property->_poa != 'yes' ? (float)$property->_rent : '',
113 + 'priceCurrency' => $property->_currency,
114 + 'unitText' => $property->_rent_frequency,
115 + ],
116 + 'businessFunction' => 'https://purl.org/goodrelations/v1#LeaseOut',
117 + // Use a schema.org ItemAvailability URL if you have one; default to InStock
118 + //'availability' => $availability ?: 'https://schema.org/InStock',
119 + 'itemOffered' => [ '@id' => $residence_id ],
120 + 'url' => $url
121 + ];
122 + }
123 +
124 + // Point the WebPage at the main entity (the actual property)
125 + $data['mainEntity'] = [ '@id' => $residence_id ];
126 +
127 + return $data;
128 + }
129 +
130 + public static function yoast_schema_graph( $graph )
131 + {
132 + // Append a Residence/House node with all the property specifics
133 +
134 + if ( ! is_singular( 'property' ) ) {
135 + return $graph;
136 + }
137 +
138 + $post_id = get_the_ID();
139 +
140 + $property = new PH_Property($post_id);
141 +
142 + $url = get_permalink( $post_id );
143 +
144 + $department = $property->_department;
145 +
146 + $residence_id = $url . '#/residence/' . $post_id;
147 +
148 + // Collect property meta
149 + $bedrooms = (int)$property->_bedrooms;
150 + $bathrooms = (int)$property->_bathrooms;
151 + $street = $property->_address_street;
152 + $locality = $property->_address_two;
153 + $region = $property->_address_three;
154 + $postcode = $property->_address_postcode;
155 + $country = $property->_address_country;
156 + $lat = $property->_latitude;
157 + $lng = $property->_longitude;
158 +
159 + $images = array();
160 + if ( get_option('propertyhive_images_stored_as', '') == 'urls' )
161 + {
162 + $photo_urls = $property->_photo_urls;
163 + if ( !is_array($photo_urls) ) { $photo_urls = array(); }
164 +
165 + if ( !empty($num_images) )
166 + {
167 + $photo_urls = array_slice($photo_urls, 0, $num_images);
168 + }
169 +
170 + foreach ( $photo_urls as $photo )
171 + {
172 + $images[] = isset($photo['url']) ? $photo['url'] : '';
173 + }
174 + }
175 + else
176 + {
177 + $gallery_attachments = $property->get_gallery_attachment_ids();
178 +
179 + if ( !empty($gallery_attachments) )
180 + {
181 + if ( !empty($num_images) )
182 + {
183 + $gallery_attachments = array_slice($gallery_attachments, 0, $num_images);
184 + }
185 +
186 + foreach ($gallery_attachments as $gallery_attachment)
187 + {
188 + $images[] = wp_get_attachment_url( $gallery_attachment );
189 + }
190 + }
191 + }
192 + $images = array_values( array_unique( array_filter( $images ) ) );
193 +
194 + $types = array( 'Residence' );
195 + if ( $department != 'commercial' && ph_get_custom_department_based_on($department) != 'commercial' )
196 + {
197 + $types[] = 'SingleFamilyResidence';
198 + }
199 + $residence = [
200 + '@type' => $types,
201 + '@id' => $residence_id,
202 + 'url' => $url,
203 + 'name' => get_the_title( $post_id ),
204 + ];
205 +
206 + // Address
207 + if ( $street || $locality || $region || $postcode || $country ) {
208 + $residence['address'] = array_filter( [
209 + '@type' => 'PostalAddress',
210 + 'streetAddress' => $street,
211 + 'addressLocality' => $locality,
212 + 'addressRegion' => $region,
213 + 'postalCode' => $postcode,
214 + 'addressCountry' => $country,
215 + ] );
216 + }
217 +
218 + // Geo
219 + if ( $lat && $lng ) {
220 + $residence['geo'] = [
221 + '@type' => 'GeoCoordinates',
222 + 'latitude' => (float)$lat,
223 + 'longitude' => (float)$lng,
224 + ];
225 + }
226 +
227 + // Rooms
228 + if ( $bedrooms && $department != 'commercial' && ph_get_custom_department_based_on($department) != 'commercial' ) { $residence['numberOfBedrooms'] = $bedrooms; }
229 + if ( $bathrooms && $department != 'commercial' && ph_get_custom_department_based_on($department) != 'commercial' ) { $residence['numberOfBathroomsTotal'] = $bathrooms; }
230 +
231 + // Photos / primary image
232 + if ( $images ) {
233 + $residence['image'] = $images;
234 + }
235 +
236 + $graph[] = $residence;
237 + return $graph;
238 + }
239 +
31 240 public static function sitemap_exclude_taxonomy( $value, $taxonomy ) {
32 241 $taxonomy_objects = get_object_taxonomies( 'property', 'objects' );
33 242 $taxonomies = array();
34 243
@@ -64,8 +273,9 @@
64 273 return get_posts(array(
65 274 'fields' => 'ids',
66 275 'posts_per_page' => -1,
67 276 'post_type' => 'property',
277 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Sitemap exclusions require all property IDs explicitly marked off-market in the existing metadata schema; preserve the complete exclusion list and status semantics.
68 278 'meta_query' => array(
69 279 array(
70 280 'key' => '_on_market',
71 281 'value' => '',
@@ -75,5 +285,5 @@
75 285 }
76 286
77 287 }
78 288
79 -PH_Yoast_API::init();
289 +PH_Yoast_SEO::init();