time() - 35 * DAY_IN_SECONDS ) { self::backup_completed( 'UpdraftPlus', (int) $last_backup['backup_time'], ! empty( $last_backup['success'] ) ); } return $last_backup; } /** * Records a finished backup, whether it worked or not. * * @param string $tool The backup tool's name. * @param int $time When it finished (Unix time). * @param bool $succeeded Whether it worked. */ public static function backup_completed( $tool, $time, $succeeded = true ) { Monthwrap_Store::record( array( 'type' => 'backup_completed', 'id' => 'backup_completed:' . sanitize_key( $tool ) . ':' . (int) $time, 'occurred_at' => gmdate( 'c', (int) $time ), 'name' => mb_substr( (string) $tool, 0, 200 ), 'status' => $succeeded ? 'succeeded' : 'failed', ) ); } /** * After an upgrade, check once the request is done (a bulk update upgrades several). */ public static function after_upgrade() { if ( ! self::$check_queued ) { self::$check_queued = true; add_action( 'shutdown', array( __CLASS__, 'check' ) ); } } /** * Remembers the current versions without reporting anything. Run on activation, so * the first check doesn't treat everything as new. */ public static function snapshot() { update_option( self::OPTION, self::installed(), false ); } /** * Queues an event for every version that went up since the last check. */ public static function check() { $before = get_option( self::OPTION ); $now = self::installed(); update_option( self::OPTION, $now, false ); if ( ! is_array( $before ) ) { return; } $self = plugin_basename( MONTHWRAP_FILE ); foreach ( array( 'plugin' => 'plugins', 'theme' => 'themes' ) as $kind => $list ) { foreach ( $now[ $list ] as $slug => $item ) { if ( $slug === $self ) { continue; } $old = $before[ $list ][ $slug ]['version'] ?? null; if ( null === $old ) { self::add( $kind . '_installed', $slug, $item, $item['version'] ); } elseif ( self::went_up( $old, $item['version'] ) ) { self::add( $kind . '_updated', $slug, $item, $item['version'], $old ); } } foreach ( $before[ $list ] ?? array() as $slug => $item ) { if ( $slug !== $self && ! isset( $now[ $list ][ $slug ] ) ) { self::add( $kind . '_removed', $slug, $item, current_time( 'Y-m-d' ) ); } } } foreach ( array( 'php' => 'PHP', 'db' => $now['db_engine'] ) as $part => $label ) { if ( isset( $before[ $part ] ) && self::went_up( $before[ $part ], $now[ $part ] ) ) { Monthwrap_Store::record( array( 'type' => 'server_updated', 'id' => 'server_updated:' . $part . ':' . $now[ $part ], 'occurred_at' => gmdate( 'c' ), 'name' => $label, 'from_version' => mb_substr( $before[ $part ], 0, 40 ), 'to_version' => mb_substr( $now[ $part ], 0, 40 ), ) ); } } self::updraftplus_backup( get_option( 'updraft_last_backup' ) ); if ( isset( $before['core'] ) && self::went_up( $before['core'], $now['core'] ) ) { Monthwrap_Store::record( array( 'type' => 'core_updated', 'id' => 'core_updated:' . $now['core'], 'occurred_at' => gmdate( 'c' ), 'from_version' => mb_substr( $before['core'], 0, 40 ), 'to_version' => mb_substr( $now['core'], 0, 40 ), ) ); } } /** * Queues a plugin or theme event. * * @param string $type e.g. "plugin_updated". * @param string $slug Plugin file or theme folder. * @param array $item Its name and version. * @param string $marker What makes this event unique (a version, or a day). * @param string|null $from The previous version, for updates. */ private static function add( $type, $slug, array $item, $marker, $from = null ) { $event = array( 'type' => $type, 'id' => $type . ':' . $slug . ':' . $marker, 'occurred_at' => gmdate( 'c' ), 'name' => mb_substr( '' !== $item['name'] ? $item['name'] : $slug, 0, 200 ), ); if ( '_removed' !== substr( $type, -8 ) ) { $event['to_version'] = mb_substr( $item['version'], 0, 40 ); } if ( null !== $from ) { $event['from_version'] = mb_substr( $from, 0, 40 ); } Monthwrap_Store::record( $event ); } /** * Whether a version moved up. Rollbacks and blanks don't count as work. * * @param string $old Previous version. * @param string $new Current version. * @return bool */ private static function went_up( $old, $new ) { return '' !== $new && '' !== $old && version_compare( $new, $old, '>' ); } /** * Installed versions, read fresh from disk. * * @return array{core: string, plugins: array, themes: array, php: string, db: string, db_engine: string} */ private static function installed() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } wp_clean_plugins_cache( false ); $plugins = array(); foreach ( get_plugins() as $file => $data ) { $plugins[ $file ] = array( 'name' => (string) $data['Name'], 'version' => (string) $data['Version'], ); } wp_clean_themes_cache( false ); $themes = array(); foreach ( wp_get_themes() as $slug => $theme ) { $themes[ $slug ] = array( 'name' => (string) $theme->get( 'Name' ), 'version' => (string) $theme->get( 'Version' ), ); } global $wpdb; $server_info = method_exists( $wpdb, 'db_server_info' ) ? (string) $wpdb->db_server_info() : ''; return array( 'core' => self::core_version(), 'plugins' => $plugins, 'themes' => $themes, 'php' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION, 'db' => (string) $wpdb->db_version(), 'db_engine' => false !== stripos( $server_info, 'mariadb' ) ? 'MariaDB' : 'MySQL', ); } /** * WordPress's version as installed on disk. After a core update in this same request, * the global $wp_version still holds the old one, so the version is read from the file. * * @return string */ private static function core_version() { $source = (string) file_get_contents( ABSPATH . WPINC . '/version.php' ); return preg_match( '/^\$wp_version\s*=\s*\'([^\']+)\'/m', $source, $match ) ? $match[1] : get_bloginfo( 'version' ); } }