diff --git a/README.md b/README.md index 7a0bbab..72d2a92 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # URI Tides Widget -Add the `[uri-tides]` shortcode to a page and a tides widget appears. +Add the `[uri-tides]` shortcode to a page and a tides widget appears. Requires the [URI Tides Updater](https://github.com/uriweb/uri-tides-updater) plugin to be installed and activated on at least one site in a multisite network. -The widget pulls in live water temp and tide prediction data from NOAA and graphically displays the current position of the tide. By default, data is pulled from the station at Quonset Point, RI, but a different station or buoy can be set if desired. +## How do I get set up? + +1. Install [URI Tides Updater](https://github.com/uriweb/uri-tides-updater). For multisite networks, activate it only on one site (e.g. the homepage) to avoid cron job duplication. +2. Install [URI Tides](https://github.com/uriweb/uri-tides/archive/refs/heads/master.zip) and activate it where you intend to use it. Network-activation may be appropriate. +3. Configure the shortcode to taste. ## Attributes The tides widget is somewhat configurable by adding attributes to the shortcode: -**`station`** (num)(optional) -The NOAA station ID from which to retrieve data. The default is Quonset Point, RI. (default: `8454049`) -Find a station on NOAA's [tides and currents website](https://tidesandcurrents.noaa.gov/stations.html). - **`height`** (num)(optional) Set a height in pixels for the tide chart (do not include units). The water temp will scale accordingly. (default: `30`) @@ -26,5 +26,5 @@ Set custom CSS class(s) (default: none) Contributors: Brandon Fuller, John Pennypacker Tags: widgets Requires at least: 4.0 -Tested up to: 4.9 -Stable tag: 1.1.1 \ No newline at end of file +Tested up to: 6.0 +Stable tag: 2.0 diff --git a/js/tides.js b/js/tides.js index b283408..4115a03 100644 --- a/js/tides.js +++ b/js/tides.js @@ -2,15 +2,6 @@ 'use strict'; - /* - * See CO-OPS JSON API documentation at https://tidesandcurrents.noaa.gov/api/ - */ - var parameters = { - 'timezone' : 'GMT', - 'baseURL' : 'https://tidesandcurrents.noaa.gov/api/datagetter?' - }; - - // Wait for the window to load... window.addEventListener('load', function(){ uriTidesInit(); @@ -95,12 +86,11 @@ 'padding' : h * (1/6) }; - // Set the station id - var station = els[i].getAttribute('data-station'); - - helpers.status(els[i], 'Initiating tide data...'); - buildChart(els[i], curve, station, ); - }; + if ( tides ) { + helpers.status(els[i], 'Initiating tide data...'); + buildChart(els[i], curve ); + } + } } @@ -109,10 +99,9 @@ * @param el el the tide widget element * @param curve obj the curve dimensions */ - function buildChart(el, curve, station) { + function buildChart(el, curve) { - var tideHeight, - output, + var output, tide = tides.tide.predictions, temp = tides.temperature.data, display = { @@ -178,9 +167,9 @@ var x = (2 * Math.PI) / m.cycle * m.x; m.y = Math.sin(x) + 1; - // prepare the date of tide retrieval for display + // prepare the date of tide retrieval for display var retrieved = new Date(tides.date * 1000); - var options = { year: 'numeric', month: 'short', day: 'numeric', hour: "2-digit", minute: "2-digit" }; + var options = { year: 'numeric', month: 'short', day: 'numeric', hour: "2-digit", minute: "2-digit" }; var fillcolor = el.classList.contains('darkmode') ? '#fff' : '#555'; @@ -193,7 +182,7 @@ output += ''; output += ''; - output += '
Source: NOAA/NOS/CO-OPS
'; + output += '
Source: NOAA/NOS/CO-OPS
'; // Display diff --git a/uri-tides.php b/uri-tides.php index 1003aaa..6b4c608 100755 --- a/uri-tides.php +++ b/uri-tides.php @@ -2,8 +2,8 @@ /* Plugin Name: URI Tides Plugin URI: http://www.uri.edu -Description: Live tide data from NOAA -Version: 1.2 +Description: Display live tide data from NOAA (requires URI Tides Updater as a controller) +Version: 2.0 Author: URI Web Communications Author URI: @author: Brandon Fuller @@ -16,38 +16,29 @@ /** - * Loads up the javascript + * Loads up the javascript and styles */ -function uri_tides_scripts() { +function uri_tides_enqueues() { wp_register_script( 'uri-tides', plugins_url( '/js/tides.js', __FILE__ ) ); wp_enqueue_script( 'uri-tides' ); - $tides = uri_tides_get_data(); - wp_localize_script( 'uri-tides', 'tides', $tides); -} + $tides = get_site_option( 'uri_tides_updater_cache', FALSE ); + wp_localize_script( 'uri-tides', 'tides', $tides); -/** - * Loads up the css - */ -function uri_tides_styles() { wp_register_style( 'uri-tides-css', plugins_url( '/css/tides.css', __FILE__ ) ); wp_enqueue_style( 'uri-tides-css' ); } +add_action( 'wp_enqueue_scripts', 'uri_tides_enqueues' ); /** * Shortcode callback */ function uri_tides_shortcode($attributes, $content, $shortcode) { - - uri_tides_scripts(); - uri_tides_styles(); - // Attributes extract( shortcode_atts( array( - 'station' => '8454049', 'darkmode' => false, 'height' => '30', 'class' => '' @@ -64,260 +55,9 @@ function uri_tides_shortcode($attributes, $content, $shortcode) { $output .= ' ' . $class; } - $output .= '" data-station="' . $station . '" " data-height="' . $height . '">'; + $output .= '" data-height="' . $height . '">'; return $output; } add_shortcode( 'uri-tides', 'uri_tides_shortcode' ); - - - - - - -/** - * WP CRON SETTINGS - * Set up a cron interval to run every 10 minutes - */ -function uri_tides_add_cron_interval( $schedules ) { - $schedules['ten_minutes'] = array( - 'interval' => 60 * 10, - 'display' => esc_html__( 'Every Ten Minutes' ), - ); - return $schedules; -} -add_filter( 'cron_schedules', 'uri_tides_add_cron_interval' ); - -// set us up the cron hook -// https://developer.wordpress.org/plugins/cron/scheduling-wp-cron-events/ -add_action( 'uri_tides_cron_hook', 'uri_tides_query_buoy' ); - -// finally, make sure that get tides is going run during the next 10 minute cron run -if ( ! wp_next_scheduled( 'uri_tides_cron_hook' ) ) { - wp_schedule_event( time(), 'ten_minutes', 'uri_tides_cron_hook' ); -} - - -/** - * Deactivate the cron setting if the plugin is shut off - */ -function uri_tides_deactivate() { - $timestamp = wp_next_scheduled( 'uri_tides_cron_hook' ); - wp_unschedule_event( $timestamp, 'uri_tides_cron_hook' ); -} -register_deactivation_hook( __FILE__, 'uri_tides_deactivate' ); - - -/** - * Controller of the tides data for the plugin. - * Checks for a cache - * if we have a good cache, we use that. - * otherwise, we query new tides data, and if it's good, we cache it. - * - * This is likely redundant due to the cron activity, but the code's here, and it - * won't run if there's a recent cache - * @see uri_tides_add_cron_interval() - * - * Why not a transient? Because I'm a control freak - * who would rather have stale data than no data - */ -function uri_tides_get_data() { - - $refresh_cache = FALSE; - - // 1. load all cached tide data - $tides_data = _uri_tides_load_cache(); - - // 2. check if we have a cache for this resource - if ( $tides_data !== FALSE ) { - // we've got cached data - // 3. check if the cache has sufficient recency - $expires_on = isset($tides_data['expires_on']) ? $tides_data['expires_on'] : $tides_data['date']; - if ( uri_tides_is_expired( $expires_on ) ) { - // cache is older than the specified recency, refresh it - // 4. refresh tides / update cache if needed - $refresh_cache = TRUE; - } - - } else { // no cache data - $refresh_cache = TRUE; - } - - if( $refresh_cache ) { - //echo '
Pull fresh tides and cache them
'; - - $tides_data = uri_tides_query_buoy(); - - if($tides_data !== FALSE) { - uri_tides_write_cache($tides_data); - } else { - // the cache is expired, but the fresh buoy response is invalid. - // extend the cache's lifespan for an hour - $expires_on = strtotime( '+1 hour', strtotime('now') ); - $tides_data = _uri_tides_load_cache(); - uri_tides_write_cache($tides_data, $expires_on); - - // notify the administrator of a problem - // _uri_tides_notify_administrator( $tides_data ); - - } - - } - // reload the tides data from the database to capitalize on cache updates - $tides_data = _uri_tides_load_cache(); - - return $tides_data; -} - -/** - * Send a notification to the administrator about the cache status - * @param $tides_data arr the tides data - * @return bool - */ -function _uri_tides_notify_administrator( $tides_data ) { - // @todo: identify which site is sending the error - $to = get_option('admin_email'); - if( empty ( $to ) ) { - $to = 'jpennypacker@uri.edu'; - } - $timezone = get_option('timezone_string'); - $date = (new DateTime('@' . $tides_data['date']))->setTimezone(new DateTimeZone( $timezone )); - $expiry = (new DateTime('@' . $tides_data['expires_on']))->setTimezone(new DateTimeZone( $timezone )); - - $subject = 'URI Tides failed to update tide data'; - $message = "The last time that tides data was refreshed successfully was on: " . $date->format( 'Y-m-d\TH:i:s' ); - $message .= "\n\n"; - $message .= "The site will try to refresh tides information on: " . $expiry->format( 'Y-m-d\TH:i:s' ); - - return wp_mail($to, $subject, $message ); -} - -/** - * Retrieve the tides data from the database - */ -function _uri_tides_load_cache() { - $tides_data = get_site_option( 'uri_tides_cache', FALSE); - if ( empty( $tides_data ) ) { - $tides_data = array(); - $tides_data['date'] = strtotime('now -10 seconds'); - $tides_data['expires_on'] = strtotime('now -10 seconds'); - } - return $tides_data; -} - -/** - * Query the NOAA buoy - * @return mixed; arr on success, bool false on failure - */ -function uri_tides_query_buoy() { - $station = '8454049'; - $tides_data = array(); - $tides_data['temperature'] = _uri_tides_query( _uri_tides_build_url ( 'temperature', $station ) ); - $tides_data['tide'] = _uri_tides_query( _uri_tides_build_url ( 'tide', $station ) ); - - if ( $tides_data['temperature'] !== FALSE && $tides_data['tide'] !== FALSE ) { - return $tides_data; - } else { - // - return FALSE; - } -} - -/** - * Build the URL for the tides request - * @param str $subject is the three letter subject code - * @return str - */ -function _uri_tides_build_url( $q='temperature', $station='8454049' ) { - $base = 'https://tidesandcurrents.noaa.gov/api/datagetter?'; - $application = 'NOS.COOPS.TAC.' . ($q == 'temperature') ? 'PHYSOCEAN' : 'WL'; - - if($q == 'temperature' ) { - $url = $base . 'product=water_temperature&application=' . $application . - '&date=latest&station=' . $station . - '&time_zone=GMT&units=english&interval=6&format=json'; - } else { - $start_date = date( 'Ymd', strtotime( 'yesterday' ) ); - $end_date = date( 'Ymd', strtotime( '+2 days' ) ); - - $url = $base . 'product=predictions&application=' . - $application . '&begin_date=' . $start_date . '&end_date=' . $end_date . - '&datum=MLLW&station=' . $station . - '&time_zone=GMT&units=english&interval=hilo&format=json'; - - } - - return $url; -} - -/** - * Save the data retrieved from the NOAA buoy as a WordPress site-wide option - * @param arr $tides_data is an array of tides data [temperature, tide] - * @param str $expires_on expects a date object for some time in the future, if empty, - * it'll use the value set in the admin preferences (or the default five minutes) - */ -function uri_tides_write_cache( $tides_data, $expires_on='' ) { - - // if expires on is empty or not in the future, set a new expiry date - if ( empty ( $expires_on ) || !($expires_on > strtotime('now')) ) { - $recency = get_site_option( 'uri_tides_recency', '5 minutes' ); - $expires_on = strtotime( '+'.$recency, strtotime('now') ); - } - - $tides_data['date'] = strtotime('now'); - $tides_data['expires_on'] = $expires_on; - update_site_option( 'uri_tides_cache', $tides_data, TRUE ); -} - - -/** - * check if a date has recency - * @param int date - * @return bool - */ -function uri_tides_is_expired( $date ) { - return ( $date < strtotime('now') ); -} - - - -/** - * Query the buoy for tide level - * @return mixed arr on success; FALSE on failure - */ -function _uri_tides_query( $url ) { - - $args = array( - 'user-agent' => 'URI Tides WordPress Plugin', // So the endpoint can figure out who we are - 'headers' => [ ], - 'timeout' => 5 // limit query time to 5 seconds - ); - - - $response = wp_safe_remote_get ( $url, $args ); - - - if( is_wp_error ($response) ) { - // there was an error making the API call - // echo 'The error message is: ' . $response->get_error_message(); - return FALSE; - } - - // still here? good. it means WP got an acceptable response. Let's validate it. - if ( isset( $response['body'] ) && !empty( $response['body'] ) && wp_remote_retrieve_response_code($response) == '200' ) { - $data = json_decode ( wp_remote_retrieve_body ( $response ) ); - // check that the response has a body and that it contains the properties that we're looking for - if( ( isset($data->metadata) || isset($data->predictions) ) ) { - // hooray, all is well! - return $data; - } - } - - // still here? Then the content from API has been rejected - // @todo: log sensible debugging information - - return FALSE; - -} -