PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | extensions/blocks/map/map.php +328 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,328 @@
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 + // Tracking::record_user_event() prefixes the source itself; tracks_record_event() does not.
56 + tracks_record_event( wp_get_current_user(), 'jetpack_' . $event_name );
57 + } elseif ( ( new Host() )->is_woa_site() && Jetpack::is_connection_ready() ) {
58 + $tracking = new Tracking();
59 + $tracking->record_user_event( $event_name );
60 + }
61 +}
62 +
63 +/**
64 + * Function to determine which map provider to choose
65 + *
66 + * @param string $html The block's HTML - needed for the class name.
67 + *
68 + * @return string The name of the map provider.
69 + */
70 +function get_map_provider( $html ) {
71 + $mapbox_styles = array( 'is-style-terrain' );
72 + // return mapbox if html contains one of the mapbox styles
73 + foreach ( $mapbox_styles as $style ) {
74 + if ( str_contains( $html, $style ) ) {
75 + return 'mapbox';
76 + }
77 + }
78 +
79 + // you can override the map provider with a cookie
80 + if ( isset( $_COOKIE['map_provider'] ) ) {
81 + return sanitize_text_field( wp_unslash( $_COOKIE['map_provider'] ) );
82 + }
83 +
84 + // if we don't apply the filters & default to mapbox
85 + return apply_filters( 'wpcom_map_block_map_provider', 'mapbox' );
86 +}
87 +
88 +/**
89 + * Map block registration/dependency declaration.
90 + *
91 + * @param array $attr Array containing the map block attributes.
92 + * @param string $content String containing the map block content.
93 + *
94 + * @return string
95 + */
96 +function load_assets( $attr, $content ) {
97 + $access_token = Jetpack_Mapbox_Helper::get_access_token();
98 + wpcom_load_event( $access_token['source'] );
99 +
100 + if ( Blocks::is_amp_request() ) {
101 + static $map_block_counter = array();
102 +
103 + $id = get_the_ID();
104 + if ( ! isset( $map_block_counter[ $id ] ) ) {
105 + $map_block_counter[ $id ] = 0;
106 + }
107 + ++$map_block_counter[ $id ];
108 +
109 + $iframe_url = add_query_arg(
110 + array(
111 + 'map-block-counter' => absint( $map_block_counter[ $id ] ),
112 + 'map-block-post-id' => $id,
113 + ),
114 + get_permalink()
115 + );
116 +
117 + $placeholder = preg_replace( '/(?<=<div\s)/', 'placeholder ', $content );
118 +
119 + return sprintf(
120 + '<amp-iframe src="%s" width="%d" height="%d" layout="responsive" allowfullscreen sandbox="allow-scripts">%s</amp-iframe>',
121 + esc_url( $iframe_url ),
122 + 4,
123 + 3,
124 + $placeholder
125 + );
126 + }
127 +
128 + Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
129 +
130 + $map_provider = get_map_provider( $content );
131 +
132 + return rebuild_map_block_div( $attr, $content, $map_provider, $access_token );
133 +}
134 +
135 +/**
136 + * Rebuild the map block div to move large JSON data from HTML attributes
137 + * into an inline JS payload, keeping the DOM lightweight.
138 + *
139 + * @param array $attr Array containing the map block attributes.
140 + * @param string $content String containing the map block content.
141 + * @param string $map_provider The map provider (mapbox or mapkit).
142 + * @param array $access_token The Mapbox/MapKit access token data.
143 + *
144 + * @return string
145 + */
146 +function rebuild_map_block_div( $attr, $content, $map_provider, $access_token ) {
147 + static $did_inline = false;
148 +
149 + $block_id = wp_unique_id( 'jp-map-' );
150 +
151 + // Extract map-style from the saved HTML since it's no longer a block attribute
152 + // (was deprecated and converted to CSS class names like is-style-terrain).
153 + $map_style = 'default';
154 + if ( preg_match( '/data-map-style="([^"]*)"/', $content, $matches ) ) {
155 + $map_style = $matches[1];
156 + }
157 +
158 + $classes = array( 'wp-block-jetpack-map' );
159 + if ( ! empty( $attr['align'] ) ) {
160 + $classes[] = 'align' . $attr['align'];
161 + }
162 + if ( ! empty( $attr['className'] ) ) {
163 + $classes[] = $attr['className'];
164 + }
165 +
166 + // Keep the tag small: no giant JSON in attributes.
167 + $data_attrs = array(
168 + 'class' => implode( ' ', $classes ),
169 + 'data-map-id' => $block_id,
170 + 'data-map-provider' => $map_provider,
171 + 'data-api-key' => $access_token['key'],
172 + 'data-map-style' => $map_style,
173 + );
174 +
175 + if ( 'mapkit' === $map_provider ) {
176 + $data_attrs['data-blog-id'] = \Jetpack_Options::get_option( 'id' );
177 + }
178 +
179 + // Put everything else in the JS payload (including points).
180 + $payload = array(
181 + 'points' => $attr['points'] ?? null,
182 + 'zoom' => $attr['zoom'] ?? null,
183 + 'mapCenter' => $attr['mapCenter'] ?? null,
184 + 'markerColor' => $attr['markerColor'] ?? null,
185 + 'scrollToZoom' => $attr['scrollToZoom'] ?? null,
186 + 'mapDetails' => $attr['mapDetails'] ?? null,
187 + 'mapHeight' => $attr['mapHeight'] ?? null,
188 + 'showFullscreenButton' => $attr['showFullscreenButton'] ?? null,
189 + 'mapStyle' => $map_style,
190 + );
191 +
192 + $handle = 'jetpack-block-map';
193 +
194 + // Seed global once.
195 + if ( ! $did_inline ) {
196 + wp_add_inline_script( $handle, 'window.JetpackMapBlockData = window.JetpackMapBlockData || {};', 'before' );
197 + $did_inline = true;
198 + }
199 +
200 + // Add this block's payload keyed by ID.
201 + wp_add_inline_script(
202 + $handle,
203 + 'window.JetpackMapBlockData[' . wp_json_encode( $block_id, JSON_HEX_TAG | JSON_HEX_AMP ) . '] = ' . wp_json_encode( $payload, JSON_HEX_TAG | JSON_HEX_AMP ) . ';',
204 + 'before'
205 + );
206 +
207 + $div_open = '<div';
208 + foreach ( $data_attrs as $key => $value ) {
209 + $div_open .= ' ' . $key . '="' . esc_attr( $value ) . '"';
210 + }
211 + $div_open .= '>';
212 +
213 + $result = preg_replace( '/<div[^>]*>/', $div_open, $content, 1 );
214 + return $result ?? $content;
215 +}
216 +
217 +/**
218 + * Render a page containing only a single Map block.
219 + */
220 +function render_single_block_page() {
221 + // phpcs:ignore WordPress.Security.NonceVerification
222 + $map_block_counter = isset( $_GET['map-block-counter'] ) ? absint( $_GET['map-block-counter'] ) : null;
223 + // phpcs:ignore WordPress.Security.NonceVerification
224 + $map_block_post_id = isset( $_GET['map-block-post-id'] ) ? absint( $_GET['map-block-post-id'] ) : null;
225 +
226 + if ( ! $map_block_counter || ! $map_block_post_id ) {
227 + return;
228 + }
229 +
230 + /* Create an array of all root-level DIVs that are Map Blocks */
231 + $post = get_post( $map_block_post_id );
232 +
233 + if ( ! class_exists( 'DOMDocument' ) ) {
234 + return;
235 + }
236 +
237 + $post_html = new \DOMDocument();
238 + /** This filter is already documented in core/wp-includes/post-template.php */
239 + $content = apply_filters( 'the_content', $post->post_content );
240 +
241 + // Return early if empty to prevent DOMDocument::loadHTML fatal.
242 + if ( empty( $content ) ) {
243 + return;
244 + }
245 +
246 + /* Suppress warnings */
247 + libxml_use_internal_errors( true );
248 + @$post_html->loadHTML( $content ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
249 + libxml_use_internal_errors( false );
250 +
251 + $xpath = new \DOMXPath( $post_html );
252 + $container = $xpath->query( '//div[ contains( @class, "wp-block-jetpack-map" ) ]' )->item( $map_block_counter - 1 );
253 +
254 + /* Check that we have a block matching the counter position */
255 + if ( ! $container ) {
256 + return;
257 + }
258 +
259 + /* Compile scripts and styles */
260 + ob_start();
261 +
262 + add_filter( 'jetpack_is_amp_request', '__return_false' );
263 +
264 + Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
265 + wp_scripts()->do_items();
266 + wp_styles()->do_items();
267 +
268 + add_filter( 'jetpack_is_amp_request', '__return_true' );
269 +
270 + $head_content = ob_get_clean();
271 +
272 + /* Put together a new complete document containing only the requested block markup and the scripts/styles needed to render it */
273 + $block_markup = $post_html->saveHTML( $container );
274 + $access_token = Jetpack_Mapbox_Helper::get_access_token();
275 + $page_html = sprintf(
276 + '<!DOCTYPE html><head><style>html, body { margin: 0; padding: 0; }</style>%s</head><body>%s</body>',
277 + $head_content,
278 + preg_replace( '/(?<=<div\s)/', 'data-api-key="' . esc_attr( $access_token['key'] ) . '" ', $block_markup, 1 )
279 + );
280 + echo $page_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
281 + exit( 0 );
282 +}
283 +add_action( 'wp', __NAMESPACE__ . '\render_single_block_page' );
284 +
285 +/**
286 + * Helper function to generate the markup of the block in PHP.
287 + *
288 + * @param Array $points - Array containing geo location points.
289 + *
290 + * @return string Markup for the jetpack/map block.
291 + */
292 +function map_block_from_geo_points( $points ) {
293 + $map_block_data = array(
294 + 'points' => $points,
295 + 'zoom' => 8,
296 + 'mapCenter' => array(
297 + 'lng' => $points[0]['coordinates']['longitude'],
298 + 'lat' => $points[0]['coordinates']['latitude'],
299 + ),
300 + );
301 +
302 + $list_items = array_map(
303 + function ( $point ) {
304 + $link = add_query_arg(
305 + array(
306 + 'api' => 1,
307 + 'query' => $point['coordinates']['latitude'] . ',' . $point['coordinates']['longitude'],
308 + ),
309 + 'https://www.google.com/maps/search/'
310 + );
311 + return sprintf( '<li><a href="%s">%s</a></li>', esc_url( $link ), $point['title'] );
312 + },
313 + $points
314 + );
315 +
316 + $map_block = '<!-- wp:jetpack/map ' . wp_json_encode( $map_block_data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ' -->' . PHP_EOL;
317 + $map_block .= sprintf(
318 + '<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">',
319 + esc_attr( wp_json_encode( $map_block_data['points'], JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ),
320 + $map_block_data['zoom'],
321 + esc_attr( wp_json_encode( $map_block_data['mapCenter'], JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) )
322 + );
323 + $map_block .= '<ul>' . implode( "\n", $list_items ) . '</ul>';
324 + $map_block .= '</div>' . PHP_EOL;
325 + $map_block .= '<!-- /wp:jetpack/map -->';
326 +
327 + return $map_block;
328 +}