diff --git a/ACTIONS.md b/ACTIONS.md index cd0b476c..e71e2660 100644 --- a/ACTIONS.md +++ b/ACTIONS.md @@ -130,9 +130,13 @@ Fires after an user has been successfully created or logged-in via the OAuth 2.0 --- -### `do_action( 'fictioneer_after_update' )` +### `do_action( 'fictioneer_after_update', $new_version, $previous_version )` Fires after the theme has been updated, once per version change. +**Parameters:** +* $new_version (string) – The new version tag (e.g. v5.19.1). +* $previous_version (string) – The previous version tag (e.g. v5.19.0). + **Hooked Actions:** * `fictioneer_purge_caches_after_update()` – Purges selected caches and Transients. Priority 10. diff --git a/INSTALLATION.md b/INSTALLATION.md index 8ec4b3e8..8ba59669 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -1479,7 +1479,7 @@ define( 'CONSTANT_NAME', value ); | FICTIONEER_AJAX_LOGIN_TTL | integer | How long to cache AJAX authentications locally in _milliseconds_. Default `15000`. | FICTIONEER_AJAX_POST_DEBOUNCE_RATE | integer | How long to debounce AJAX requests of the same type in _milliseconds_. Default `700`. | FICTIONEER_AUTHOR_KEYWORD_SEARCH_LIMIT | integer | Maximum number of authors in the advanced search suggestions. Default `100`. -| FICTIONEER_UPDATE_CHECK_TIMEOUT | integer | Timeout between checks for theme updates in _seconds_. Default `3600`. +| FICTIONEER_UPDATE_CHECK_TIMEOUT | integer | Timeout between checks for theme updates in _seconds_. Default `43200`. | FICTIONEER_API_STORYGRAPH_CACHE_TTL | integer | How long Storygraph responses are cached in _seconds_. Default `3600`. | FICTIONEER_API_STORYGRAPH_STORIES_PER_PAGE | integer | How many items the Storygraph `/stories` endpoint returns. Default 10. | FICTIONEER_MAX_CUSTOM_PAGES_PER_STORY | integer | Maximum number of story custom pages. Default `4`. diff --git a/includes/functions/_setup-admin.php b/includes/functions/_setup-admin.php index 3782e6c8..aff1c8e1 100644 --- a/includes/functions/_setup-admin.php +++ b/includes/functions/_setup-admin.php @@ -111,114 +111,130 @@ add_action( 'admin_enqueue_scripts', 'fictioneer_admin_scripts' ); // CHECK FOR UPDATES // ============================================================================= -if ( ! function_exists( 'fictioneer_check_for_updates' ) ) { - /** - * Check Github repository for a new release - * - * Makes a remote request to the Github API to check the latest release and, - * if a newer version tag than what is installed is returned, updates the - * 'fictioneer_latest_version' option. Only one request is made every 12 hours - * unless you are on the updates page. - * - * @since 5.0.0 - * @since 5.7.5 - Refactored. - * - * @return boolean True if there is a newer version, false if not. - */ +/** + * Check Github repository for a new release + * + * @since 5.0.0 + * @since 5.7.5 - Refactored. + * @since 5.19.1 - Refactored again. + * + * @return boolean True if there is a newer version, false if not. + */ - function fictioneer_check_for_updates() { - global $pagenow; +function fictioneer_check_for_updates() { + global $pagenow; - // Setup - $last_check = (int) get_option( 'fictioneer_update_check_timestamp', 0 ); - $latest_version = get_option( 'fictioneer_latest_version', FICTIONEER_RELEASE_TAG ); - $is_updates_page = $pagenow === 'update-core.php'; + // Setup + $theme_info = fictioneer_get_theme_info(); + $last_check_timestamp = strtotime( $theme_info['last_update_check'] ?? 0 ); + $remote_version = $theme_info['last_update_version']; + $is_updates_page = $pagenow === 'update-core.php'; - // Only call API every n seconds, otherwise check database - if ( ! empty( $latest_version ) && time() < $last_check + FICTIONEER_UPDATE_CHECK_TIMEOUT && ! $is_updates_page ) { - return version_compare( $latest_version, FICTIONEER_RELEASE_TAG, '>' ); - } - - // Remember this check - update_option( 'fictioneer_update_check_timestamp', time() ); - - // Request to repository - $response = wp_remote_get( - 'https://api.github.com/repos/Tetrakern/fictioneer/releases/latest', - array( - 'headers' => array( - 'User-Agent' => 'FICTIONEER', - 'Accept' => 'application/vnd.github+json', - 'X-GitHub-Api-Version' => '2022-11-28' - ) - ) - ); - - // Abort if request failed or is not a 2xx success status code - if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) >= 300 ) { - return false; - } - - // Decode JSON to array - $release = json_decode( wp_remote_retrieve_body( $response ), true ); - $release_tag = sanitize_text_field( $release['tag_name'] ?? '' ); - - // Abort if request did not return expected data - if ( ! $release_tag ) { - return false; - } - - // Remember latest version - update_option( 'fictioneer_latest_version', $release_tag ); - - // Compare with currently installed version - return version_compare( $release_tag, FICTIONEER_RELEASE_TAG, '>' ); + // Only call API every n seconds, otherwise check database + if ( + $remote_version && + ! $is_updates_page && + current_time( 'timestamp', true ) < $last_check_timestamp + FICTIONEER_UPDATE_CHECK_TIMEOUT + ) { + return version_compare( $remote_version, FICTIONEER_RELEASE_TAG, '>' ); } + + // Remember this check + $theme_info['last_update_check'] = current_time( 'mysql', 1 ); + + // Request to repository + $response = wp_remote_get( + 'https://api.github.com/repos/Tetrakern/fictioneer/releases/latest', + array( + 'headers' => array( + 'User-Agent' => 'FICTIONEER', + 'Accept' => 'application/vnd.github+json', + 'X-GitHub-Api-Version' => '2022-11-28' + ) + ) + ); + + // Abort if request failed or is not a 2xx success status code + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) >= 300 ) { + return false; + } + + // Decode JSON to array + $release = json_decode( wp_remote_retrieve_body( $response ), true ); + $release_tag = sanitize_text_field( $release['tag_name'] ?? '' ); + + // Abort if request did not return expected data + if ( ! $release_tag ) { + return false; + } + + // Add to theme info + $theme_info['last_update_version'] = $release_tag; + $theme_info['last_update_notes'] = sanitize_textarea_field( $release['body'] ?? '' ); + $theme_info['last_update_nag'] = ''; // Reset + + if ( $release['assets'] ?? 0 ) { + $theme_info['last_version_download_url'] = sanitize_url( $release['assets'][0]['browser_download_url'] ?? '' ); + } else { + $theme_info['last_version_download_url'] = ''; + } + + // Update info in database + update_option( 'fictioneer_theme_info', $theme_info ); + + // Compare with currently installed version + return version_compare( $release_tag, FICTIONEER_RELEASE_TAG, '>' ); } /** * Show notice when a newer version is available * * @since 5.0.0 + * @since 5.19.1 - Refactored. */ function fictioneer_admin_update_notice() { - global $pagenow; - - // Setup - $last_notice = (int) get_option( 'fictioneer_update_notice_timestamp', 0 ); - $is_updates_page = $pagenow == 'update-core.php'; - - // Show only once every n seconds - if ( $last_notice + FICTIONEER_UPDATE_CHECK_TIMEOUT > time() && ! $is_updates_page ) { + // Guard + if ( ! current_user_can( 'install_themes' ) || ! fictioneer_check_for_updates() ) { return; } - // Update? - if ( ! fictioneer_check_for_updates() ) { + global $pagenow; + + // Setup + $theme_info = fictioneer_get_theme_info(); + $last_update_nag = strtotime( $theme_info['last_update_nag'] ?? 0 ); + $is_updates_page = $pagenow == 'update-core.php'; + + // Show only once every n seconds + if ( ! $is_updates_page && current_time( 'timestamp', true ) < $last_update_nag + 60 ) { return; } // Render notice + $notes = fictioneer_prepare_release_notes( $theme_info['last_update_notes'] ?? '' ); + wp_admin_notice( sprintf( - __( 'Fictioneer %1$s is available. Please download and install the latest version at your next convenience.', 'fictioneer' ), - get_option( 'fictioneer_latest_version', FICTIONEER_RELEASE_TAG ), - 'https://github.com/Tetrakern/fictioneer/releases' + __( 'Fictioneer %1$s is available. Please download and install the latest version at your next convenience.%3$s', 'fictioneer' ), + $theme_info['last_update_version'], + 'https://github.com/Tetrakern/fictioneer/releases', + $notes ? '
' . __( 'Release Notes', 'fictioneer' ) . '' . $notes . '
' : '' ), array( 'type' => 'warning', - 'dismissible' => true + 'dismissible' => true, + 'additional_classes' => ['fictioneer-update-notice'] ) ); // Remember notice - update_option( 'fictioneer_update_notice_timestamp', time() ); -} + $theme_info['last_update_nag'] = current_time( 'mysql', 1 ); -if ( current_user_can( 'install_themes' ) ) { - add_action( 'admin_notices', 'fictioneer_admin_update_notice' ); + // Update info in database + update_option( 'fictioneer_theme_info', $theme_info ); } +add_action( 'admin_notices', 'fictioneer_admin_update_notice' ); /** * Extracts the release notes from the update message diff --git a/includes/functions/_setup-theme.php b/includes/functions/_setup-theme.php index 60c0292f..78ce3dc6 100644 --- a/includes/functions/_setup-theme.php +++ b/includes/functions/_setup-theme.php @@ -13,10 +13,10 @@ function fictioneer_bring_out_legacy_trash() { // Setup $options = wp_cache_get( 'alloptions', 'options' ); - $obsolete = ['fictioneer_disable_html_in_comments', 'fictioneer_block_subscribers_from_admin', 'fictioneer_admin_restrict_menus', 'fictioneer_admin_restrict_private_data', 'fictioneer_admin_reduce_subscriber_profile', 'fictioneer_enable_subscriber_self_delete', 'fictioneer_strip_shortcodes_for_non_administrators', 'fictioneer_restrict_media_access', 'fictioneer_subscription_enabled', 'fictioneer_patreon_badge_map', 'fictioneer_patreon_tier_as_badge', 'fictioneer_patreon_campaign_ids', 'fictioneer_patreon_campaign_id', 'fictioneer_mount_wpdiscuz_theme_styles', 'fictioneer_base_site_width', 'fictioneer_featherlight_enabled', 'fictioneer_tts_enabled', 'fictioneer_log', 'fictioneer_enable_ajax_nonce', 'fictioneer_flush_object_cache', 'fictioneer_enable_all_block_styles', 'fictioneer_light_mode_as_default', 'fictioneer_remove_wp_svg_filters']; + $obsolete = ['fictioneer_disable_html_in_comments', 'fictioneer_block_subscribers_from_admin', 'fictioneer_admin_restrict_menus', 'fictioneer_admin_restrict_private_data', 'fictioneer_admin_reduce_subscriber_profile', 'fictioneer_enable_subscriber_self_delete', 'fictioneer_strip_shortcodes_for_non_administrators', 'fictioneer_restrict_media_access', 'fictioneer_subscription_enabled', 'fictioneer_patreon_badge_map', 'fictioneer_patreon_tier_as_badge', 'fictioneer_patreon_campaign_ids', 'fictioneer_patreon_campaign_id', 'fictioneer_mount_wpdiscuz_theme_styles', 'fictioneer_base_site_width', 'fictioneer_featherlight_enabled', 'fictioneer_tts_enabled', 'fictioneer_log', 'fictioneer_enable_ajax_nonce', 'fictioneer_flush_object_cache', 'fictioneer_enable_all_block_styles', 'fictioneer_light_mode_as_default', 'fictioneer_remove_wp_svg_filters', 'fictioneer_update_check_timestamp', 'fictioneer_latest_version', 'fictioneer_update_notice_timestamp', 'fictioneer_theme_status']; // Check for most recent obsolete option... - if ( isset( $options['fictioneer_remove_wp_svg_filters'] ) ) { + if ( isset( $options['fictioneer_theme_status'] ) ) { // Looping everything is not great but it only happens once! foreach ( $obsolete as $trash ) { delete_option( $trash ); @@ -120,19 +120,62 @@ function fictioneer_theme_setup() { add_image_size( 'snippet', 0, 200 ); // After update actions - $theme_status = get_option( 'fictioneer_theme_status' ); + $theme_info = fictioneer_get_theme_info(); - if ( empty( $theme_status ) || ( $theme_status['version'] ?? 0 ) !== FICTIONEER_VERSION ) { - $theme_status = is_array( $theme_status ) ? $theme_status : []; - $theme_status['version'] = FICTIONEER_VERSION; + if ( ( $theme_info['version'] ?? 0 ) !== FICTIONEER_VERSION ) { + $previous_version = $theme_info['version']; + $theme_info['version'] = FICTIONEER_VERSION; - update_option( 'fictioneer_theme_status', $theme_status ); + update_option( 'fictioneer_theme_info', $theme_info, 'yes' ); - do_action( 'fictioneer_after_update' ); + do_action( 'fictioneer_after_update', $theme_info['version'], $previous_version ); } } add_action( 'after_setup_theme', 'fictioneer_theme_setup' ); +/** + * Get basic theme info + * + * @since 5.19.1 + * + * @return array Associative array with theme info. + */ + +function fictioneer_get_theme_info() { + // Setup + $info = get_option( 'fictioneer_theme_info' ) ?: []; + + // Set up if missing + if ( ! $info || ! is_array( $info ) ) { + $info = array( + 'last_update_check' => current_time( 'mysql', 1 ), + 'last_update_version' => '', + 'last_update_nag' => current_time( 'mysql', 1 ), + 'last_update_notes' => '', + 'last_version_download_url' => '', + 'version' => FICTIONEER_VERSION + ); + + update_option( 'fictioneer_theme_info', $info, 'yes' ); + } + + // Merge with defaults (in case of incomplete data) + $info = array_merge( + array( + 'last_update_check' => current_time( 'mysql', 1 ), + 'last_update_version' => '', + 'last_update_nag' => '', + 'last_update_notes' => '', + 'last_version_download_url' => '', + 'version' => FICTIONEER_VERSION + ), + $info + ); + + // Return info + return $info; +} + // ============================================================================= // AFTER UPDATE // ============================================================================= diff --git a/includes/functions/settings/_settings_page_general.php b/includes/functions/settings/_settings_page_general.php index d4f89e90..df70ad53 100644 --- a/includes/functions/settings/_settings_page_general.php +++ b/includes/functions/settings/_settings_page_general.php @@ -26,9 +26,11 @@

Fictioneer %1$s is available. Please download and install the latest version at your next convenience.', 'fictioneer' ), - get_option( 'fictioneer_latest_version', FICTIONEER_RELEASE_TAG ), + $theme_info['last_update_version'], 'https://github.com/Tetrakern/fictioneer/releases' ); ?>