diff --git a/ds-npr-api.php b/ds-npr-api.php index 82dc062..c29b944 100644 --- a/ds-npr-api.php +++ b/ds-npr-api.php @@ -68,17 +68,17 @@ function nprstory_activation() { global $wpdb; if ( function_exists( 'is_multisite' ) && is_multisite() ) { - // check if it is a network activation - if so, run the activation function for each blog id - $old_blog = $wpdb->blogid; + // check if it is a network activation - if so, run the activation function for each blog id + $old_blog = $wpdb->blogid; // Get all blog ids $blogids = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs" ) ); foreach ( $blogids as $blog_id ) { - switch_to_blog( $blog_id ); - nprstory_activate(); + switch_to_blog( $blog_id ); + nprstory_activate(); } switch_to_blog( $old_blog ); } else { - nprstory_activate(); + nprstory_activate(); } } @@ -99,13 +99,12 @@ function nprstory_activate() { if ( empty( $pull_url ) ) { update_option( 'ds_npr_api_pull_url', $def_url ); } - } function nprstory_deactivation() { global $wpdb; if ( function_exists( 'is_multisite' ) && is_multisite() ) { - // check if it is a network activation - if so, run the activation function for each blog id + // check if it is a network activation - if so, run the activation function for each blog id $old_blog = $wpdb->blogid; // Get all blog ids $blogids = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs" ) ); @@ -150,11 +149,11 @@ function nprstory_create_post_type() { 'labels' => array( 'name' => __( 'NPR Stories' ), 'singular_name' => __( 'NPR Story' ), - ), - 'public' => true, - 'has_archive' => true, - 'menu_position' => 5, - 'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields' ), + ), + 'public' => true, + 'has_archive' => true, + 'menu_position' => 5, + 'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields' ), ) ); } diff --git a/push_story.php b/push_story.php index 54e93a6..d99691e 100644 --- a/push_story.php +++ b/push_story.php @@ -624,6 +624,7 @@ function nprstory_save_nprone_featured( $post_ID ) { * @param Int $post_ID The post ID of the post we're saving * @since 1.7 * @see nprstory_publish_meta_box + * @uses nprstory_get_datetimezone * @link https://en.wikipedia.org/wiki/ISO_8601 */ function nprstory_save_datetime( $post_ID ) { @@ -642,7 +643,7 @@ function nprstory_save_datetime( $post_ID ) { // If the post is not published and values are not set, save an empty post meta if ( isset( $date ) && 'publish' === $post->status ) { $timezone = get_option( 'gmt_offset' ); - $datetime = date_create( $date, new DateTimeZone( $timezone ) ); + $datetime = date_create( $date, nprstory_get_datetimezone() ); $time = explode( ':', $time ); $datetime->setTime( $time[0], $time[1] ); $value = date_format( $datetime , DATE_ATOM ); @@ -662,24 +663,56 @@ function nprstory_save_datetime( $post_ID ) { * @param WP_Post|int $post the post ID or WP_Post object * @return DateTime the DateTime object created from the post expiry date * @see note on DATE_ATOM and DATE_ISO8601 https://secure.php.net/manual/en/class.datetime.php#datetime.constants.types + * @uses nprstory_get_datetimezone * @since 1.7 * @todo rewrite this to use fewer queries, so it's using the WP_Post internally instead of the post ID */ function nprstory_get_post_expiry_datetime( $post ) { $post = ( $post instanceof WP_Post ) ? $post->ID : $post ; $iso_8601 = get_post_meta( $post, '_nprone_expiry_8601', true ); - $timezone = get_option( 'gmt_offset' ); + $timezone = nprstory_get_datetimezone(); if ( empty( $iso_8601 ) ) { // return DateTime for the publish date plus seven days $future = get_the_date( DATE_ATOM, $post ); // publish date - return date_add( date_create( $future, new DateTimeZone( $timezone ) ), new DateInterval( 'P7D' ) ); + return date_add( date_create( $future, $timezone ), new DateInterval( 'P7D' ) ); } else { // return DateTime for the expiry date - return date_create( $iso_8601, new DateTimeZone( $timezone ) ); + return date_create( $iso_8601, $timezone ); } } +/** + * Helper for getting WordPress GMT offset + * + * It turns out we don't need to do anything with regards to get_option( 'timezone_string' ), + * because WordPress includes wp_timezone_override_offset as a default filter upon + * the filter pre_option_gmt_offset + * + * @since 1.7.2 + * @link https://github.com/npr/nprapi-wordpress/issues/52 + * @return DateTimeZone + */ +function nprstory_get_datetimezone() { + $timezone = get_option( 'gmt_offset' ); + + if ( is_numeric( $timezone ) ) { + // Because PHP handles timezone offsets for this purpose in seconds, + // (at least, according to https://secure.php.net/manual/en/datetimezone.getoffset.php) + // we must convert the WordPress-stored decimal hours into seconds. This value can be positive, negative, or zero. + $offset = floatval( $timezone ) * HOUR_IN_SECONDS; + } // It could also be '' empty string, which is a valid offset for the purposes of DateTimeZone::__construct(). + + try { + $return = new DateTimeZone( $offset ); + } catch( Exception $e ) { + nprstory_error_log( $e->getMessage() ); + $return = new DateTimeZone( '+0000' ); // The default timezone when WordPress does not have a configured timezone. This will also trigger when the gmt_offset is '0', which is the case when the GMT time is Greenwich Mean Time. + } + + return $return; +} + /** * Add an admin notice to the post editor with the post's error message if it exists */ diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ac5e54c..e31704a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,12 +1,37 @@ expectOutputRegex('/
markTestIncomplete('This test has not been implemented yet.'); } + function test_nprstory_get_datetimezone_all() { + + /* + * Get a list of time zones' UTC offsets. + * + * Yes, http://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe-about-time has been read. + * Yes, https://secure.php.net/manual/en/datetimezone.listabbreviations.php returns some canonically ~weird~ time zones. + * We'll mitigate those later in this test. + */ + $tzdata = DateTimeZone::listAbbreviations(); + $zones = array(); + + // The format of $tzdata groups time zones by general zones. + foreach ( $tzdata as $general => $specific_zones ) { + + // We want the offset of each specific zone. + $zones_each = wp_list_pluck( $specific_zones, 'offset' ); + + // Calculate the string offset format. + foreach ( $zones_each as $seconds ) { + if ( $seconds % 60 === 0 ) { // no fractional minutes + // no 20- or 40-minute offsets; they're valid but WordPress does not consider them. + if ( ( $seconds / 60 ) % 15 === 0 ) { + $zones[] = $seconds / 3600; // WordPress saves time zones as a decimal + } + } + } + } + $zones = array_unique( $zones ); + asort( $zones ); + + // Other test cases + $zones[] = ''; + + // Run every single test case + foreach ( $zones as $offset ) { + update_option( 'gmt_offset', $offset ); + $DateTimeZone = nprstory_get_datetimezone(); + $this->assertInstanceOf( + DateTimeZone::class, + $DateTimeZone, + sprintf( + '%1$s is not an instance of DateTimeZone when DateTimeZone::__construct was called with the timezone %2$s as the wp_options option_key gmt_offset.', + var_export( $DateTimeZone ), + var_export( $offset ) + ) + ); + } + + // reset + delete_option( 'gmt_offset' ); + } }