! ( is_string( $item ) && str_contains( $item, 'open-plugin-details-modal' ) ) ) ); $meta[] = sprintf( '%s', esc_url( self::REPO_URL . '/releases/tag/v' . USC_VERSION ), esc_html__( 'View details', 'unsupervised-schedular' ) ); return $meta; } /** * `update_plugins_{hostname}` filter callback. Returns the incoming * value untouched unless a newer release with a zip asset exists, in * which case it returns the update array core expects. */ public function provideUpdate( mixed $update, mixed $plugin_data, mixed $plugin_file ): mixed { if ( plugin_basename( USC_PLUGIN_FILE ) !== $plugin_file ) { return $update; } $release = $this->latestRelease(); if ( '' === $release['version'] || '' === $release['package'] ) { return $update; } if ( version_compare( $release['version'], USC_VERSION, '<=' ) ) { return $update; } return [ 'slug' => 'unsupervised-schedular', 'version' => $release['version'], 'url' => self::REPO_URL, 'package' => $release['package'], ]; } /** * The latest published release, from the transient cache when fresh. * * @return array{version: string, package: string} Empty strings when no * usable release exists. */ private function latestRelease(): array { $cached = get_transient( self::TRANSIENT ); if ( is_array( $cached ) ) { return [ 'version' => Val::string( $cached['version'] ?? '' ), 'package' => Val::string( $cached['package'] ?? '' ), ]; } $release = $this->fetchLatestRelease(); set_transient( self::TRANSIENT, $release, self::CACHE_TTL ); return $release; } /** * Ask the Gitea API for the latest published release's version and zip asset. * * @return array{version: string, package: string} */ private function fetchLatestRelease(): array { $none = [ 'version' => '', 'package' => '', ]; $response = wp_remote_get( self::API_URL, [ 'timeout' => 10, 'headers' => [ 'Accept' => 'application/json' ], ] ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return $none; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $body ) ) { return $none; } // Release tags are named v1.2.3; the plugin header carries the bare version. $version = preg_replace( '/^v/i', '', Val::string( $body['tag_name'] ?? '' ) ) ?? ''; // The release workflow attaches the built plugin zip (top-level // unsupervised-schedular/ folder, production autoloader) as an asset. // Gitea's auto-generated source archives are not usable packages. $package = ''; $assets = $body['assets'] ?? null; if ( is_array( $assets ) ) { foreach ( $assets as $asset ) { if ( ! is_array( $asset ) ) { continue; } if ( str_ends_with( strtolower( Val::string( $asset['name'] ?? '' ) ), '.zip' ) ) { $package = Val::string( $asset['browser_download_url'] ?? '' ); break; } } } if ( '' === $version || '' === $package ) { return $none; } return [ 'version' => $version, 'package' => $package, ]; } }