PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.2-beta
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 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / extensions / blocks / map / map.php

map.php in Jetpack – WP Security, Backup, Speed, & Growth 16.2-beta, at extensions/blocks/map/map.php

328 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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