Changeset 3393864
- Timestamp:
- 11/11/2025 06:37:25 PM (4 months ago)
- Location:
- weather-write/trunk
- Files:
-
- 6 edited
-
includes/class-admin.php (modified) (5 diffs)
-
includes/class-cronjob.php (modified) (5 diffs)
-
includes/class-rest.php (modified) (2 diffs)
-
includes/class-scheduler.php (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
-
weather-write.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
weather-write/trunk/includes/class-admin.php
r3391250 r3393864 1234 1234 1235 1235 // GeoNameID status + resolve button (allows updating the system ID after changing location) 1236 // Only show if Meteoblue embed is enabled (GeoNameID is only used for Meteoblue widgets) 1236 1237 add_settings_field( 1237 1238 'wwrt_geonameid_status', … … 1240 1241 self::render_images_header(); 1241 1242 $options = get_option( self::OPTION_NAME, [] ); 1243 $mb_enabled = ! empty( $options['meteoblue_embed_enabled'] ); 1244 1245 // Only show if Meteoblue embed is enabled 1246 if ( ! $mb_enabled ) { 1247 echo '<div style="display:none;"></div>'; 1248 return; 1249 } 1250 1242 1251 $gid = isset( $options['geonameid'] ) ? (int) $options['geonameid'] : 0; 1243 1252 $lat = isset( $options['auto_lat'] ) ? (float) $options['auto_lat'] : null; … … 1982 1991 if ( is_array( $input ) && array_key_exists( 'auto_times', $input ) ) { 1983 1992 $times_out = []; 1993 $invalid_times = []; 1984 1994 if ( is_array( $input['auto_times'] ) ) { 1985 1995 $tz = wp_timezone(); … … 1988 1998 if ( '' === $raw ) { continue; } 1989 1999 $parsed = false; 1990 $attempts = [ 'g:ia', 'g:iA', 'H:i' ]; 2000 // Try multiple common time formats 2001 $attempts = [ 2002 'g:ia', // 6:00am, 12:30pm 2003 'g:iA', // 6:00AM, 12:30PM 2004 'H:i', // 06:00, 18:00 (24-hour) 2005 'ga', // 6am, 12pm 2006 'gA', // 6AM, 12PM 2007 'g a', // 6 am, 12 pm 2008 'g A', // 6 AM, 12 PM 2009 'H', // 6, 18 (24-hour, no minutes) 2010 ]; 1991 2011 foreach ( $attempts as $fmt ) { 1992 2012 $dt = DateTime::createFromFormat( $fmt, $raw, $tz ); … … 1995 2015 if ( $parsed ) { 1996 2016 $times_out[] = $parsed->format( 'H:i' ); 2017 } else { 2018 // Track invalid time for user feedback 2019 $invalid_times[] = $raw; 1997 2020 } 1998 2021 } 1999 2022 } 2000 2023 $times_out = array_values( array_unique( $times_out ) ); 2024 2025 // Show admin notice for invalid times 2026 if ( ! empty( $invalid_times ) ) { 2027 add_settings_error( 2028 self::OPTION_NAME, 2029 'invalid_time_format', 2030 sprintf( 2031 __( 'Invalid time format(s): %s. Please use formats like "6:00am", "6:00 AM", "18:00", or "6pm". Times were not saved.', 'weather-write' ), 2032 implode( ', ', array_map( 'esc_html', $invalid_times ) ) 2033 ), 2034 'error' 2035 ); 2036 } 2037 2001 2038 // Don't sort or assign yet - need to process tags first 2002 2039 if ( empty( $times_out ) ) { -
weather-write/trunk/includes/class-cronjob.php
r3365015 r3393864 13 13 $tz = function_exists('wp_timezone_string') ? wp_timezone_string() : 'UTC'; 14 14 $base = trailingslashit( home_url( '/' ) ); 15 $url = $base . 'wp-cron.php?doing_wp_cron=1'; 15 // Use direct REST API endpoint instead of wp-cron.php for more reliable execution 16 $url = rest_url( 'weatherwrite/v1/trigger' ) . '?token=' . urlencode( $token ); 16 17 17 18 // Load previous mapping … … 36 37 foreach ( array_keys( $desired ) as $hm ) { 37 38 list( $H, $M ) = array_map( 'intval', explode( ':', $hm ) ); 38 $title = sprintf( ' Ping site %s at %02d:%02d', untrailingslashit( $base ), $H, $M );39 $title = sprintf( 'WeatherWrite %s at %02d:%02d', untrailingslashit( $base ), $H, $M ); 39 40 $jobId = isset( $map[$hm] ) ? (int) $map[$hm] : 0; 40 $result = self::upsert_job( $token, $url, $tz, $H, $M, $title, $jobId ); 41 // Pass the time slot in the request body 42 $body = [ 'time' => $hm ]; 43 $result = self::upsert_job( $token, $url, $tz, $H, $M, $title, $jobId, $body ); 41 44 if ( is_wp_error( $result ) ) { 42 45 // Best-effort; continue … … 73 76 } 74 77 75 public static function upsert_job( string $token, string $url, string $timezone, int $hour, int $minute, string $title, int $jobId = 0 ) {78 public static function upsert_job( string $token, string $url, string $timezone, int $hour, int $minute, string $title, int $jobId = 0, array $body = [] ) { 76 79 $payload = [ 77 80 'job' => [ … … 79 82 'title' => $title, 80 83 'url' => $url, 81 'requestMethod' => 0,84 'requestMethod' => 1, // 0 = GET, 1 = POST 82 85 'saveResponses' => false, 83 86 'requestTimeout' => 30, … … 92 95 ], 93 96 ]; 97 98 // Add request body if provided 99 if ( ! empty( $body ) ) { 100 $payload['job']['body'] = wp_json_encode( $body ); 101 } 94 102 if ( $jobId > 0 ) { 95 103 // Update existing job (use PATCH per API spec for deltas) -
weather-write/trunk/includes/class-rest.php
r3356828 r3393864 41 41 ] 42 42 ); 43 44 // Public endpoint for cron-job.org to trigger scheduled posts 45 register_rest_route( 46 self::ROUTE_NS, 47 '/trigger', 48 [ 49 'methods' => WP_REST_Server::CREATABLE, 50 'callback' => [ __CLASS__, 'handle_trigger' ], 51 'permission_callback' => '__return_true', // Public, but requires token 52 'args' => [ 53 'token' => [ 'type' => 'string', 'required' => true ], 54 'time' => [ 'type' => 'string', 'required' => false ], 55 ], 56 ] 57 ); 43 58 } 44 59 … … 55 70 56 71 return new WP_REST_Response( $result, 200 ); 72 } 73 74 public static function handle_trigger( WP_REST_Request $request ) { 75 // Verify token 76 $token = sanitize_text_field( (string) $request->get_param( 'token' ) ); 77 $expected_token = defined( 'WWRT_CRONJOB_API_TOKEN' ) ? (string) WWRT_CRONJOB_API_TOKEN : ''; 78 79 if ( '' === $expected_token || $token !== $expected_token ) { 80 return new WP_Error( 'invalid_token', 'Invalid or missing token', [ 'status' => 401 ] ); 81 } 82 83 // Get the time slot (optional - if not provided, runs immediately) 84 $time = sanitize_text_field( (string) $request->get_param( 'time' ) ); 85 86 // Trigger the post generation directly 87 if ( ! class_exists( 'WWRT_Scheduler' ) ) { 88 require_once plugin_dir_path( __FILE__ ) . 'class-scheduler.php'; 89 } 90 91 $result = WWRT_Scheduler::run_generation( $time, false ); 92 93 if ( is_wp_error( $result ) ) { 94 return new WP_REST_Response( [ 95 'success' => false, 96 'error' => $result->get_error_message(), 97 'code' => $result->get_error_code(), 98 ], 500 ); 99 } 100 101 return new WP_REST_Response( [ 102 'success' => true, 103 'post_id' => $result, 104 'time' => $time, 105 'triggered_at' => current_time( 'mysql' ), 106 ], 200 ); 57 107 } 58 108 -
weather-write/trunk/includes/class-scheduler.php
r3391231 r3393864 171 171 // Context for new notification system 172 172 $run_id = wp_generate_uuid4(); 173 $slot_time = $hm ? gmdate( 'Y-m-d H:i:s', strtotime( $hm ) ) : gmdate( 'Y-m-d H:i:s' ); 173 // Use WordPress timezone for slot_time to match watchdog expectations 174 $tz = wp_timezone(); 175 $slot_time = $hm ? wp_date( 'Y-m-d H:i:s', strtotime( $hm ), $tz ) : wp_date( 'Y-m-d H:i:s' ); 174 176 $location_key = wwrt_compute_location_key(); 175 177 -
weather-write/trunk/readme.txt
r3391991 r3393864 4 4 Requires at least: 6.5 5 5 Tested up to: 6.8 6 Stable tag: 1. 2.96 Stable tag: 1.3.1 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 87 87 88 88 == Changelog == 89 90 = 1.3.1 = 91 - CRITICAL FIX: Fixed timezone bug causing false "missed run" watchdog alerts 92 - IMPROVEMENT: Watchdog now correctly matches events across all timezones 93 - ENHANCEMENT: Added time format validation for schedule settings 94 - UX: Clear error messages when invalid time formats entered (e.g., "6PM" instead of "6:00pm") 95 - UX: Auto-converts common time formats (6pm, 6:00am, 18:00, etc.) to standard format 96 - TECHNICAL: Changed slot_time logging from gmdate() to wp_date() with WordPress timezone 97 - TECHNICAL: Added support for 8 different time input formats 98 - RELIABILITY: Eliminates intermittent false alerts when posts were created successfully 99 - COMPATIBILITY: Fix applies automatically on next scheduled post (no user action required) 100 101 = 1.3.0 = 102 - ENHANCEMENT: Added direct REST API endpoint for more reliable cron job execution 103 - ENHANCEMENT: New /wp-json/weatherwrite/v1/trigger endpoint bypasses WordPress cron system 104 - IMPROVEMENT: Eliminates "missed run" false alerts caused by wp-cron.php timing issues 105 - IMPROVEMENT: Direct execution ensures post generation always logs start event 106 - IMPROVEMENT: Token-based authentication for secure public endpoint 107 - IMPROVEMENT: Cron-job.org now uses POST method with time parameter in request body 108 - TECHNICAL: Modified class-rest.php to add handle_trigger() method 109 - TECHNICAL: Modified class-cronjob.php to use REST API endpoint instead of wp-cron.php 110 - TECHNICAL: Changed cron job request method from GET to POST (requestMethod: 1) 111 - UX: Hidden GeoNameID field when Meteoblue embed is disabled (reduces confusion) 112 - COMPATIBILITY: Maintains backward compatibility with existing cron jobs 113 - RELIABILITY: Significantly reduces false "watchdog detected missed run" alerts 89 114 90 115 = 1.2.9 = -
weather-write/trunk/weather-write.php
r3391991 r3393864 3 3 * Plugin Name: Weather Write 4 4 * Description: Generate and publish weather-aware posts with summaries, charts, images, alerts, SEO, and more — fully automated or on-demand. 5 * Version: 1. 2.95 * Version: 1.3.1 6 6 * Author: Mike Freeman - WeatherWrite 7 7 * Plugin URI: https://www.weatherwrite.com/
Note: See TracChangeset
for help on using the changeset viewer.