wp/wp-admin/includes/class-wp-community-events.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
equal deleted inserted replaced
17:34716fd837a4 18:be944660c56a
    27 	/**
    27 	/**
    28 	 * Stores location data for the user.
    28 	 * Stores location data for the user.
    29 	 *
    29 	 *
    30 	 * @since 4.8.0
    30 	 * @since 4.8.0
    31 	 *
    31 	 *
    32 	 * @var bool|array
    32 	 * @var false|array
    33 	 */
    33 	 */
    34 	protected $user_location = false;
    34 	protected $user_location = false;
    35 
    35 
    36 	/**
    36 	/**
    37 	 * Constructor for WP_Community_Events.
    37 	 * Constructor for WP_Community_Events.
    38 	 *
    38 	 *
    39 	 * @since 4.8.0
    39 	 * @since 4.8.0
    40 	 *
    40 	 *
    41 	 * @param int        $user_id       WP user ID.
    41 	 * @param int        $user_id       WP user ID.
    42 	 * @param bool|array $user_location Stored location data for the user.
    42 	 * @param false|array $user_location {
    43 	 *                                  false to pass no location;
    43 	 *     Stored location data for the user. false to pass no location.
    44 	 *                                  array to pass a location {
    44 	 *
    45 	 *     @type string $description The name of the location
    45 	 *     @type string $description The name of the location
    46 	 *     @type string $latitude    The latitude in decimal degrees notation, without the degree
    46 	 *     @type string $latitude    The latitude in decimal degrees notation, without the degree
    47 	 *                               symbol. e.g.: 47.615200.
    47 	 *                               symbol. e.g.: 47.615200.
    48 	 *     @type string $longitude   The longitude in decimal degrees notation, without the degree
    48 	 *     @type string $longitude   The longitude in decimal degrees notation, without the degree
    49 	 *                               symbol. e.g.: -122.341100.
    49 	 *                               symbol. e.g.: -122.341100.
    75 	 * pass the data for caching. Having WP make the request also introduces
    75 	 * pass the data for caching. Having WP make the request also introduces
    76 	 * the opportunity to anonymize the IP before sending it to w.org, which
    76 	 * the opportunity to anonymize the IP before sending it to w.org, which
    77 	 * mitigates possible privacy concerns.
    77 	 * mitigates possible privacy concerns.
    78 	 *
    78 	 *
    79 	 * @since 4.8.0
    79 	 * @since 4.8.0
       
    80 	 * @since 5.5.2 Response no longer contains formatted date field. They're added
       
    81 	 *              in `wp.communityEvents.populateDynamicEventFields()` now.
    80 	 *
    82 	 *
    81 	 * @param string $location_search Optional. City name to help determine the location.
    83 	 * @param string $location_search Optional. City name to help determine the location.
    82 	 *                                e.g., "Seattle". Default empty string.
    84 	 *                                e.g., "Seattle". Default empty string.
    83 	 * @param string $timezone        Optional. Timezone to help determine the location.
    85 	 * @param string $timezone        Optional. Timezone to help determine the location.
    84 	 *                                Default empty string.
    86 	 *                                Default empty string.
   156 			 */
   158 			 */
   157 			if ( $this->coordinates_match( $request_args['body'], $response_body['location'] ) && empty( $response_body['location']['description'] ) ) {
   159 			if ( $this->coordinates_match( $request_args['body'], $response_body['location'] ) && empty( $response_body['location']['description'] ) ) {
   158 				$response_body['location']['description'] = $this->user_location['description'];
   160 				$response_body['location']['description'] = $this->user_location['description'];
   159 			}
   161 			}
   160 
   162 
       
   163 			/*
       
   164 			 * Store the raw response, because events will expire before the cache does.
       
   165 			 * The response will need to be processed every page load.
       
   166 			 */
   161 			$this->cache_events( $response_body, $expiration );
   167 			$this->cache_events( $response_body, $expiration );
   162 
   168 
   163 			$response_body = $this->trim_events( $response_body );
   169 			$response_body['events'] = $this->trim_events( $response_body['events'] );
   164 			$response_body = $this->format_event_data_time( $response_body );
       
   165 
   170 
   166 			return $response_body;
   171 			return $response_body;
   167 		}
   172 		}
   168 	}
   173 	}
   169 
   174 
   299 	 * which is less prone to errors.
   304 	 * which is less prone to errors.
   300 	 *
   305 	 *
   301 	 * @since 4.8.0
   306 	 * @since 4.8.0
   302 	 *
   307 	 *
   303 	 * @param array $location Should contain 'latitude' and 'longitude' indexes.
   308 	 * @param array $location Should contain 'latitude' and 'longitude' indexes.
   304 	 * @return bool|string false on failure, or a string on success.
   309 	 * @return string|false Transient key on success, false on failure.
   305 	 */
   310 	 */
   306 	protected function get_events_transient_key( $location ) {
   311 	protected function get_events_transient_key( $location ) {
   307 		$key = false;
   312 		$key = false;
   308 
   313 
   309 		if ( isset( $location['ip'] ) ) {
   314 		if ( isset( $location['ip'] ) ) {
   318 	/**
   323 	/**
   319 	 * Caches an array of events data from the Events API.
   324 	 * Caches an array of events data from the Events API.
   320 	 *
   325 	 *
   321 	 * @since 4.8.0
   326 	 * @since 4.8.0
   322 	 *
   327 	 *
   323 	 * @param array    $events     Response body from the API request.
   328 	 * @param array     $events     Response body from the API request.
   324 	 * @param int|bool $expiration Optional. Amount of time to cache the events. Defaults to false.
   329 	 * @param int|false $expiration Optional. Amount of time to cache the events. Defaults to false.
   325 	 * @return bool true if events were cached; false if not.
   330 	 * @return bool true if events were cached; false if not.
   326 	 */
   331 	 */
   327 	protected function cache_events( $events, $expiration = false ) {
   332 	protected function cache_events( $events, $expiration = false ) {
   328 		$set              = false;
   333 		$set              = false;
   329 		$transient_key    = $this->get_events_transient_key( $events['location'] );
   334 		$transient_key    = $this->get_events_transient_key( $events['location'] );
   338 
   343 
   339 	/**
   344 	/**
   340 	 * Gets cached events.
   345 	 * Gets cached events.
   341 	 *
   346 	 *
   342 	 * @since 4.8.0
   347 	 * @since 4.8.0
       
   348 	 * @since 5.5.2 Response no longer contains formatted date field. They're added
       
   349 	 *              in `wp.communityEvents.populateDynamicEventFields()` now.
   343 	 *
   350 	 *
   344 	 * @return array|false An array containing `location` and `events` items
   351 	 * @return array|false An array containing `location` and `events` items
   345 	 *                     on success, false on failure.
   352 	 *                     on success, false on failure.
   346 	 */
   353 	 */
   347 	public function get_cached_events() {
   354 	public function get_cached_events() {
   348 		$cached_response = get_site_transient( $this->get_events_transient_key( $this->user_location ) );
   355 		$cached_response = get_site_transient( $this->get_events_transient_key( $this->user_location ) );
   349 		$cached_response = $this->trim_events( $cached_response );
   356 
   350 
   357 		if ( isset( $cached_response['events'] ) ) {
   351 		return $this->format_event_data_time( $cached_response );
   358 			$cached_response['events'] = $this->trim_events( $cached_response['events'] );
       
   359 		}
       
   360 
       
   361 		return $cached_response;
   352 	}
   362 	}
   353 
   363 
   354 	/**
   364 	/**
   355 	 * Adds formatted date and time items for each event in an API response.
   365 	 * Adds formatted date and time items for each event in an API response.
   356 	 *
   366 	 *
   358 	 * the cached events are shared by all users. If it was called before storing
   368 	 * the cached events are shared by all users. If it was called before storing
   359 	 * the cache, then all users would see the events in the localized data/time
   369 	 * the cache, then all users would see the events in the localized data/time
   360 	 * of the user who triggered the cache refresh, rather than their own.
   370 	 * of the user who triggered the cache refresh, rather than their own.
   361 	 *
   371 	 *
   362 	 * @since 4.8.0
   372 	 * @since 4.8.0
       
   373 	 * @deprecated 5.6.0 No longer used in core.
   363 	 *
   374 	 *
   364 	 * @param array $response_body The response which contains the events.
   375 	 * @param array $response_body The response which contains the events.
   365 	 * @return array The response with dates and times formatted.
   376 	 * @return array The response with dates and times formatted.
   366 	 */
   377 	 */
   367 	protected function format_event_data_time( $response_body ) {
   378 	protected function format_event_data_time( $response_body ) {
       
   379 		_deprecated_function(
       
   380 			__METHOD__,
       
   381 			'5.5.2',
       
   382 			'This is no longer used by core, and only kept for backward compatibility.'
       
   383 		);
       
   384 
   368 		if ( isset( $response_body['events'] ) ) {
   385 		if ( isset( $response_body['events'] ) ) {
   369 			foreach ( $response_body['events'] as $key => $event ) {
   386 			foreach ( $response_body['events'] as $key => $event ) {
   370 				$timestamp = strtotime( $event['date'] );
   387 				$timestamp = strtotime( $event['date'] );
   371 
   388 
   372 				/*
   389 				/*
   373 				 * The `date_format` option is not used because it's important
   390 				 * The `date_format` option is not used because it's important
   374 				 * in this context to keep the day of the week in the formatted date,
   391 				 * in this context to keep the day of the week in the formatted date,
   375 				 * so that users can tell at a glance if the event is on a day they
   392 				 * so that users can tell at a glance if the event is on a day they
   376 				 * are available, without having to open the link.
   393 				 * are available, without having to open the link.
   377 				 */
   394 				 */
   378 				/* translators: Date format for upcoming events on the dashboard. Include the day of the week. See https://www.php.net/date */
   395 				/* translators: Date format for upcoming events on the dashboard. Include the day of the week. See https://www.php.net/manual/datetime.format.php */
   379 				$formatted_date = date_i18n( __( 'l, M j, Y' ), $timestamp );
   396 				$formatted_date = date_i18n( __( 'l, M j, Y' ), $timestamp );
   380 				$formatted_time = date_i18n( get_option( 'time_format' ), $timestamp );
   397 				$formatted_time = date_i18n( get_option( 'time_format' ), $timestamp );
   381 
   398 
   382 				if ( isset( $event['end_date'] ) ) {
   399 				if ( isset( $event['end_date'] ) ) {
   383 					$end_timestamp      = strtotime( $event['end_date'] );
   400 					$end_timestamp      = strtotime( $event['end_date'] );
   384 					$formatted_end_date = date_i18n( __( 'l, M j, Y' ), $end_timestamp );
   401 					$formatted_end_date = date_i18n( __( 'l, M j, Y' ), $end_timestamp );
   385 
   402 
   386 					if ( 'meetup' !== $event['type'] && $formatted_end_date !== $formatted_date ) {
   403 					if ( 'meetup' !== $event['type'] && $formatted_end_date !== $formatted_date ) {
   387 						/* translators: Upcoming events month format. See https://www.php.net/date */
   404 						/* translators: Upcoming events month format. See https://www.php.net/manual/datetime.format.php */
   388 						$start_month = date_i18n( _x( 'F', 'upcoming events month format' ), $timestamp );
   405 						$start_month = date_i18n( _x( 'F', 'upcoming events month format' ), $timestamp );
   389 						$end_month   = date_i18n( _x( 'F', 'upcoming events month format' ), $end_timestamp );
   406 						$end_month   = date_i18n( _x( 'F', 'upcoming events month format' ), $end_timestamp );
   390 
   407 
   391 						if ( $start_month === $end_month ) {
   408 						if ( $start_month === $end_month ) {
   392 							$formatted_date = sprintf(
   409 							$formatted_date = sprintf(
   393 								/* translators: Date string for upcoming events. 1: Month, 2: Starting day, 3: Ending day, 4: Year. */
   410 								/* translators: Date string for upcoming events. 1: Month, 2: Starting day, 3: Ending day, 4: Year. */
   394 								__( '%1$s %2$d–%3$d, %4$d' ),
   411 								__( '%1$s %2$d–%3$d, %4$d' ),
   395 								$start_month,
   412 								$start_month,
   396 								/* translators: Upcoming events day format. See https://www.php.net/date */
   413 								/* translators: Upcoming events day format. See https://www.php.net/manual/datetime.format.php */
   397 								date_i18n( _x( 'j', 'upcoming events day format' ), $timestamp ),
   414 								date_i18n( _x( 'j', 'upcoming events day format' ), $timestamp ),
   398 								date_i18n( _x( 'j', 'upcoming events day format' ), $end_timestamp ),
   415 								date_i18n( _x( 'j', 'upcoming events day format' ), $end_timestamp ),
   399 								/* translators: Upcoming events year format. See https://www.php.net/date */
   416 								/* translators: Upcoming events year format. See https://www.php.net/manual/datetime.format.php */
   400 								date_i18n( _x( 'Y', 'upcoming events year format' ), $timestamp )
   417 								date_i18n( _x( 'Y', 'upcoming events year format' ), $timestamp )
   401 							);
   418 							);
   402 						} else {
   419 						} else {
   403 							$formatted_date = sprintf(
   420 							$formatted_date = sprintf(
   404 								/* translators: Date string for upcoming events. 1: Starting month, 2: Starting day, 3: Ending month, 4: Ending day, 5: Year. */
   421 								/* translators: Date string for upcoming events. 1: Starting month, 2: Starting day, 3: Ending month, 4: Ending day, 5: Year. */
   433 	 * the event will be at the end of the list, and will need to be moved into a
   450 	 * the event will be at the end of the list, and will need to be moved into a
   434 	 * higher position, so that it doesn't get trimmed off.
   451 	 * higher position, so that it doesn't get trimmed off.
   435 	 *
   452 	 *
   436 	 * @since 4.8.0
   453 	 * @since 4.8.0
   437 	 * @since 4.9.7 Stick a WordCamp to the final list.
   454 	 * @since 4.9.7 Stick a WordCamp to the final list.
   438 	 *
   455 	 * @since 5.5.2 Accepts and returns only the events, rather than an entire HTTP response.
   439 	 * @param array $response_body The response body which contains the events.
   456 	 *
       
   457 	 * @param array $events The events that will be prepared.
   440 	 * @return array The response body with events trimmed.
   458 	 * @return array The response body with events trimmed.
   441 	 */
   459 	 */
   442 	protected function trim_events( $response_body ) {
   460 	protected function trim_events( array $events ) {
   443 		if ( isset( $response_body['events'] ) ) {
   461 		$future_events = array();
   444 			$wordcamps = array();
   462 
   445 			$today     = current_time( 'Y-m-d' );
   463 		foreach ( $events as $event ) {
   446 
   464 			/*
   447 			foreach ( $response_body['events'] as $key => $event ) {
   465 			 * The API's `date` and `end_date` fields are in the _event's_ local timezone, but UTC is needed so
   448 				/*
   466 			 * it can be converted to the _user's_ local time.
   449 				 * Skip WordCamps, because they might be multi-day events.
   467 			 */
   450 				 * Save a copy so they can be pinned later.
   468 			$end_time = (int) $event['end_unix_timestamp'];
   451 				 */
   469 
   452 				if ( 'wordcamp' === $event['type'] ) {
   470 			if ( time() < $end_time ) {
   453 					$wordcamps[] = $event;
   471 				array_push( $future_events, $event );
   454 					continue;
   472 			}
   455 				}
   473 		}
   456 
   474 
   457 				// We don't get accurate time with timezone from API, so we only take the date part (Y-m-d).
   475 		$future_wordcamps = array_filter(
   458 				$event_date = substr( $event['date'], 0, 10 );
   476 			$future_events,
   459 
   477 			function( $wordcamp ) {
   460 				if ( $today > $event_date ) {
   478 				return 'wordcamp' === $wordcamp['type'];
   461 					unset( $response_body['events'][ $key ] );
   479 			}
   462 				}
   480 		);
   463 			}
   481 
   464 
   482 		$future_wordcamps    = array_values( $future_wordcamps ); // Remove gaps in indices.
   465 			$response_body['events'] = array_slice( $response_body['events'], 0, 3 );
   483 		$trimmed_events      = array_slice( $future_events, 0, 3 );
   466 			$trimmed_event_types     = wp_list_pluck( $response_body['events'], 'type' );
   484 		$trimmed_event_types = wp_list_pluck( $trimmed_events, 'type' );
   467 
   485 
   468 			// Make sure the soonest upcoming WordCamp is pinned in the list.
   486 		// Make sure the soonest upcoming WordCamp is pinned in the list.
   469 			if ( ! in_array( 'wordcamp', $trimmed_event_types, true ) && $wordcamps ) {
   487 		if ( $future_wordcamps && ! in_array( 'wordcamp', $trimmed_event_types, true ) ) {
   470 				array_pop( $response_body['events'] );
   488 			array_pop( $trimmed_events );
   471 				array_push( $response_body['events'], $wordcamps[0] );
   489 			array_push( $trimmed_events, $future_wordcamps[0] );
   472 			}
   490 		}
   473 		}
   491 
   474 
   492 		return $trimmed_events;
   475 		return $response_body;
       
   476 	}
   493 	}
   477 
   494 
   478 	/**
   495 	/**
   479 	 * Logs responses to Events API requests.
   496 	 * Logs responses to Events API requests.
   480 	 *
   497 	 *