Completed
Push — update/editor-blocks-icon-colo... ( 093ab2...3cfb5e )
by
unknown
08:47
created

Jetpack_Geo_Location::get_post_id()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 2
nop 0
dl 0
loc 9
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Adds support for geo-location features.
5
 *
6
 * All Jetpack sites can support geo-location features.  Users can tag posts with geo-location data
7
 * using the UI provided by Calypso.  That information will be included in RSS feeds, meta tags during
8
 * wp_head, and in the Geo microformat following post content.
9
 *
10
 * If your theme declares support for "geo-location", you'll also get a small icon and location label
11
 * visible to users at the bottom of single posts and pages.
12
 *
13
 * To declare support in your theme, call `add_theme_support( 'jetpack-geo-location' )`.
14
 *
15
 * Once you've added theme support, you can rely on the standard HTML output generated in the
16
 * the_content_location_display() method of this class.  Or, you can use the "geo_location_display"
17
 * filter to generate custom HTML for your particular theme.  Your filter function will receive an
18
 * the default HTML as its first argument and an array containing the geo-location information as
19
 * its second argument in the following format:
20
 *
21
 * array(
22
 *     'is_public'    => boolean,
23
 *     'latitude'     => float,
24
 *     'longitude'    => float,
25
 *     'label'        => string,
26
 *     'is_populated' => boolean
27
 * )
28
 *
29
 * Add your filter with:
30
 *
31
 * add_filter( 'jetpack_geo_location_display', 'your_filter_function_name', 10, 2);
32
 */
