repo_url = rtrim( $repo_url, '/' ); $this->api_token = $api_token; } public function get_id(): string { return 'github'; } public function get_label(): string { return 'GitHub'; } public function get_repo_data(): ?array { if ( empty( $this->repo_url ) ) { error_log( 'Projects Portfolio: GitHub URL is empty.' ); return null; } $api_url = str_replace( 'https://github.com/', 'https://api.github.com/repos/', $this->repo_url ); $cache_key = 'projects_portfolio_provider_data_' . md5( $api_url ); $cached_data = get_transient( $cache_key ); if ( $cached_data ) { return $cached_data; } $headers = [ 'Accept' => 'application/vnd.github.v3+json' ]; if ( ! empty( $this->api_token ) ) { $headers['Authorization'] = 'token ' . $this->api_token; } else { error_log( 'Projects Portfolio: GitHub API token is missing. Using unauthenticated requests.' ); } $response = wp_remote_get( $api_url, [ 'headers' => $headers ] ); if ( is_wp_error( $response ) ) { error_log( 'Projects Portfolio: GitHub API error: ' . $response->get_error_message() ); return null; } $code = wp_remote_retrieve_response_code( $response ); if ( 403 === $code ) { $remaining = wp_remote_retrieve_header( $response, 'x-ratelimit-remaining' ); $reset = wp_remote_retrieve_header( $response, 'x-ratelimit-reset' ); error_log( 'Projects Portfolio: GitHub API 403: Rate limit exceeded. Remaining: ' . $remaining . ' Reset at: ' . date( 'Y-m-d H:i:s', (int) $reset ) ); return null; } if ( 200 !== $code ) { error_log( 'Projects Portfolio: GitHub API error: Received status ' . $code ); return null; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $data ) || ! is_array( $data ) ) { error_log( 'Projects Portfolio: GitHub API error: Invalid data received.' ); return null; } set_transient( $cache_key, $data, HOUR_IN_SECONDS ); return $data; } public function get_release_url(): ?string { $api_url = $this->release_api_url(); if ( null === $api_url ) { return null; } $cache_key = 'projects_portfolio_provider_release_' . md5( $api_url ); $cached = get_transient( $cache_key ); if ( $cached ) { return $cached; } $headers = [ 'Accept' => 'application/vnd.github.v3+json' ]; if ( ! empty( $this->api_token ) ) { $headers['Authorization'] = 'token ' . $this->api_token; } $response = wp_remote_get( $api_url, [ 'headers' => $headers ] ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return null; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $data ) ) { return null; } if ( ! empty( $data['assets'] ) && is_array( $data['assets'] ) ) { foreach ( $data['assets'] as $asset ) { if ( isset( $asset['name'] ) && 'zip' === pathinfo( $asset['name'], PATHINFO_EXTENSION ) ) { $url = $asset['browser_download_url'] ?? null; if ( $url ) { set_transient( $cache_key, $url, HOUR_IN_SECONDS ); return $url; } } } } $zipball = $data['zipball_url'] ?? null; if ( $zipball ) { set_transient( $cache_key, $zipball, HOUR_IN_SECONDS ); } return $zipball; } public function get_latest_version(): string { $api_url = $this->release_api_url(); if ( null === $api_url ) { return 'Unknown'; } $cache_key = 'projects_portfolio_provider_release_version_' . md5( $api_url ); $cached = get_transient( $cache_key ); if ( $cached ) { return $cached; } $response = wp_remote_get( $api_url, [] ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return 'Unknown'; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); $tag = is_array( $data ) && isset( $data['tag_name'] ) ? (string) $data['tag_name'] : 'Unknown'; set_transient( $cache_key, $tag, HOUR_IN_SECONDS ); return $tag; } public function get_repo_browse_url(): string { return $this->repo_url; } public function get_owner_data( string $owner_login ): ?array { if ( empty( $owner_login ) ) { return null; } $api_url = 'https://api.github.com/users/' . $owner_login; $ctx = stream_context_create( [ 'http' => [ 'method' => 'GET', 'header' => "User-Agent: WORDPRESS\r\n", ], ] ); $body = @file_get_contents( $api_url, false, $ctx ); if ( false === $body ) { error_log( 'Projects Portfolio: Error fetching data from GitHub API.' ); return null; } $data = json_decode( $body, true ); if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $data ) ) { error_log( 'Projects Portfolio: Error decoding JSON data: ' . json_last_error_msg() ); return null; } return [ 'avatar_url' => $data['avatar_url'] ?? '', 'login' => $data['login'] ?? $owner_login, 'html_url' => $data['html_url'] ?? ( 'https://github.com/' . $owner_login ), ]; } private function release_api_url(): ?string { if ( empty( $this->repo_url ) ) { return null; } return str_replace( 'https://github.com/', 'https://api.github.com/repos/', $this->repo_url ) . '/releases/latest'; } }