PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / extensions / blocks / map / map.php
jetpack / extensions / blocks / map Last commit date
map.php 3 months ago
map.php
328 lines
1 <?php
2 /**
3 * Map block.
4 *
5 * @since 6.8.0
6 *
7 * @package automattic/jetpack
8 */
9
10 namespace Automattic\Jetpack\Extensions\Map;
11
12 use Automattic\Jetpack\Blocks;
13 use Automattic\Jetpack\Status\Host;
14 use Automattic\Jetpack\Tracking;
15 use Jetpack;
16 use Jetpack_Gutenberg;
17 use Jetpack_Mapbox_Helper;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit( 0 );
21 }
22
23 if ( ! class_exists( 'Jetpack_Mapbox_Helper' ) ) {
24 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-mapbox-helper.php';
25 }
26
27 /**
28 * Registers the block for use in Gutenberg
29 * This is done via an action so that we can disable
30 * registration if we need to.
31 */
32 function register_block() {
33 Blocks::jetpack_register_block(
34 __DIR__,
35 array(
36 'render_callback' => __NAMESPACE__ . '\load_assets',
37 )
38 );
39 }
40 add_action( 'init', __NAMESPACE__ . '\register_block' );
41
42 /**
43 * Record a Tracks event every time the Map block is loaded on WordPress.com and Atomic.
44 *
45 * @param string $access_token_source The Mapbox API access token source.
46 */
47 function wpcom_load_event( $access_token_source ) {
48 if ( 'wpcom' !== $access_token_source ) {
49 return;
50 }
51
52 $event_name = 'map_block_mapbox_wpcom_key_load';
53 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
54 require_lib( 'tracks/client' );
55 tracks_record_event( wp_get_current_user(), $event_name );
56 } elseif ( ( new Host() )->is_woa_site() && Jetpack::is_connection_ready() ) {
57 $tracking = new Tracking();
58 $tracking->record_user_event( $event_name );
59 }
60 }
61
62 /**
63 * Function to determine which map provider to choose
64 *
65 * @param string $html The block's HTML - needed for the class name.
66 *
67 * @return string The name of the map provider.
68 */
69 function get_map_provider( $html ) {
70 $mapbox_styles = array( 'is-style-terrain' );
71 // return mapbox if html contains one of the mapbox styles
72 foreach ( $mapbox_styles as $style ) {
73 if ( str_contains( $html, $style ) ) {
74 return 'mapbox';
75 }
76 }
77
78 // you can override the map provider with a cookie
79 if ( isset( $_COOKIE['map_provider'] ) ) {
80 return sanitize_text_field( wp_unslash( $_COOKIE['map_provider'] ) );
81 }
82
83 // if we don't apply the filters & default to mapbox
84 return apply_filters( 'wpcom_map_block_map_provider', 'mapbox' );
85 }
86
87 /**
88 * Map block registration/dependency declaration.
89 *
90 * @param array $attr Array containing the map block attributes.
91 * @param string $content String containing the map block content.
92 *
93 * @return string
94 */
95 function load_assets( $attr, $content ) {
96 $access_token = Jetpack_Mapbox_Helper::get_access_token();
97 wpcom_load_event( $access_token['source'] );
98
99 if ( Blocks::is_amp_request() ) {
100 static $map_block_counter = array();
101
102 $id = get_the_ID();
103 if ( ! isset( $map_block_counter[ $id ] ) ) {
104 $map_block_counter[ $id ] = 0;
105 }
106 ++$map_block_counter[ $id ];
107
108 $iframe_url = add_query_arg(
109 array(
110 'map-block-counter' => absint( $map_block_counter[ $id ] ),
111 'map-block-post-id' => $id,
112 ),
113 get_permalink()
114 );
115
116 $placeholder = preg_replace( '/(?<=<div\s)/', 'placeholder ', $content );
117
118 return sprintf(
119 '<amp-iframe src="%s" width="%d" height="%d" layout="responsive" allowfullscreen sandbox="allow-scripts">%s</amp-iframe>',
120 esc_url( $iframe_url ),
121 4,
122 3,
123 $placeholder
124 );
125 }
126
127 Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
128
129 $map_provider = get_map_provider( $content );
130
131 return rebuild_map_block_div( $attr, $content, $map_provider, $access_token );
132 }
133
134 /**
135 * Rebuild the map block div to move large JSON data from HTML attributes
136 * into an inline JS payload, keeping the DOM lightweight.
137 *
138 * @param array $attr Array containing the map block attributes.
139 * @param string $content String containing the map block content.
140 * @param string $map_provider The map provider (mapbox or mapkit).
141 * @param array $access_token The Mapbox/MapKit access token data.
142 *
143 * @return string
144 */
145 function rebuild_map_block_div( $attr, $content, $map_provider, $access_token ) {
146 static $did_inline = false;
147
148 $block_id = wp_unique_id( 'jp-map-' );
149
150 // Extract map-style from the saved HTML since it's no longer a block attribute
151 // (was deprecated and converted to CSS class names like is-style-terrain).
152 $map_style = 'default';
153 if ( preg_match( '/data-map-style="([^"]*)"/', $content, $matches ) ) {
154 $map_style = $matches[1];
155 }
156
157 $classes = array( 'wp-block-jetpack-map' );
158 if ( ! empty( $attr['align'] ) ) {
159 $classes[] = 'align' . $attr['align'];
160 }
161 if ( ! empty( $attr['className'] ) ) {
162 $classes[] = $attr['className'];
163 }
164
165 // Keep the tag small: no giant JSON in attributes.
166 $data_attrs = array(
167 'class' => implode( ' ', $classes ),
168 'data-map-id' => $block_id,
169 'data-map-provider' => $map_provider,
170 'data-api-key' => $access_token['key'],
171 'data-map-style' => $map_style,
172 );
173
174 if ( 'mapkit' === $map_provider ) {
175 $data_attrs['data-blog-id'] = \Jetpack_Options::get_option( 'id' );
176 }
177
178 // Put everything else in the JS payload (including points).
179 $payload = array(
180 'points' => $attr['points'] ?? null,
181 'zoom' => $attr['zoom'] ?? null,
182 'mapCenter' => $attr['mapCenter'] ?? null,
183 'markerColor' => $attr['markerColor'] ?? null,
184 'scrollToZoom' => $attr['scrollToZoom'] ?? null,
185 'mapDetails' => $attr['mapDetails'] ?? null,
186 'mapHeight' => $attr['mapHeight'] ?? null,
187 'showFullscreenButton' => $attr['showFullscreenButton'] ?? null,
188 'mapStyle' => $map_style,
189 );
190
191 $handle = 'jetpack-block-map';
192
193 // Seed global once.
194 if ( ! $did_inline ) {
195 wp_add_inline_script( $handle, 'window.JetpackMapBlockData = window.JetpackMapBlockData || {};', 'before' );
196 $did_inline = true;
197 }
198
199 // Add this block's payload keyed by ID.
200 wp_add_inline_script(
201 $handle,
202 'window.JetpackMapBlockData[' . wp_json_encode( $block_id, JSON_HEX_TAG | JSON_HEX_AMP ) . '] = ' . wp_json_encode( $payload, JSON_HEX_TAG | JSON_HEX_AMP ) . ';',
203 'before'
204 );
205
206 $div_open = '<div';
207 foreach ( $data_attrs as $key => $value ) {
208 $div_open .= ' ' . $key . '="' . esc_attr( $value ) . '"';
209 }
210 $div_open .= '>';
211
212 $result = preg_replace( '/<div[^>]*>/', $div_open, $content, 1 );
213 return $result ?? $content;
214 }
215
216 /**
217 * Render a page containing only a single Map block.
218 */
219 function render_single_block_page() {
220 // phpcs:ignore WordPress.Security.NonceVerification
221 $map_block_counter = isset( $_GET['map-block-counter'] ) ? absint( $_GET['map-block-counter'] ) : null;
222 // phpcs:ignore WordPress.Security.NonceVerification
223 $map_block_post_id = isset( $_GET['map-block-post-id'] ) ? absint( $_GET['map-block-post-id'] ) : null;
224
225 if ( ! $map_block_counter || ! $map_block_post_id ) {
226 return;
227 }
228
229 /* Create an array of all root-level DIVs that are Map Blocks */
230 $post = get_post( $map_block_post_id );
231
232 if ( ! class_exists( 'DOMDocument' ) ) {
233 return;
234 }
235
236 $post_html = new \DOMDocument();
237 /** This filter is already documented in core/wp-includes/post-template.php */
238 $content = apply_filters( 'the_content', $post->post_content );
239
240 // Return early if empty to prevent DOMDocument::loadHTML fatal.
241 if ( empty( $content ) ) {
242 return;
243 }
244
245 /* Suppress warnings */
246 libxml_use_internal_errors( true );
247 @$post_html->loadHTML( $content ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
248 libxml_use_internal_errors( false );
249
250 $xpath = new \DOMXPath( $post_html );
251 $container = $xpath->query( '//div[ contains( @class, "wp-block-jetpack-map" ) ]' )->item( $map_block_counter - 1 );
252
253 /* Check that we have a block matching the counter position */
254 if ( ! $container ) {
255 return;
256 }
257
258 /* Compile scripts and styles */
259 ob_start();
260
261 add_filter( 'jetpack_is_amp_request', '__return_false' );
262
263 Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
264 wp_scripts()->do_items();
265 wp_styles()->do_items();
266
267 add_filter( 'jetpack_is_amp_request', '__return_true' );
268
269 $head_content = ob_get_clean();
270
271 /* Put together a new complete document containing only the requested block markup and the scripts/styles needed to render it */
272 $block_markup = $post_html->saveHTML( $container );
273 $access_token = Jetpack_Mapbox_Helper::get_access_token();
274 $page_html = sprintf(
275 '<!DOCTYPE html><head><style>html, body { margin: 0; padding: 0; }</style>%s</head><body>%s</body>',
276 $head_content,
277 preg_replace( '/(?<=<div\s)/', 'data-api-key="' . esc_attr( $access_token['key'] ) . '" ', $block_markup, 1 )
278 );
279 echo $page_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
280 exit( 0 );
281 }
282 add_action( 'wp', __NAMESPACE__ . '\render_single_block_page' );
283
284 /**
285 * Helper function to generate the markup of the block in PHP.
286 *
287 * @param Array $points - Array containing geo location points.
288 *
289 * @return string Markup for the jetpack/map block.
290 */
291 function map_block_from_geo_points( $points ) {
292 $map_block_data = array(
293 'points' => $points,
294 'zoom' => 8,
295 'mapCenter' => array(
296 'lng' => $points[0]['coordinates']['longitude'],
297 'lat' => $points[0]['coordinates']['latitude'],
298 ),
299 );
300
301 $list_items = array_map(
302 function ( $point ) {
303 $link = add_query_arg(
304 array(
305 'api' => 1,
306 'query' => $point['coordinates']['latitude'] . ',' . $point['coordinates']['longitude'],
307 ),
308 'https://www.google.com/maps/search/'
309 );
310 return sprintf( '<li><a href="%s">%s</a></li>', esc_url( $link ), $point['title'] );
311 },
312 $points
313 );
314
315 $map_block = '<!-- wp:jetpack/map ' . wp_json_encode( $map_block_data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ' -->' . PHP_EOL;
316 $map_block .= sprintf(
317 '<div class="wp-block-jetpack-map" data-map-style="default" data-map-details="true" data-points="%1$s" data-zoom="%2$d" data-map-center="%3$s" data-marker-color="red" data-show-fullscreen-button="true">',
318 esc_attr( wp_json_encode( $map_block_data['points'], JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ),
319 $map_block_data['zoom'],
320 esc_attr( wp_json_encode( $map_block_data['mapCenter'], JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) )
321 );
322 $map_block .= '<ul>' . implode( "\n", $list_items ) . '</ul>';
323 $map_block .= '</div>' . PHP_EOL;
324 $map_block .= '<!-- /wp:jetpack/map -->';
325
326 return $map_block;
327 }
328