説明なし

contact-info.php 17KB

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