base = $base; } /** * Schedules the log cleanup event in the WordPress CRON on a daily basis * * @since 3.9.8 */ public function schedule_log_cleanup_event() { // Bail if the preserve logs settings is indefinite. if ( ! $this->base->get_class( 'settings' )->get_setting( 'log', '[preserve_days]' ) ) { return; } // Bail if the scheduled event already exists. $scheduled_event = $this->get_log_cleanup_event(); if ( $scheduled_event !== false ) { return; } // Schedule event. $scheduled_date_time = gmdate( 'Y-m-d', strtotime( '+1 day' ) ) . ' 00:00:00'; wp_schedule_event( strtotime( $scheduled_date_time ), 'daily', $this->base->plugin->filter_name . '_log_cleanup_cron' ); } /** * Unschedules the log cleanup event in the WordPress CRON. * * @since 3.9.8 */ public function unschedule_log_cleanup_event() { wp_clear_scheduled_hook( $this->base->plugin->filter_name . '_log_cleanup_cron' ); } /** * Reschedules the log cleanup event in the WordPress CRON, by unscheduling * and scheduling it. * * @since 3.9.8 */ public function reschedule_log_cleanup_event() { $this->unschedule_log_cleanup_event(); $this->schedule_log_cleanup_event(); } /** * Returns the scheduled log cleanup event, if it exists * * @since 3.9.8 */ public function get_log_cleanup_event() { return wp_get_schedule( $this->base->plugin->filter_name . '_log_cleanup_cron' ); } /** * Returns the scheduled log cleanup event's next date and time to run, if it exists * * @since 3.9.8 * * @param mixed $format Format Timestamp (false | php date() compat. string). */ public function get_log_cleanup_event_next_scheduled( $format = false ) { // Get timestamp for when the event will next run. $scheduled = wp_next_scheduled( $this->base->plugin->filter_name . '_log_cleanup_cron' ); // If no timestamp or we're not formatting the result, return it now. if ( ! $scheduled || ! $format ) { return $scheduled; } // Return formatted date/time. return gmdate( $format, $scheduled ); } /** * Runs the log cleanup CRON event * * @since 3.9.8 */ public function log_cleanup() { // Bail if the preserve logs settings is indefinite. // We shouldn't ever call this function if this is the case, but it's a useful sanity check. $preserve_days = $this->base->get_class( 'settings' )->get_setting( 'log', '[preserve_days]' ); if ( ! $preserve_days ) { return; } // Define the date cutoff. $date_time = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $preserve_days . ' days' ) ); // Delete log entries older than the date. $this->base->get_class( 'log' )->delete_by_request_sent_cutoff( $date_time ); } /** * Schedules the media cleanup event in the WordPress CRON on a daily basis * * @since 4.2.0 */ public function schedule_media_cleanup_event() { // Bail if the scheduled event already exists. $scheduled_event = $this->get_media_cleanup_event(); if ( $scheduled_event !== false ) { return; } // Schedule event. $scheduled_date_time = gmdate( 'Y-m-d', strtotime( '+1 day' ) ) . ' 01:00:00'; wp_schedule_event( strtotime( $scheduled_date_time ), 'daily', $this->base->plugin->filter_name . '_media_cleanup_cron' ); } /** * Unschedules the media cleanup event in the WordPress CRON. * * @since 4.2.0 */ public function unschedule_media_cleanup_event() { wp_clear_scheduled_hook( $this->base->plugin->filter_name . '_media_cleanup_cron' ); } /** * Reschedules the media cleanup event in the WordPress CRON, by unscheduling * and scheduling it. * * @since 4.2.0 */ public function reschedule_media_cleanup_event() { $this->unschedule_media_cleanup_event(); $this->schedule_media_cleanup_event(); } /** * Returns the scheduled media cleanup event, if it exists * * @since 4.2.0 */ public function get_media_cleanup_event() { return wp_get_schedule( $this->base->plugin->filter_name . '_media_cleanup_cron' ); } /** * Returns the scheduled media cleanup event's next date and time to run, if it exists * * @since 4.2.0 * * @param mixed $format Format Timestamp (false | php date() compat. string). */ public function get_media_cleanup_event_next_scheduled( $format = false ) { // Get timestamp for when the event will next run. $scheduled = wp_next_scheduled( $this->base->plugin->filter_name . '_media_cleanup_cron' ); // If no timestamp or we're not formatting the result, return it now. if ( ! $scheduled || ! $format ) { return $scheduled; } // Return formatted date/time. return gmdate( $format, $scheduled ); } /** * Schedules a single event in the WordPress CRON to refresh the access token(s) * shortly before the earliest one expires. * * Access tokens are short lived and refresh tokens are single use, so refreshing * proactively avoids requests failing with an authentication error, and avoids * several requests attempting to refresh using the same refresh token at once. * * @since 6.2.0 */ public function schedule_refresh_token_event() { // Clear any existing scheduled event. $this->unschedule_refresh_token_event(); // Get the earliest expiry timestamp across all connected accounts. $token_expires = $this->get_earliest_token_expiry(); // Bail only if no account has a refresh token. if ( $token_expires === false ) { return; } // The default number of seconds before an access token expires to refresh it. $seconds = 5 * MINUTE_IN_SECONDS; /** * The number of seconds before an access token expires to refresh it. * * @since 6.2.0 * * @param int $seconds Seconds before expiry. */ $seconds = apply_filters( $this->base->plugin->filter_name . '_cron_refresh_token_seconds_before_expiry', $seconds ); // Determine when to refresh. $refresh_at = $token_expires - $seconds; // If the refresh time is in the past, schedule the event for 1 minute from now. if ( $refresh_at <= time() ) { $refresh_at = time() + MINUTE_IN_SECONDS; } // Schedule event. wp_schedule_single_event( $refresh_at, $this->base->plugin->filter_name . '_refresh_token_cron' ); } /** * Unschedules the refresh token event in the WordPress CRON. * * @since 6.2.0 */ public function unschedule_refresh_token_event() { wp_clear_scheduled_hook( $this->base->plugin->filter_name . '_refresh_token_cron' ); } /** * Reschedules the refresh token event in the WordPress CRON, based on the expiry * of the stored access token(s). * * Called on upgrade, so that sites connected before this event existed get one * scheduled without having to reconnect, and whenever a token is issued or * refreshed, so that each token schedules the refresh of its successor. * * @since 6.2.0 */ public function reschedule_refresh_token_event() { $this->schedule_refresh_token_event(); } /** * Returns the scheduled refresh token event, if it exists * * @since 6.2.0 * * @return mixed bool | string */ public function get_refresh_token_event() { return wp_get_schedule( $this->base->plugin->filter_name . '_refresh_token_cron' ); } /** * Returns the scheduled refresh token event's next date and time to run, if it exists * * @since 6.2.0 * * @param mixed $format Format Timestamp (false | php date() compat. string). * @return mixed bool | int | string */ public function get_refresh_token_event_next_scheduled( $format = false ) { // Get timestamp for when the event will next run. $scheduled = wp_next_scheduled( $this->base->plugin->filter_name . '_refresh_token_cron' ); // If no timestamp or we're not formatting the result, return it now. if ( ! $scheduled || ! $format ) { return $scheduled; } // Return formatted date/time. return gmdate( $format, $scheduled ); } /** * Returns the earliest access token expiry timestamp across all connected accounts. * * Accounts holding a refresh token but no expiry timestamp are treated as due now, * returning zero, so that an event is still scheduled for them. * * @since 6.2.0 * * @return mixed false | int false if no account has a refresh token. */ private function get_earliest_token_expiry() { $earliest = false; foreach ( $this->base->get_class( 'settings' )->get_accounts() as $account ) { // Skip accounts with no refresh token, as there's nothing to refresh with. if ( empty( $account['refresh_token'] ) ) { continue; } // An account with no stored expiry is treated as due now, so that an // event is still scheduled for it. $token_expires = ( empty( $account['token_expires'] ) ? 0 : (int) $account['token_expires'] ); // Compare against false explicitly, as a zero timestamp is a valid value // here and would otherwise be treated as "not yet set". if ( $earliest === false || $token_expires < $earliest ) { $earliest = $token_expires; } } return $earliest; } /** * Runs the refresh token CRON event, exchanging each stored refresh token that is * due to expire for a new access token. * * The API class fires an action on a successful refresh, which stores the new * access token, refresh token and expiry against the account. * * @since 6.2.0 */ public function refresh_token() { // Bail if the API class cannot refresh tokens, for example where the service // issues a token that does not expire. if ( ! method_exists( $this->base->get_class( 'api' ), 'refresh_token' ) ) { return; } // The default number of seconds before an access token expires to refresh it. $seconds = 5 * MINUTE_IN_SECONDS; /** * The number of seconds before an access token expires to refresh it. * * @since 6.2.0 * * @param int $seconds Seconds before expiry. */ $seconds = apply_filters( $this->base->plugin->filter_name . '_cron_refresh_token_seconds_before_expiry', $seconds ); // Iterate through accounts, refreshing any access token that is due to expire. foreach ( $this->base->get_class( 'settings' )->get_accounts() as $account ) { // Skip accounts with no refresh token, as there's nothing to refresh with. if ( empty( $account['refresh_token'] ) ) { continue; } // Skip accounts whose access token isn't due to expire yet. if ( ! empty( $account['token_expires'] ) && (int) $account['token_expires'] > ( time() + $seconds ) ) { continue; } // Refresh this account's access token. $this->base->get_class( 'api' )->set_tokens( $account['access_token'], $account['refresh_token'], $account['token_expires'] ); $this->base->get_class( 'api' )->refresh_token(); } // Schedule the next event based on what is now stored. $this->schedule_refresh_token_event(); } }