added additional plugins

This commit is contained in:
Rodolfo Martinez 2025-12-12 19:05:48 -05:00
parent c85895d306
commit 00e60ec1b7
132 changed files with 27514 additions and 0 deletions

View file

@ -0,0 +1,749 @@
<?php
/**
* Git Platform API Handler Class
* Handles API interactions for GitHub, GitLab, Bitbucket, and Codeberg
*/
// Prevent direct access
if (!defined('WPINC')) {
die('Direct access not permitted.');
}
if (!defined('ABSPATH')) {
exit;
}
class MCB_GitHub_API {
private $cache_duration = 3600; // 1 hour cache
// Platform API configurations
private $platforms = array(
'github' => array(
'name' => 'GitHub',
'api_base' => 'https://api.github.com',
'repo_pattern' => '/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/',
'web_base' => 'https://github.com'
),
'gitlab' => array(
'name' => 'GitLab',
'api_base' => 'https://gitlab.com/api/v4',
'repo_pattern' => '/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/',
'web_base' => 'https://gitlab.com'
),
'bitbucket' => array(
'name' => 'Bitbucket',
'api_base' => 'https://api.bitbucket.org/2.0',
'repo_pattern' => '/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/',
'web_base' => 'https://bitbucket.org'
),
'codeberg' => array(
'name' => 'Codeberg',
'api_base' => 'https://codeberg.org/api/v1',
'repo_pattern' => '/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/',
'web_base' => 'https://codeberg.org'
)
);
/**
* Detect platform from repository string
*/
private function detect_platform($repo_string) {
// Check for platform prefix (e.g., "gitlab:owner/repo")
if (strpos($repo_string, ':') !== false) {
list($platform, $repo) = explode(':', $repo_string, 2);
if (isset($this->platforms[$platform])) {
return array('platform' => $platform, 'repo' => $repo);
}
}
// Check for full URLs
if (strpos($repo_string, 'https://') === 0 || strpos($repo_string, 'http://') === 0) {
foreach ($this->platforms as $key => $config) {
if (strpos($repo_string, $config['web_base']) !== false) {
// Extract owner/repo from URL
$parsed = parse_url($repo_string);
$path = trim($parsed['path'], '/');
$parts = explode('/', $path);
if (count($parts) >= 2) {
return array(
'platform' => $key,
'repo' => $parts[0] . '/' . $parts[1]
);
}
}
}
}
// Default to GitHub for backward compatibility
return array('platform' => 'github', 'repo' => $repo_string);
}
/**
* Get repository files list
*/
public function get_repository_files($repo_string, $path = '') {
$platform_info = $this->detect_platform($repo_string);
$platform = $platform_info['platform'];
$repo = $platform_info['repo'];
// Validate repo format
if (!preg_match($this->platforms[$platform]['repo_pattern'], $repo)) {
return new WP_Error('invalid_repo', 'Invalid repository format. Use: owner/repository');
}
// Check cache first
$cache_key = 'mcb_repo_' . $platform . '_' . md5($repo . '_' . $path);
$cached_data = get_transient($cache_key);
if ($cached_data !== false) {
return $cached_data;
}
// Get files based on platform
switch ($platform) {
case 'github':
$files = $this->get_github_files($repo, $path);
break;
case 'gitlab':
$files = $this->get_gitlab_files($repo, $path);
break;
case 'bitbucket':
$files = $this->get_bitbucket_files($repo, $path);
break;
case 'codeberg':
$files = $this->get_codeberg_files($repo, $path);
break;
default:
return new WP_Error('unsupported_platform', 'Unsupported platform');
}
if (!is_wp_error($files)) {
// Cache the results
set_transient($cache_key, $files, $this->cache_duration);
}
return $files;
}
/**
* Get file content from repository
*/
public function get_file_content($repo_string, $file_path) {
$platform_info = $this->detect_platform($repo_string);
$platform = $platform_info['platform'];
$repo = $platform_info['repo'];
// Validate inputs
if (!preg_match($this->platforms[$platform]['repo_pattern'], $repo)) {
return new WP_Error('invalid_repo', 'Invalid repository format');
}
// Sanitize and validate file path
$file_path = trim($file_path, '/');
// Block path traversal attempts
if (strpos($file_path, '..') !== false ||
strpos($file_path, '//') !== false ||
strpos($file_path, '\\') !== false ||
preg_match('/[<>"|*?]/', $file_path)) {
return new WP_Error('invalid_path', 'Invalid file path');
}
// Check cache
$cache_key = 'mcb_file_' . $platform . '_' . md5($repo . $file_path);
$cached_content = get_transient($cache_key);
if ($cached_content !== false) {
return $cached_content;
}
// Get content based on platform
switch ($platform) {
case 'github':
$content = $this->get_github_file_content($repo, $file_path);
break;
case 'gitlab':
$content = $this->get_gitlab_file_content($repo, $file_path);
break;
case 'bitbucket':
$content = $this->get_bitbucket_file_content($repo, $file_path);
break;
case 'codeberg':
$content = $this->get_codeberg_file_content($repo, $file_path);
break;
default:
return new WP_Error('unsupported_platform', 'Unsupported platform');
}
if (!is_wp_error($content)) {
// Cache the content
set_transient($cache_key, $content, $this->cache_duration);
}
return $content;
}
/**
* GitHub-specific file listing
*/
private function get_github_files($repo, $path = '') {
$url = $this->platforms['github']['api_base'] . '/repos/' . $repo . '/contents';
if (!empty($path)) {
$url .= '/' . ltrim($path, '/');
}
$response = $this->make_api_request($url, 'github');
if (is_wp_error($response)) {
return $response;
}
return $this->parse_github_contents($response, $repo, $path);
}
/**
* GitLab-specific file listing
*/
private function get_gitlab_files($repo, $path = '') {
// GitLab uses project ID or URL-encoded path
$project_id = urlencode($repo);
$url = $this->platforms['gitlab']['api_base'] . '/projects/' . $project_id . '/repository/tree?recursive=true&per_page=100';
$response = $this->make_api_request($url, 'gitlab');
if (is_wp_error($response)) {
return $response;
}
return $this->parse_gitlab_contents($response);
}
/**
* Bitbucket-specific file listing
*/
private function get_bitbucket_files($repo, $path = '') {
$url = $this->platforms['bitbucket']['api_base'] . '/repositories/' . $repo . '/src';
$response = $this->make_api_request($url, 'bitbucket');
if (is_wp_error($response)) {
return $response;
}
return $this->parse_bitbucket_contents($response, $repo);
}
/**
* Codeberg-specific file listing (uses Gitea API)
*/
private function get_codeberg_files($repo, $path = '') {
$url = $this->platforms['codeberg']['api_base'] . '/repos/' . $repo . '/contents';
if (!empty($path)) {
$url .= '/' . ltrim($path, '/');
}
$response = $this->make_api_request($url, 'codeberg');
if (is_wp_error($response)) {
return $response;
}
// Codeberg uses Gitea, similar to GitHub API
return $this->parse_github_contents($response, $repo, $path);
}
/**
* GitHub file content retrieval
*/
private function get_github_file_content($repo, $file_path) {
$url = $this->platforms['github']['api_base'] . '/repos/' . $repo . '/contents/' . $file_path;
$response = $this->make_api_request($url, 'github');
if (is_wp_error($response)) {
return $response;
}
$data = json_decode($response, true);
if (!isset($data['content'])) {
return new WP_Error('no_content', 'File content not found');
}
return base64_decode($data['content']);
}
/**
* GitLab file content retrieval
*/
private function get_gitlab_file_content($repo, $file_path) {
$project_id = urlencode($repo);
$file_path_encoded = urlencode($file_path);
$url = $this->platforms['gitlab']['api_base'] . '/projects/' . $project_id . '/repository/files/' . $file_path_encoded . '/raw?ref=main';
// Try main branch first, then master
$response = $this->make_api_request($url, 'gitlab');
if (is_wp_error($response)) {
// Try master branch
$url = $this->platforms['gitlab']['api_base'] . '/projects/' . $project_id . '/repository/files/' . $file_path_encoded . '/raw?ref=master';
$response = $this->make_api_request($url, 'gitlab');
}
return $response;
}
/**
* Bitbucket file content retrieval
*/
private function get_bitbucket_file_content($repo, $file_path) {
// Get default branch first
$repo_url = $this->platforms['bitbucket']['api_base'] . '/repositories/' . $repo;
$repo_response = $this->make_api_request($repo_url, 'bitbucket');
if (is_wp_error($repo_response)) {
return $repo_response;
}
$repo_data = json_decode($repo_response, true);
$branch = isset($repo_data['mainbranch']['name']) ? $repo_data['mainbranch']['name'] : 'master';
$url = $this->platforms['bitbucket']['api_base'] . '/repositories/' . $repo . '/src/' . $branch . '/' . $file_path;
return $this->make_api_request($url, 'bitbucket');
}
/**
* Codeberg file content retrieval
*/
private function get_codeberg_file_content($repo, $file_path) {
// Codeberg uses Gitea API, similar to GitHub
$url = $this->platforms['codeberg']['api_base'] . '/repos/' . $repo . '/contents/' . $file_path;
$response = $this->make_api_request($url, 'codeberg');
if (is_wp_error($response)) {
return $response;
}
$data = json_decode($response, true);
if (!isset($data['content'])) {
return new WP_Error('no_content', 'File content not found');
}
return base64_decode($data['content']);
}
/**
* Make HTTP request to Git platform API
*/
private function make_api_request($url, $platform) {
// SSRF Protection - validate URL
$parsed_url = parse_url($url);
// Only allow HTTPS protocol
if ($parsed_url['scheme'] !== 'https') {
return new WP_Error('invalid_protocol', 'Only HTTPS is allowed');
}
// Only allow known platform hosts
$allowed_hosts = array(
'api.github.com',
'gitlab.com',
'api.bitbucket.org',
'codeberg.org'
);
if (!in_array($parsed_url['host'], $allowed_hosts)) {
return new WP_Error('invalid_host', 'Invalid API host');
}
// Add request throttling to prevent rate limit issues
static $last_request_time = 0;
$min_interval = 0.1; // Minimum 100ms between requests
$current_time = microtime(true);
$time_since_last = $current_time - $last_request_time;
if ($time_since_last < $min_interval) {
usleep(($min_interval - $time_since_last) * 1000000);
}
$args = array(
'timeout' => 10, // Reduced timeout to prevent hanging
'redirection' => 3, // Limit redirects
'headers' => $this->get_platform_headers($platform)
);
// Add authentication if available
$token = $this->get_platform_token($platform);
if (!empty($token)) {
$args['headers']['Authorization'] = $this->get_auth_header($platform, $token);
}
$response = wp_remote_get($url, $args);
if (is_wp_error($response)) {
return $response;
}
$status_code = wp_remote_retrieve_response_code($response);
if ($status_code !== 200) {
return new WP_Error('api_error', 'API returned status: ' . $status_code);
}
$last_request_time = microtime(true);
return wp_remote_retrieve_body($response);
}
/**
* Get platform-specific headers
*/
private function get_platform_headers($platform) {
switch ($platform) {
case 'github':
return array(
'Accept' => 'application/vnd.github.v3+json',
'User-Agent' => 'WordPress/GitHub-Code-Viewer'
);
case 'gitlab':
return array(
'Accept' => 'application/json',
'User-Agent' => 'WordPress/GitHub-Code-Viewer'
);
case 'bitbucket':
return array(
'Accept' => 'application/json',
'User-Agent' => 'WordPress/GitHub-Code-Viewer'
);
case 'codeberg':
return array(
'Accept' => 'application/json',
'User-Agent' => 'WordPress/GitHub-Code-Viewer'
);
default:
return array(
'User-Agent' => 'WordPress/GitHub-Code-Viewer'
);
}
}
/**
* Get platform token if configured
*/
private function get_platform_token($platform) {
// Use secure token manager if available
if (class_exists('MCB_Token_Manager')) {
return MCB_Token_Manager::get_token($platform);
}
// Fallback to direct option (for backwards compatibility)
return get_option('mcb_' . $platform . '_token', '');
}
/**
* Get authorization header format for platform
*/
private function get_auth_header($platform, $token) {
switch ($platform) {
case 'github':
return 'token ' . $token;
case 'gitlab':
return 'Bearer ' . $token;
case 'bitbucket':
return 'Bearer ' . $token;
case 'codeberg':
return 'token ' . $token;
default:
return 'Bearer ' . $token;
}
}
/**
* Parse GitHub/Codeberg repository contents
*/
private function parse_github_contents($response, $repo, $current_path = '') {
$items = json_decode($response, true);
$files = array();
if (!is_array($items)) {
return $files;
}
// Add parent directory navigation if not at root
if (!empty($current_path)) {
$parent_path = dirname($current_path);
if ($parent_path === '.') {
$parent_path = '';
}
$files[] = array(
'name' => '..',
'path' => $parent_path,
'size' => 0,
'type' => 'parent',
'is_folder' => true,
'url' => ''
);
}
$code_extensions = $this->get_code_extensions();
// First add all directories
foreach ($items as $item) {
if ($item['type'] === 'dir') {
$files[] = array(
'name' => $item['name'],
'path' => $item['path'],
'size' => 0,
'type' => 'folder',
'is_folder' => true,
'url' => $item['html_url']
);
}
}
// Then add all files
foreach ($items as $item) {
if ($item['type'] === 'file') {
$extension = strtolower(pathinfo($item['name'], PATHINFO_EXTENSION));
$name_lower = strtolower($item['name']);
// Include all files, not just code files, for better browsing
$files[] = array(
'name' => $item['name'],
'path' => $item['path'],
'size' => $item['size'],
'type' => $this->get_file_type($item['name']),
'is_folder' => false,
'url' => $item['html_url']
);
}
}
return $files;
}
/**
* Parse GitLab repository contents
*/
private function parse_gitlab_contents($response) {
$items = json_decode($response, true);
$files = array();
if (!is_array($items)) {
return $files;
}
$code_extensions = $this->get_code_extensions();
foreach ($items as $item) {
if ($item['type'] === 'blob') { // GitLab uses 'blob' for files
$extension = strtolower(pathinfo($item['name'], PATHINFO_EXTENSION));
$name_lower = strtolower($item['name']);
if (in_array($extension, $code_extensions) || $this->is_code_file($name_lower)) {
$files[] = array(
'name' => $item['name'],
'path' => $item['path'],
'size' => 0, // GitLab doesn't provide size in tree API
'type' => $this->get_file_type($item['name']),
'url' => '' // Will need to construct if needed
);
}
}
}
return $files;
}
/**
* Parse Bitbucket repository contents
*/
private function parse_bitbucket_contents($response, $repo) {
$data = json_decode($response, true);
$files = array();
if (!isset($data['values']) || !is_array($data['values'])) {
return $files;
}
$code_extensions = $this->get_code_extensions();
foreach ($data['values'] as $item) {
if ($item['type'] === 'commit_file') {
$extension = strtolower(pathinfo($item['path'], PATHINFO_EXTENSION));
$name_lower = strtolower(basename($item['path']));
if (in_array($extension, $code_extensions) || $this->is_code_file($name_lower)) {
$files[] = array(
'name' => basename($item['path']),
'path' => $item['path'],
'size' => isset($item['size']) ? $item['size'] : 0,
'type' => $this->get_file_type(basename($item['path'])),
'url' => 'https://bitbucket.org/' . $repo . '/src/master/' . $item['path']
);
}
}
}
return $files;
}
/**
* Get list of code file extensions
*/
private function get_code_extensions() {
return array(
'php', 'js', 'jsx', 'ts', 'tsx', 'py', 'rb', 'java', 'c', 'cpp', 'cc', 'cxx',
'h', 'hpp', 'cs', 'swift', 'kt', 'go', 'rs', 'scala', 'r', 'sql', 'sh', 'bash',
'yml', 'yaml', 'json', 'xml', 'html', 'css', 'scss', 'sass', 'less',
'md', 'markdown', 'txt', 'ini', 'conf', 'dockerfile', 'makefile',
'vue', 'svelte', 'elm', 'clj', 'ex', 'exs', 'erl', 'hrl', 'lua', 'pl', 'pm'
);
}
/**
* Check if filename indicates a code file
*/
private function is_code_file($filename) {
$code_files = array(
'dockerfile', 'makefile', '.gitignore', '.env', '.htaccess',
'gemfile', 'rakefile', 'gulpfile', 'gruntfile', 'webpack.config.js',
'package.json', 'composer.json', 'cargo.toml', 'go.mod'
);
return in_array($filename, $code_files);
}
/**
* Get file type based on extension
*/
private function get_file_type($filename) {
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$name_lower = strtolower($filename);
$type_map = array(
'php' => 'php',
'js' => 'javascript',
'jsx' => 'jsx',
'ts' => 'typescript',
'tsx' => 'tsx',
'py' => 'python',
'rb' => 'ruby',
'java' => 'java',
'c' => 'c',
'cpp' => 'cpp',
'cc' => 'cpp',
'cxx' => 'cpp',
'h' => 'c',
'hpp' => 'cpp',
'cs' => 'csharp',
'swift' => 'swift',
'kt' => 'kotlin',
'go' => 'go',
'rs' => 'rust',
'scala' => 'scala',
'r' => 'r',
'sql' => 'sql',
'sh' => 'bash',
'bash' => 'bash',
'yml' => 'yaml',
'yaml' => 'yaml',
'json' => 'json',
'xml' => 'xml',
'html' => 'html',
'css' => 'css',
'scss' => 'scss',
'sass' => 'sass',
'less' => 'less',
'md' => 'markdown',
'markdown' => 'markdown'
);
// Check special file names
if ($name_lower === 'dockerfile') {
return 'docker';
}
if ($name_lower === 'makefile') {
return 'makefile';
}
return isset($type_map[$extension]) ? $type_map[$extension] : 'plain';
}
/**
* Validate if repository exists
*/
public function validate_repository_exists($repo_string) {
$platform_info = $this->detect_platform($repo_string);
$platform = $platform_info['platform'];
$repo = $platform_info['repo'];
// Validate format first
if (!preg_match($this->platforms[$platform]['repo_pattern'], $repo)) {
return new WP_Error('invalid_repo', 'Invalid repository format');
}
// Check cache first
$cache_key = 'mcb_repo_valid_' . $platform . '_' . md5($repo);
$cached_result = get_transient($cache_key);
if ($cached_result !== false) {
return $cached_result;
}
// Check with platform API
$exists = false;
switch ($platform) {
case 'github':
$url = $this->platforms['github']['api_base'] . '/repos/' . $repo;
break;
case 'gitlab':
$project_id = urlencode($repo);
$url = $this->platforms['gitlab']['api_base'] . '/projects/' . $project_id;
break;
case 'bitbucket':
$url = $this->platforms['bitbucket']['api_base'] . '/repositories/' . $repo;
break;
case 'codeberg':
$url = $this->platforms['codeberg']['api_base'] . '/repos/' . $repo;
break;
default:
return new WP_Error('unsupported_platform', 'Unsupported platform');
}
$response = $this->make_api_request($url, $platform);
if (!is_wp_error($response)) {
$data = json_decode($response, true);
if (isset($data['id']) || isset($data['uuid']) || isset($data['name'])) {
$exists = true;
}
}
if ($exists) {
set_transient($cache_key, true, 3600);
return true;
}
return new WP_Error('repo_not_found', 'Repository not found or is private');
}
/**
* Get platform display name
*/
public function get_platform_name($repo_string) {
$platform_info = $this->detect_platform($repo_string);
return $this->platforms[$platform_info['platform']]['name'];
}
/**
* Get repository web URL
*/
public function get_repository_url($repo_string) {
$platform_info = $this->detect_platform($repo_string);
$platform = $platform_info['platform'];
$repo = $platform_info['repo'];
return $this->platforms[$platform]['web_base'] . '/' . $repo;
}
}