PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
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
propertyhive / includes / class-ph-yoast-seo.php

class-ph-yoast-seo.php in Property Hive 2.3.1, at includes/class-ph-yoast-seo.php

289 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit; // Exit if accessed directly
5 }
6
7 /**
8 * Yoast SEO Compatibility
9 *
10 * @class PH_Yoast_SEO
11 * @version 1.0.0
12 * @package PropertyHive/Classes/
13 * @category Class
14 * @author PropertyHive
15 */
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 {
18
19 /** @var PH_Yoast_SEO The single instance of the class */
20 protected static $_instance = null;
21
22 /**
23 * Hook in methods
24 */
25 public static function init() {
26 add_filter( 'wpseo_sitemap_exclude_taxonomy', array( __CLASS__, 'sitemap_exclude_taxonomy' ), 10, 2 );
27 add_filter( 'wpseo_metabox_prio', array( __CLASS__, 'yoast_meta_box_to_bottom') );
28 add_filter( 'manage_edit-property_columns', array( __CLASS__, 'yoast_remove_columns') );
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' ) );
36 }
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
240 public static function sitemap_exclude_taxonomy( $value, $taxonomy ) {
241 $taxonomy_objects = get_object_taxonomies( 'property', 'objects' );
242 $taxonomies = array();
243
244 if ( !empty($taxonomy_objects) )
245 {
246 foreach ( $taxonomy_objects as $taxonomy_name => $object )
247 {
248 $taxonomies[] = $taxonomy_name;
249 }
250 }
251 if (
252 in_array($taxonomy, $taxonomies)
253 )
254 {
255 return true;
256 }
257 }
258
259 public static function yoast_meta_box_to_bottom() {
260 return 'low';
261 }
262
263 public static function yoast_remove_columns( $columns ) {
264 unset( $columns['wpseo-score'] );
265 unset( $columns['wpseo-score-readability'] );
266 unset( $columns['wpseo-title'] );
267 unset( $columns['wpseo-metadesc'] );
268 unset( $columns['wpseo-focuskw'] );
269 return $columns;
270 }
271
272 public static function sitemap_exclude_off_market() {
273 return get_posts(array(
274 'fields' => 'ids',
275 'posts_per_page' => -1,
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.
278 'meta_query' => array(
279 array(
280 'key' => '_on_market',
281 'value' => '',
282 )
283 )
284 ));
285 }
286
287 }
288
289 PH_Yoast_SEO::init();