33
class Jetpack_Geo_Location {
34
	private static $instance;
35
36
	/**
37
	 * Whether dashicons are enqueued.
38
	 *
39
	 * @since 6.6.0
40
	 *
41
	 * @var bool
42
	 */
43
	private static $style_enqueued = false;
44
45
	public static function init() {
46
		if ( is_null( self::$instance ) ) {
47
			self::$instance = new Jetpack_Geo_Location();
48
		}
49
50
		return self::$instance;
51
	}
52
53
	/**
54
	 * This is mostly just used for testing purposes.
55
	 */
56
	public static function reset_instance() {
57
		self::$instance = null;
58
	}
59
60
	public function __construct() {
61
		add_action( 'init', array( $this, 'wordpress_init' ) );
62
		add_action( 'wp_head', array( $this, 'wp_head' ) );
63
		add_filter( 'the_content', array( $this, 'the_content_microformat' ) );
64
65
		$this->register_rss_hooks();
66
	}
67
68
	/**
69
	 * Register support for the geo-location feature on pages and posts.  Register the meta
70
	 * fields managed by this plugin so that they are properly sanitized during save.
71
	 */
72
	public function wordpress_init() {
73
		// Only render location label after post content, if the theme claims to support "geo-location".
74
		if ( current_theme_supports( 'jetpack-geo-location' ) ) {
75
			add_filter( 'the_content', array( $this, 'the_content_location_display' ), 15, 1 );
76
		}
77
78
		add_post_type_support( 'post', 'geo-location' );
79
		add_post_type_support( 'page', 'geo-location' );
80
81
		register_meta(
82
			'post',
83
			'geo_public',
84
			array(
85
				'sanitize_callback' => array( $this, 'sanitize_public' ),
86
				'type'              => 'boolean',
87
				'single'            => true,
88
			)
89
		);
90
91
		register_meta(
92
			'post',
93
			'geo_latitude',
94
			array(
95
				'sanitize_callback' => array( $this, 'sanitize_coordinate' ),
96
				'type'              => 'float',
97
				'single'            => true,
98
			)
99
		);
100
101
		register_meta(
102
			'post',
103
			'geo_longitude',
104
			array(
105
				'sanitize_callback' => array( $this, 'sanitize_coordinate' ),
106
				'type'              => 'float',
107
				'single'            => true,
108
			)
109
		);
110
111
		register_meta(
112
			'post',
113
			'geo_address',
114
			array(
115
				'sanitize_callback' => 'sanitize_text_field',
116
				'type'              => 'string',
117
				'single'            => true,
118
			)
119
		);
120
	}
121
122
	/**
123
	 * Filter "public" input to always be either 1 or 0.
124
	 *
125
	 * @param mixed $public
126
	 *
127
	 * @return int
128
	 */
129
	public function sanitize_public( $public ) {
130
		return absint( $public ) ? 1 : 0;
131
	}
132
133
	/**
134
	 * Filter geo coordinates and normalize them to floats with 7 digits of precision.
135
	 *
136
	 * @param mixed $coordinate
137
	 *
138
	 * @return float|null
139
	 */
140
	public function sanitize_coordinate( $coordinate ) {
141
		if ( ! $coordinate ) {
142
			return null;
143
		}
144
145
		return round( (float) $coordinate, 7 );
146
	}
147
148
	/**
149
	 * Render geo.position and ICBM meta tags with public geo meta values when rendering
150
	 * a single post.
151
	 */
152
	public function wp_head() {
153
		if ( ! is_single() ) {
154
			return;
155
		}
156
157
		$meta_values = $this->get_meta_values( $this->get_post_id() );
158
159
		if ( ! $meta_values['is_public'] ) {
160
			return;
161
		}
162
163
		if ( ! self::$style_enqueued ) {
164
			// only enqueue scripts and styles when needed.
165
			self::enqueue_scripts();
166
			self::$style_enqueued = true;
167
		}
168
169
		echo "\n<!-- Jetpack Geo-location Tags -->\n";
170
171
		if ( $meta_values['label'] ) {
172
			printf(
173
				'<meta name="geo.placename" content="%s" />',
174
				esc_attr( $meta_values['label'] )
175
			);
176
		}
177
178
		printf(
179
			'<meta name="geo.position" content="%s;%s" />' . PHP_EOL,
180
			esc_attr( $meta_values['latitude'] ),
181
			esc_attr( $meta_values['longitude'] )
182
		);
183
184
		printf(
185
			'<meta name="ICBM" content="%s, %s" />' . PHP_EOL,
186
			esc_attr( $meta_values['latitude'] ),
187
			esc_attr( $meta_values['longitude'] )
188
		);
189
190
		echo "\n<!-- End Jetpack Geo-location Tags -->\n";
191
	}
192
193
	/**
194
	 * Append public meta values in the Geo microformat (https://en.wikipedia.org/wiki/Geo_(microformat)
195
	 * to the supplied content.
196
	 *
197
	 * Note that we cannot render the microformat in the context of an excerpt because tags are stripped
198
	 * in that context, making our microformat data visible.
199
	 *
200
	 * @param string $content
201
	 *
202
	 * @return string
203
	 */
204
	public function the_content_microformat( $content ) {
205
		if ( is_feed() || $this->is_currently_excerpt_filter() ) {
206
			return $content;
207
		}
208
209
		$meta_values = $this->get_meta_values( $this->get_post_id() );
210
211
		if ( ! $meta_values['is_public'] ) {
212
			return $content;
213
		}
214
215
		$microformat = sprintf(
216
			'<div id="geo-post-%d" class="geo geo-post" style="display: none">',
217
			esc_attr( $this->get_post_id() )
218
		);
219
220
		$microformat .= sprintf(
221
			'<span class="latitude">%s</span>',
222
			esc_html( $meta_values['latitude'] )
223
		);
224
225
		$microformat .= sprintf(
226
			'<span class="longitude">%s</span>',
227
			esc_html( $meta_values['longitude'] )
228
		);
229
230
		$microformat .= '</div>';
231
232
		return $content . $microformat;
233
	}
234
235
	/**
236
	 * Register a range of hooks for integrating geo data with various feeds.
237
	 */
238
	public function register_rss_hooks() {
239
		add_action( 'rss2_ns', array( $this, 'rss_namespace' ) );
240
		add_action( 'atom_ns', array( $this, 'rss_namespace' ) );
241
		add_action( 'rdf_ns', array( $this, 'rss_namespace' ) );
242
		add_action( 'rss_item', array( $this, 'rss_item' ) );
243
		add_action( 'rss2_item', array( $this, 'rss_item' ) );
244
		add_action( 'atom_entry', array( $this, 'rss_item' ) );
245
		add_action( 'rdf_item', array( $this, 'rss_item' ) );
246
	}
247
248
	/**
249
	 * Add the georss namespace during RSS generation.
250
	 */
251
	public function rss_namespace() {
252
		echo PHP_EOL . "\t" . 'xmlns:georss="http://www.georss.org/georss"';
253
		echo PHP_EOL . "\t" . 'xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"';
254
		echo PHP_EOL . "\t";
255
	}
256
257
	/**
258
	 * Output georss data for RSS items, assuming we have data for the currently rendered post and
259
	 * that data as marked as public.
260
	 */
261
	public function rss_item() {
262
		$meta_values = $this->get_meta_values( $this->get_post_id() );
263
264
		if ( ! $meta_values['is_public'] ) {
265
			return;
266
		}
267
268
		printf(
269
			"\t<georss:point>%s %s</georss:point>\n",
270
			ent2ncr( esc_html( $meta_values['latitude'] ) ),
271
			ent2ncr( esc_html( $meta_values['longitude'] ) )
272
		);
273
274
		printf( "\t\t<geo:lat>%s</geo:lat>\n", ent2ncr( esc_html( $meta_values['latitude'] ) ) );
275
		printf( "\t\t<geo:long>%s</geo:long>\n", ent2ncr( esc_html( $meta_values['longitude'] ) ) );
276
	}
277
278
	/**
279
	 * Enqueue CSS for rendering post flair with geo-location.
280
	 */
281
	private static function enqueue_scripts() {
282
		wp_enqueue_style( 'dashicons' );
283
	}
284
285
	/**
286
	 * If we're rendering a single post and public geo-location data is available for it,
287
	 * include the human-friendly location label in the output.
288
	 *
289
	 * @param string $content
290
	 *
291
	 * @return string
292
	 */
293
	public function the_content_location_display( $content ) {
294
		if ( ! is_single() ) {
295
			return $content;
296
		}
297
298
		return $content . $this->get_location_label();
299
	}
300
301
	/**
302
	 * Get the HTML for displaying a label representing the location associated with the
303
	 * supplied post ID.  If no post ID is given, we'll use the global $post variable, if
304
	 * it is available.
305
	 *
306
	 * @param integer|null $post_id
307
	 *
308
	 * @return string
309
	 */
310
	public function get_location_label( $post_id = null ) {
311
		$meta_values = $this->get_meta_values( $post_id ? $post_id : $this->get_post_id() );
312
313
		if ( ! $meta_values['is_public'] ) {
314
			return '';
315
		}
316
317
		// If the location has not been labeled, do not show the location.
318
		if ( ! $meta_values['label'] ) {
319
			return '';
320
		}
321
322
		$html  = '<div class="post-geo-location-label geo-chip">';
323
		$html .= '<span class="dashicons dashicons-location" style="vertical-align: text-top;"></span> ';
324
		$html .= esc_html( $meta_values['label'] );
325
		$html .= '</div>';
326
327
		/**
328
		 * Allow modification or replacement of the default geo-location display HTML.
329
		 *
330
		 * @module geo-location
331
		 *
332
		 * @param array $html The default HTML for displaying a geo-location label.
333
		 * @param array $geo_data An array containing "latitude", "longitude" and "label".
334
		 */
335
		$html = apply_filters( 'jetpack_geo_location_display', $html, $meta_values );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $meta_values.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
336
337
		return $html;
338
	}
339
340
	/**
341
	 * Get the ID of the current global post object, if available.  Otherwise, return null.
342
	 *
343
	 * This isolates the access of the global scope to this single method, making it easier to
344
	 * safeguard against unexpected missing $post objects in other hook functions.
345
	 *
346
	 * @return int|null
347
	 */
348
	public function get_post_id() {
349
		global $post;
350
351
		if ( ! isset( $post ) || ! $post || ! is_object( $post ) || ! isset( $post->ID ) ) {
352
			return null;
353
		}
354
355
		return $post->ID;
356
	}
357
358
	/**
359
	 * This method always returns an array with the following structure:
360
	 *
361
	 * array(is_public => bool, latitude => float, longitude => float, label => string, is_populated => bool)
362
	 *
363
	 * So, regardless of whether your post actually has values in postmeta for the geo-location fields,
364
	 * you can be sure that you can reference those array keys in calling code without having to juggle
365
	 * isset(), array_key_exists(), etc.
366
	 *
367
	 * Mocking this method during testing can also be useful for testing output and logic in various
368
	 * hook functions.
369
	 *
370
	 * @param integer $post_id
371
	 *
372
	 * @return array A predictably structured array representing the meta values for the supplied post ID.
373
	 */
374
	public function get_meta_values( $post_id ) {
375
		$meta_values = array(
376
			'is_public'    => (bool) $this->sanitize_public( $this->get_meta_value( $post_id, 'public' ) ),
377
			'latitude'     => $this->sanitize_coordinate( $this->get_meta_value( $post_id, 'latitude' ) ),
378
			'longitude'    => $this->sanitize_coordinate( $this->get_meta_value( $post_id, 'longitude' ) ),
379
			'label'        => trim( $this->get_meta_value( $post_id, 'address' ) ),
380
			'is_populated' => false,
381
		);
382
383
		if ( $meta_values['latitude'] && $meta_values['longitude'] && $meta_values['label'] ) {
384
			$meta_values['is_populated'] = true;
385
		}
386
387
		return $meta_values;
388
	}
389
390
	/**
391
	 * This function wraps get_post_meta() to enable us to keep the "geo_" prefix isolated to a single
392
	 * location in the code and to assist in mocking during testing.
393
	 *
394
	 * @param integer $post_id
395
	 * @param string  $meta_field_name
396
	 *
397
	 * @return mixed
398
	 */
399
	public function get_meta_value( $post_id, $meta_field_name ) {
400
		if ( ! $post_id ) {
401
			return null;
402
		}
403
404
		return get_post_meta( $post_id, 'geo_' . $meta_field_name, true );
405
	}
406
407
	/**
408
	 * Check to see if the current filter is the get_the_excerpt filter.
409
	 *
410
	 * Just checking current_filter() here is not adequate because current_filter() only looks
411
	 * at the last element in the $wp_current_filter array.  In the context of rendering an
412
	 * excerpt, however, both get_the_excerpt and the_content are present in that array.
413
	 *
414
	 * @return bool
415
	 */
416
	public function is_currently_excerpt_filter() {
417
		if ( ! isset( $GLOBALS['wp_current_filter'] ) ) {
418
			return false;
419
		}
420
421
		$current_filters = (array) $GLOBALS['wp_current_filter'];
422
423
		return in_array( 'get_the_excerpt', $current_filters, true );
424
	}
425
}
426
427
Jetpack_Geo_Location::init();
428