get_option( 'projects_portfolio_github_api_token', '' ), 'gitea_api_token' => get_option( 'projects_portfolio_gitea_api_token', '' ), 'default_gitea_base_url' => get_option( 'projects_portfolio_default_gitea_base_url', 'https://codeberg.org' ), 'share_telemetry' => get_option( 'projects_portfolio_share_telemetry', '0' ), 'templates' => [ 'version' => get_option( 'projects_portfolio_templates_version', '0' ), 'last_updated' => get_option( 'projects_portfolio_templates_last_updated', '0' ), 'license' => get_option( 'projects_portfolio_templates_license', '0' ), 'language' => get_option( 'projects_portfolio_templates_language', '0' ), 'downloads' => get_option( 'projects_portfolio_templates_downloads', '0' ), 'forks' => get_option( 'projects_portfolio_templates_forks', '0' ), 'stargazers_count' => get_option( 'projects_portfolio_templates_stargazers_count', '0' ), 'open_issues_count' => get_option( 'projects_portfolio_templates_open_issues_count', '0' ), 'github_owner' => get_option( 'projects_portfolio_templates_github_owner', '0' ), ], 'archives' => [ 'archive_title' => get_option( 'projects_portfolio_archives_archive_title', '0' ), 'project_title' => get_option( 'projects_portfolio_archives_project_title', '0' ), 'project_excerpt' => get_option( 'projects_portfolio_archives_project_excerpt', '0' ), 'project_buttons' => get_option( 'projects_portfolio_archives_project_buttons', '0' ), ] ]; } return $settings; } /** * Fetch normalized repository data for a project. * * @since 1.1.0 * @param int $post_id * @return array|null */ function projects_portfolio_get_repo_data( int $post_id ): ?array { return projects_portfolio_get_provider( $post_id )->get_repo_data(); } /** * Resolve the latest release ZIP download URL for a project. * * @since 1.1.0 * @param int $post_id * @return string|null */ function projects_portfolio_get_release_url( int $post_id ): ?string { return projects_portfolio_get_provider( $post_id )->get_release_url(); } /** * Fetch the latest tag name for a project. * * @since 1.1.0 * @param int $post_id * @return string */ function projects_portfolio_get_version( int $post_id ): string { return projects_portfolio_get_provider( $post_id )->get_latest_version(); } /** * Fetch the repo owner profile data for a project. * * @since 1.1.0 * @param int $post_id * @return array|null */ function projects_portfolio_get_owner( int $post_id ): ?array { $provider = projects_portfolio_get_provider( $post_id ); $repo = $provider->get_repo_data(); $login = $repo['owner']['login'] ?? ''; if ( '' === $login ) { return null; } return $provider->get_owner_data( $login ); } /** * Public browse URL for the project's repo. * * @since 1.1.0 * @param int $post_id * @return string */ function projects_portfolio_get_repo_browse_url( int $post_id ): string { return projects_portfolio_get_provider( $post_id )->get_repo_browse_url(); } /** * GitHub API call for owner data * * @param mixed $owner_name * * @since 1.0.0 * @return mixed */ function projects_portfolio_github_owner( $owner_name = NULL ) { _deprecated_function( __FUNCTION__, '1.1.0', 'projects_portfolio_get_owner( $post_id )' ); if ( ! $owner_name ) { return; } $owner = 'https://api.github.com/users/' . $owner_name; // Set up the context with a User-Agent header (GitHub requires this). $options = [ 'http' => [ 'method' => 'GET', 'header' => "User-Agent: WORDPRESS\r\n" ] ]; $context = stream_context_create( $options ); // Fetch the API response. $response = file_get_contents( $owner, false, $context ); if ( $response === false ) { die( 'Error fetching data from GitHub API.' ); } // Decode the JSON response to an associative array. $owner = json_decode( $response, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { die( 'Error decoding JSON data: ' . json_last_error_msg() ); } return $owner; } /** * Fetch GitHub latest release URL. * * @since 1.0.0 * @return mixed */ function projects_portfolio_get_github_release_url( $github_url ) { _deprecated_function( __FUNCTION__, '1.1.0', 'projects_portfolio_get_release_url( $post_id )' ); $api_token = get_option( 'projects_portfolio_github_api_token', '' ); $api_url = str_replace( 'https://github.com/', 'https://api.github.com/repos/', rtrim( $github_url, '/' ) ) . '/releases/latest'; $headers = [ 'Accept' => 'application/vnd.github.v3+json' ]; if ( $api_token ) { $headers['Authorization'] = 'token ' . $api_token; } $response = wp_remote_get( $api_url, [ 'headers' => $headers ] ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return false; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); // Check for assets and return the URL of a custom asset if available. if ( ! empty( $data['assets'] ) && is_array( $data['assets'] ) ) { foreach ( $data['assets'] as $asset ) { // For example, check if the asset is a zip file and maybe even match a specific name. if ( isset( $asset['name'] ) && pathinfo( $asset['name'], PATHINFO_EXTENSION ) === 'zip' ) { return $asset['browser_download_url']; } } } // Fallback to the auto-generated zipball_url. return $data['zipball_url'] ?? false; } /** * Retrieves GitHub repository data from the GitHub API with optional token authentication and caching. * * @param string $github_url The full URL of the GitHub repository. * * @since 1.0.0 * @return array|null Associative array of GitHub repo data on success, or null on failure. */ function projects_portfolio_get_github_data( $github_url ) { _deprecated_function( __FUNCTION__, '1.1.0', 'projects_portfolio_get_repo_data( $post_id )' ); if ( empty( $github_url ) ) { error_log( 'GitHub URL is empty.' ); return null; } $api_url = str_replace( 'https://github.com/', 'https://api.github.com/repos/', rtrim( $github_url, '/' ) ); $api_token = get_option( 'projects_portfolio_github_api_token', '' ); $headers = [ 'Accept' => 'application/vnd.github.v3+json' ]; if ( ! empty( $api_token ) ) { $headers['Authorization'] = 'token ' . $api_token; } else { error_log( 'GitHub API token is missing. Using unauthenticated requests.' ); } $cache_key = 'projects_portfolio_github_data_' . md5( $api_url ); $cached_data = get_transient( $cache_key ); if ( $cached_data ) { return $cached_data; } $response = wp_remote_get( $api_url, [ 'headers' => $headers ] ); if ( is_wp_error( $response ) ) { error_log( 'GitHub API error: ' . $response->get_error_message() ); return null; } $response_code = wp_remote_retrieve_response_code( $response ); if ( $response_code === 403 ) { $rate_limit_remaining = wp_remote_retrieve_header( $response, 'x-ratelimit-remaining' ); $rate_limit_reset = wp_remote_retrieve_header( $response, 'x-ratelimit-reset' ); error_log( 'GitHub API 403: Rate limit exceeded. Remaining: ' . $rate_limit_remaining . ' Reset at: ' . date( 'Y-m-d H:i:s', $rate_limit_reset ) ); return null; } elseif ( $response_code !== 200 ) { error_log( 'GitHub API error: Received status ' . $response_code ); return null; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $data ) || ! is_array( $data ) ) { error_log( 'GitHub API error: Invalid data received.' ); return null; } set_transient( $cache_key, $data, HOUR_IN_SECONDS ); return $data; } /** * Fetch the version number from the GitHub API. * * @param string $github_url The GitHub repository URL. * * @since 1.0.0 * @return string The version number or 'Unknown'. */ function projects_portfolio_get_version_from_github( $github_url ) { _deprecated_function( __FUNCTION__, '1.1.0', 'projects_portfolio_get_version( $post_id )' ); if ( empty( $github_url ) ) { return 'Unknown'; } $api_url = str_replace( 'https://github.com/', 'https://api.github.com/repos/', rtrim( $github_url, '/' ) ) . '/releases/latest'; $response = wp_remote_get( $api_url ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return 'Unknown'; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); return $data['tag_name'] ?? 'Unknown'; }