From e87ccb2742ec68ef1d5aa933c34a4959e6c6d30a Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 22 May 2018 21:46:38 -0400 Subject: [PATCH 1/9] Move the DateTimeZone construction into its own separate testable function, and write a test for that that iterates over the known valid GMT offsets from WordPress's database. This is for https://github.com/npr/nprapi-wordpress/issues/52 --- push_story.php | 42 ++++++++++++++++++++++++++++++---- tests/test-push_story.php | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/push_story.php b/push_story.php index 54e93a6..80abc5f 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,57 @@ 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 ) { + error_log(var_export( $e->getMessage(), true)); + 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/test-push_story.php b/tests/test-push_story.php index 686f24d..2c3d6a9 100644 --- a/tests/test-push_story.php +++ b/tests/test-push_story.php @@ -121,4 +121,52 @@ function test_save_send_to_npr_one() { $this->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, $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' ); + } } From 4b5a09c0658d02f95816b1a77b4bf78bdce83770 Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 22 May 2018 21:52:26 -0400 Subject: [PATCH 2/9] remove extraneous error_log --- push_story.php | 1 - 1 file changed, 1 deletion(-) diff --git a/push_story.php b/push_story.php index 80abc5f..4c141b8 100644 --- a/push_story.php +++ b/push_story.php @@ -706,7 +706,6 @@ function nprstory_get_datetimezone() { try { $return = new DateTimeZone( $offset ); } catch( Exception $e ) { - error_log(var_export( $e->getMessage(), true)); 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. } From 07a4c237da11f579fa7e356b9cf2e7565866decd Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 18 Sep 2018 17:33:10 -0400 Subject: [PATCH 3/9] Fix minor typo --- push_story.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/push_story.php b/push_story.php index 4c141b8..d99691e 100644 --- a/push_story.php +++ b/push_story.php @@ -699,7 +699,7 @@ function nprstory_get_datetimezone() { 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. + // 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(). From dfd8fbcc01048a14d41439870b6948a7480413fa Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 18 Sep 2018 17:50:09 -0400 Subject: [PATCH 4/9] Fix unexpected ; in tests/test-push_story.php --- tests/test-push_story.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test-push_story.php b/tests/test-push_story.php index 2c3d6a9..f32c2d2 100644 --- a/tests/test-push_story.php +++ b/tests/test-push_story.php @@ -159,10 +159,14 @@ function test_nprstory_get_datetimezone_all() { foreach ( $zones as $offset ) { update_option( 'gmt_offset', $offset ); $DateTimeZone = nprstory_get_datetimezone(); - $this->assertInstanceOf( DateTimeZone, $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 ) + $this->assertInstanceOf( + DateTimeZone, + $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 ) + ) ); } From 7db701dbfbaedf6c929086ea5a0c65461143859b Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 18 Sep 2018 21:59:13 -0400 Subject: [PATCH 5/9] Because Travis isn't initializing the plugin tests correctly, copy in the boilerplate from the 'wp scaffold plugin' command, without removing existing bootstrap.php functionality. --- tests/bootstrap.php | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ac5e54c..8f97da6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,12 +1,37 @@ Date: Tue, 18 Sep 2018 22:21:58 -0400 Subject: [PATCH 6/9] Correct boilerplate filename used in boilerplate code from 'wp scaffold plugin' --- tests/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 8f97da6..e31704a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -29,7 +29,7 @@ function _manually_load_environment() { * Manually load the plugin being tested. */ function _manually_load_plugin() { - require dirname( dirname( __FILE__ ) ) . '/sample-plugin.php'; + require dirname( dirname( __FILE__ ) ) . '/ds-npr-api.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); From 06db5b0718b83c517a5f86ae2dcb25b085f5954a Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 18 Sep 2018 23:19:15 -0400 Subject: [PATCH 7/9] Pass $post to nprstory_publish_meta_box() in Test_MetaBoxes::test_nprstory_publish_meta_box Fixes the error seen in https://travis-ci.com/npr/nprapi-wordpress/jobs/146555595 1) Test_MetaBoxes::test_nprstory_publish_meta_box Missing argument 1 for nprstory_publish_meta_box(), called in /tmp/wordpress/src/wp-content/plugins/nprapi-wordpress/tests/test-meta-boxes.php on line 17 and defined /tmp/wordpress/src/wp-content/plugins/nprapi-wordpress/meta-boxes.php:18 /tmp/wordpress/src/wp-content/plugins/nprapi-wordpress/tests/test-meta-boxes.php:17 --- tests/test-meta-boxes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-meta-boxes.php b/tests/test-meta-boxes.php index ec30798..9352f88 100644 --- a/tests/test-meta-boxes.php +++ b/tests/test-meta-boxes.php @@ -14,7 +14,7 @@ function test_nprstory_publish_meta_box() { # Simple test of output to verify some part of the expected markup is present $this->expectOutputRegex('/
Date: Tue, 18 Sep 2018 23:20:00 -0400 Subject: [PATCH 8/9] Fix 'undefined constant DateTimeZone' error in Test_PushStory::test_nprstory_get_datetimezone_all The error message: 2) Test_PushStory::test_nprstory_get_datetimezone_all Use of undefined constant DateTimeZone - assumed 'DateTimeZone' /tmp/wordpress/src/wp-content/plugins/nprapi-wordpress/tests/test-push_story.php:163 Must pass the class. --- tests/test-push_story.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-push_story.php b/tests/test-push_story.php index f32c2d2..331210a 100644 --- a/tests/test-push_story.php +++ b/tests/test-push_story.php @@ -160,7 +160,7 @@ function test_nprstory_get_datetimezone_all() { update_option( 'gmt_offset', $offset ); $DateTimeZone = nprstory_get_datetimezone(); $this->assertInstanceOf( - DateTimeZone, + 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.', From 1623aa074bfae8a9417ea134bbf8c61817bcc673 Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Tue, 18 Sep 2018 23:29:10 -0400 Subject: [PATCH 9/9] clean up spacing in ds-npr-api.php this does not fix anything, but it does make the code in this file more readable, which helped with debugging the tests that were broken in https://github.com/npr/nprapi-wordpress/pull/53 and that were fixed by commits https://github.com/npr/nprapi-wordpress/pull/53/commits/7db701dbfbaedf6c929086ea5a0c65461143859b https://github.com/npr/nprapi-wordpress/pull/53/commits/2c74c8bf7703190cf82bdd8a52fcd4121056c9df --- ds-npr-api.php | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) 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' ), ) ); }