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 | modules/widgets/contact-info.php +503 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,503 @@
1 +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 +
3 +// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
4 +
5 +use Automattic\Jetpack\Assets;
6 +use Automattic\Jetpack\Redirect;
7 +
8 +if ( ! defined( 'ABSPATH' ) ) {
9 + exit( 0 );
10 +}
11 +
12 +if ( ! class_exists( 'Jetpack_Contact_Info_Widget' ) ) {
13 +
14 + /**
15 + * Register Contact_Info_Widget widget
16 + */
17 + function jetpack_contact_info_widget_init() {
18 + register_widget( 'Jetpack_Contact_Info_Widget' );
19 + }
20 +
21 + add_action( 'widgets_init', 'jetpack_contact_info_widget_init' );
22 +
23 + /**
24 + * Makes a custom Widget for displaying Restaurant Location/Map, Hours, and Contact Info available.
25 + *
26 + * @package WordPress
27 + */
28 + class Jetpack_Contact_Info_Widget extends WP_Widget {
29 +
30 + /**
31 + * Constructor
32 + */
33 + public function __construct() {
34 + global $pagenow;
35 +
36 + $widget_ops = array(
37 + 'classname' => 'widget_contact_info',
38 + 'description' => __( 'Display a map with your location, hours, and contact information.', 'jetpack' ),
39 + 'customize_selective_refresh' => true,
40 + 'show_instance_in_rest' => true,
41 + );
42 + parent::__construct(
43 + 'widget_contact_info',
44 + /** This filter is documented in modules/widgets/facebook-likebox.php */
45 + apply_filters( 'jetpack_widget_name', __( 'Contact Info & Map', 'jetpack' ) ),
46 + $widget_ops
47 + );
48 + $this->alt_option_name = 'widget_contact_info';
49 +
50 + if ( is_customize_preview() ) {
51 + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
52 + } elseif ( 'widgets.php' === $pagenow ) {
53 + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
54 + }
55 +
56 + add_action( 'wp_ajax_customize-contact-info-api-key', array( $this, 'ajax_check_api_key' ) );
57 + add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_widget_in_block_editor' ) );
58 + }
59 +
60 + /**
61 + * Remove the "Contact info and Map" widget from the Legacy Widget block
62 + *
63 + * @param array $widget_types List of widgets that are currently removed from the Legacy Widget block.
64 + * @return array $widget_types New list of widgets that will be removed.
65 + */
66 + public function hide_widget_in_block_editor( $widget_types ) {
67 + $widget_types[] = 'widget_contact_info';
68 + return $widget_types;
69 + }
70 +
71 + /**
72 + * Enqueue scripts and styles.
73 + */
74 + public function enqueue_scripts() {
75 + wp_enqueue_style(
76 + 'contact-info-map-css',
77 + plugins_url( 'contact-info/contact-info-map.css', __FILE__ ),
78 + array(),
79 + JETPACK__VERSION
80 + );
81 + }
82 +
83 + /**
84 + * Return an associative array of default values
85 + *
86 + * These values are used in new widgets.
87 + *
88 + * @return array Array of default values for the Widget's options
89 + */
90 + public function defaults() {
91 + return array(
92 + 'title' => __( 'Hours & Info', 'jetpack' ),
93 + 'address' => __( "3999 Mission Boulevard,\nSan Diego CA 92109", 'jetpack' ),
94 + 'phone' => _x( '1-202-555-1212', 'Example of a phone number', 'jetpack' ),
95 + 'hours' => __( "Lunch: 11am - 2pm \nDinner: M-Th 5pm - 11pm, Fri-Sat:5pm - 1am", 'jetpack' ),
96 + 'email' => null,
97 + 'showmap' => 0,
98 + 'apikey' => null,
99 + 'goodmap' => null,
100 + );
101 + }
102 +
103 + /**
104 + * Outputs the HTML for this widget.
105 + *
106 + * @param array $args An array of standard parameters for widgets in this theme.
107 + * @param array $instance An array of settings for this widget instance.
108 + *
109 + * @return void Echoes it's output
110 + **/
111 + public function widget( $args, $instance ) {
112 + $instance = wp_parse_args( $instance, $this->defaults() );
113 +
114 + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
115 +
116 + if ( '' !== $instance['title'] ) {
117 + echo $args['before_title'] . $instance['title'] . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
118 + }
119 +
120 + /**
121 + * Fires at the beginning of the Contact Info widget, after the title.
122 + *
123 + * @module widgets
124 + *
125 + * @since 3.9.2
126 + */
127 + do_action( 'jetpack_contact_info_widget_start' );
128 +
129 + echo '<div itemscope itemtype="http://schema.org/LocalBusiness">';
130 +
131 + if ( '' !== $instance['address'] ) {
132 +
133 + $showmap = $instance['showmap'];
134 + $goodmap = $instance['goodmap'] ?? $this->has_good_map( $instance );
135 +
136 + if ( $showmap && true === $goodmap ) {
137 + /**
138 + * Set a Google Maps API Key.
139 + *
140 + * @since 4.1.0
141 + *
142 + * @param string $api_key Google Maps API Key
143 + */
144 + $api_key = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] );
145 + echo $this->build_map( $instance['address'], $api_key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
146 + } elseif ( $showmap && is_customize_preview() && true !== $goodmap ) {
147 + printf(
148 + '<span class="contact-map-api-error" style="display: block;">%s</span>',
149 + esc_html( $instance['goodmap'] )
150 + );
151 + }
152 +
153 + $map_link = $this->build_map_link( $instance['address'] );
154 +
155 + printf(
156 + '<div class="confit-address" itemscope itemtype="http://schema.org/PostalAddress" itemprop="address"><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a></div>',
157 + esc_url( $map_link ),
158 + str_replace( "\n", '<br/>', esc_html( $instance['address'] ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
159 + );
160 + }
161 +
162 + if ( '' !== $instance['phone'] ) {
163 + echo '<div class="confit-phone"><span itemprop="telephone"><a href="' . esc_url( 'tel:' . $instance['phone'] ) . '">' . esc_html( $instance['phone'] ) . '</a></span></div>';
164 + }
165 +
166 + if (
167 + $instance['email']
168 + && is_email( trim( $instance['email'] ) )
169 + ) {
170 + printf(
171 + '<div class="confit-email"><a href="mailto:%1$s">%1$s</a></div>',
172 + esc_html( $instance['email'] )
173 + );
174 + }
175 +
176 + if ( '' !== $instance['hours'] ) {
177 + printf(
178 + '<div class="confit-hours" itemprop="openingHours">%s</div>',
179 + str_replace( "\n", '<br/>', esc_html( $instance['hours'] ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
180 + );
181 + }
182 +
183 + echo '</div>';
184 +
185 + /**
186 + * Fires at the end of Contact Info widget.
187 + *
188 + * @module widgets
189 + *
190 + * @since 3.9.2
191 + */
192 + do_action( 'jetpack_contact_info_widget_end' );
193 +
194 + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
195 +
196 + /** This action is documented in modules/widgets/gravatar-profile.php */
197 + do_action( 'jetpack_stats_extra', 'widget_view', 'contact_info' );
198 + }
199 +
200 + /**
201 + * Deals with the settings when they are saved by the admin. Here is
202 + * where any validation should be dealt with.
203 + *
204 + * @param array $new_instance New configuration values.
205 + * @param array $old_instance Old configuration values.
206 + *
207 + * @return array
208 + */
209 + public function update( $new_instance, $old_instance ) {
210 +
211 + $instance = array();
212 + $instance['title'] = wp_kses( $new_instance['title'] ?? '', array() );
213 + $instance['address'] = wp_kses( $new_instance['address'] ?? '', array() );
214 + $instance['phone'] = wp_kses( $new_instance['phone'] ?? '', array() );
215 + $instance['email'] = wp_kses( $new_instance['email'] ?? '', array() );
216 + $instance['hours'] = wp_kses( $new_instance['hours'] ?? '', array() );
217 + $instance['apikey'] = wp_kses( $new_instance['apikey'] ?? $old_instance['apikey'] ?? '', array() );
218 +
219 + if ( ! isset( $new_instance['showmap'] ) ) {
220 + $instance['showmap'] = 0;
221 + } else {
222 + $instance['showmap'] = (int) $new_instance['showmap'];
223 + }
224 +
225 + $instance['goodmap'] = $this->update_goodmap( $old_instance, $instance );
226 +
227 + return $instance;
228 + }
229 +
230 + /**
231 + * Displays the form for this widget on the Widgets page of the WP Admin area.
232 + *
233 + * @param array $instance Instance configuration.
234 + *
235 + * @return string|void
236 + */
237 + public function form( $instance ) {
238 + $instance = wp_parse_args( $instance, $this->defaults() );
239 + /** This filter is documented in modules/widgets/contact-info.php */
240 + $apikey = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] );
241 +
242 + wp_enqueue_script(
243 + 'contact-info-admin',
244 + Assets::get_file_url_for_environment(
245 + '_inc/build/widgets/contact-info/contact-info-admin.min.js',
246 + 'modules/widgets/contact-info/contact-info-admin.js'
247 + ),
248 + array( 'jquery' ),
249 + 20160727,
250 + false
251 + );
252 +
253 + if ( is_customize_preview() ) {
254 + $customize_contact_info_api_key_nonce = wp_create_nonce( 'customize_contact_info_api_key' );
255 + wp_localize_script(
256 + 'contact-info-admin',
257 + 'contact_info_api_key_ajax_obj',
258 + array( 'nonce' => $customize_contact_info_api_key_nonce )
259 + );
260 + }
261 +
262 + ?>
263 + <p>
264 + <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label>
265 + <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
266 + </p>
267 +
268 + <p>
269 + <label for="<?php echo esc_attr( $this->get_field_id( 'address' ) ); ?>"><?php esc_html_e( 'Address:', 'jetpack' ); ?></label>
270 + <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'address' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'address' ) ); ?>"><?php echo esc_textarea( $instance['address'] ); ?></textarea>
271 + </p>
272 +
273 + <p>
274 + <input class="jp-contact-info-showmap" id="<?php echo esc_attr( $this->get_field_id( 'showmap' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'showmap' ) ); ?>" value="1" type="checkbox" <?php checked( $instance['showmap'], 1 ); ?> />
275 + <label for="<?php echo esc_attr( $this->get_field_id( 'showmap' ) ); ?>"><?php esc_html_e( 'Show map', 'jetpack' ); ?></label>
276 + </p>
277 +
278 + <?php if ( ! has_filter( 'jetpack_google_maps_api_key' ) || false === apply_filters( 'jetpack_google_maps_api_key', false ) ) { ?>
279 +
280 + <p class="jp-contact-info-admin-map" style="<?php echo $instance['showmap'] ? '' : 'display: none;'; ?>">
281 + <label for="<?php echo esc_attr( $this->get_field_id( 'apikey' ) ); ?>">
282 + <?php esc_html_e( 'Google Maps API Key', 'jetpack' ); ?>
283 + <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'apikey' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'apikey' ) ); ?>" type="text" value="<?php echo esc_attr( $apikey ); ?>" />
284 + <br />
285 + <small>
286 + <?php
287 + printf(
288 + wp_kses(
289 + /* Translators: placeholder is a URL to support documentation. */
290 + __( 'Google now requires an API key to use their maps on your site. <a href="%s">See our documentation</a> for instructions on acquiring a key.', 'jetpack' ),
291 + array(
292 + 'a' => array(
293 + 'href' => true,
294 + ),
295 + )
296 + ),
297 + ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? 'https://wordpress.com/support/widgets/contact-info/' : esc_url( Redirect::get_url( 'jetpack-support-extra-sidebar-widgets-contact-info-widget' ) )
298 + );
299 + ?>
300 + </small>
301 + </label>
302 + </p>
303 +
304 + <?php } else { ?>
305 +
306 + <input type="hidden" id="<?php echo esc_attr( $this->get_field_id( 'apikey' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'apikey' ) ); ?>" value="<?php echo esc_attr( $apikey ); ?>" />
307 +
308 + <?php } // end if jetpack_google_maps_api_key check. ?>
309 +
310 + <p class="jp-contact-info-admin-map jp-contact-info-embed-map" style="<?php echo $instance['showmap'] ? '' : 'display: none;'; ?>">
311 + <?php
312 + if ( ! is_customize_preview() && true === $instance['goodmap'] ) {
313 + echo $this->build_map( $instance['address'], $apikey ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
314 + } elseif ( true !== $instance['goodmap'] && ! empty( $instance['goodmap'] ) ) {
315 + printf(
316 + '<span class="button-link-delete">%s</span>',
317 + esc_html( $instance['goodmap'] )
318 + );
319 + }
320 + ?>
321 + </p>
322 +
323 + <p>
324 + <label for="<?php echo esc_attr( $this->get_field_id( 'phone' ) ); ?>"><?php esc_html_e( 'Phone:', 'jetpack' ); ?></label>
325 + <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'phone' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'phone' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['phone'] ); ?>" />
326 + </p>
327 +
328 + <p>
329 + <label for="<?php echo esc_attr( $this->get_field_id( 'email' ) ); ?>"><?php esc_html_e( 'Email Address:', 'jetpack' ); ?></label>
330 + <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'email' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'email' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['email'] ); ?>" />
331 + </p>
332 +
333 + <p>
334 + <label for="<?php echo esc_attr( $this->get_field_id( 'hours' ) ); ?>"><?php esc_html_e( 'Hours:', 'jetpack' ); ?></label>
335 + <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'hours' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'hours' ) ); ?>"><?php echo esc_textarea( $instance['hours'] ); ?></textarea>
336 + </p>
337 +
338 + <?php
339 + }
340 +
341 + /**
342 + * Generate a Google Maps link for the supplied address.
343 + *
344 + * @param string $address Address to link to.
345 + *
346 + * @return string
347 + */
348 + private function build_map_link( $address ) {
349 + // Google map urls have lots of available params but zoom (z) and query (q) are enough.
350 + return 'https://maps.google.com/maps?z=16&q=' . $this->urlencode_address( $address );
351 + }
352 +
353 + /**
354 + * Builds map display HTML code from the supplied address.
355 + *
356 + * @param string $address Address.
357 + * @param string $api_key API Key.
358 + *
359 + * @return string HTML of the map.
360 + */
361 + private function build_map( $address, $api_key = null ) {
362 + $this->enqueue_scripts();
363 + $src = add_query_arg( 'q', rawurlencode( $address ), 'https://www.google.com/maps/embed/v1/place' );
364 + if ( ! empty( $api_key ) ) {
365 + $src = add_query_arg( 'key', $api_key, $src );
366 + }
367 +
368 + $height = 216;
369 +
370 + $iframe_attributes = sprintf(
371 + ' height="%d" frameborder="0" src="%s" title="%s" class="contact-map"',
372 + esc_attr( $height ),
373 + esc_url( $src ),
374 + __( 'Google Map Embed', 'jetpack' )
375 + );
376 +
377 + $iframe_html = sprintf( '<iframe width="600" %s></iframe>', $iframe_attributes );
378 +
379 + if (
380 + ! class_exists( 'Jetpack_AMP_Support' )
381 + || ! Jetpack_AMP_Support::is_amp_request()
382 + ) {
383 + return $iframe_html;
384 + }
385 +
386 + $amp_iframe_html = sprintf( '<amp-iframe layout="fixed-height" width="auto" sandbox="allow-scripts allow-same-origin" %s>', $iframe_attributes );
387 +
388 + // Add placeholder to avoid AMP error: <amp-iframe> elements must be positioned outside the first 75% of the viewport or 600px from the top (whichever is smaller).
389 + $amp_iframe_html .= sprintf( '<span placeholder>%s</span>', esc_html__( 'Loading map&hellip;', 'jetpack' ) );
390 +
391 + // Add original iframe as fallback in case JavaScript is disabled.
392 + $amp_iframe_html .= sprintf( '<noscript>%s</noscript>', $iframe_html );
393 +
394 + $amp_iframe_html .= '</amp-iframe>';
395 + return $amp_iframe_html;
396 + }
397 +
398 + /**
399 + * Encode an URL
400 + *
401 + * @param string $address The URL to encode.
402 + *
403 + * @return string The encoded URL
404 + */
405 + private function urlencode_address( $address ) {
406 +
407 + $address = strtolower( $address );
408 + // Get rid of any unwanted whitespace.
409 + $address = preg_replace( '/\s+/', ' ', trim( $address ) );
410 + // Use + not %20.
411 + $address = str_ireplace( ' ', '+', $address );
412 + return rawurlencode( $address );
413 + }
414 +
415 + /**
416 + * Returns the instance's updated 'goodmap' value.
417 + *
418 + * @param array $old_instance Old configuration values.
419 + * @param array $instance Current configuration values.
420 + *
421 + * @return bool|string The instance's updated 'goodmap' value. The value is true if
422 + * $instance can display a good map. If not, returns an error message.
423 + */
424 + private function update_goodmap( $old_instance, $instance ) {
425 + /*
426 + * If we have no address or don't want to show a map,
427 + * no need to check if the map is valid.
428 + */
429 + if ( empty( $instance['address'] ) || 0 === $instance['showmap'] ) {
430 + return false;
431 + }
432 +
433 + /*
434 + * If there have been any changes that may impact the map in the widget
435 + * (adding an address, address changes, new API key, API key change)
436 + * then we want to check whether our map can be displayed again.
437 + */
438 + if (
439 + ! isset( $instance['goodmap'] )
440 + || ! isset( $old_instance['address'] )
441 + || $this->urlencode_address( $old_instance['address'] ) !== $this->urlencode_address( $instance['address'] )
442 + || ! isset( $old_instance['apikey'] )
443 + || $old_instance['apikey'] !== $instance['apikey']
444 + ) {
445 + return $this->has_good_map( $instance );
446 + } else {
447 + return $instance['goodmap'];
448 + }
449 + }
450 +
451 + /**
452 + * Check if the instance has a valid Map location.
453 + *
454 + * @param array $instance Widget instance configuration.
455 + *
456 + * @return bool|string Whether or not there is a valid map. If not, return an error message.
457 + */
458 + private function has_good_map( $instance ) {
459 + /** This filter is documented in modules/widgets/contact-info.php */
460 + $api_key = apply_filters( 'jetpack_google_maps_api_key', $instance['apikey'] );
461 + if ( ! empty( $api_key ) ) {
462 + $path = add_query_arg(
463 + array(
464 + 'q' => rawurlencode( $instance['address'] ),
465 + 'key' => $api_key,
466 + ),
467 + 'https://www.google.com/maps/embed/v1/place'
468 + );
469 + $wp_remote_get_args = array(
470 + 'headers' => array( 'Referer' => home_url() ),
471 + );
472 + $response = wp_remote_get( esc_url_raw( $path ), $wp_remote_get_args );
473 +
474 + if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
475 + return true;
476 + } else {
477 + return wp_remote_retrieve_body( $response );
478 + }
479 + }
480 +
481 + return __( 'Please enter a valid Google API Key.', 'jetpack' );
482 + }
483 +
484 + /**
485 + * Check the Google Maps API key after an Ajax call from the widget's admin form in
486 + * the Customizer preview.
487 + */
488 + public function ajax_check_api_key() {
489 + if ( isset( $_POST['apikey'] ) ) {
490 + if ( check_ajax_referer( 'customize_contact_info_api_key' ) && current_user_can( 'customize' ) ) {
491 + $apikey = wp_kses( wp_unslash( $_POST['apikey'] ), array() );
492 + $default_instance = $this->defaults();
493 + $default_instance['apikey'] = $apikey;
494 + // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
495 + wp_send_json( array( 'result' => esc_html( $this->has_good_map( $default_instance ) ) ), null, JSON_UNESCAPED_SLASHES );
496 + }
497 + } else {
498 + wp_die();
499 + }
500 + }
501 + }
502 +
503 +}