register_block(); } public function load_textdomain(): void { load_plugin_textdomain( self::TEXT_DOMAIN, false, dirname(plugin_basename(__FILE__)) . "/languages", ); } private function register_block(): void { register_block_type(self::BLOCK_NAME, [ "editor_script" => "git-embed-feicode-editor", "editor_style" => "git-embed-feicode-style", "style" => "git-embed-feicode-style", "render_callback" => [$this, "render_block"], "attributes" => [ "platform" => [ "type" => "string", "default" => "github", ], "customDomain" => [ "type" => "string", "default" => "", ], "customSiteName" => [ "type" => "string", "default" => "", ], "owner" => [ "type" => "string", "default" => "", ], "repo" => [ "type" => "string", "default" => "", ], "showDescription" => [ "type" => "boolean", "default" => true, ], "showStats" => [ "type" => "boolean", "default" => true, ], "showLanguage" => [ "type" => "boolean", "default" => true, ], "showActions" => [ "type" => "boolean", "default" => true, ], "showViewButton" => [ "type" => "boolean", "default" => true, ], "showCloneButton" => [ "type" => "boolean", "default" => true, ], "showDownloadButton" => [ "type" => "boolean", "default" => true, ], "cardStyle" => [ "type" => "string", "default" => "default", ], "buttonStyle" => [ "type" => "string", "default" => "default", ], "buttonSize" => [ "type" => "string", "default" => "medium", ], "showIssuesButton" => [ "type" => "boolean", "default" => false, ], "showForksButton" => [ "type" => "boolean", "default" => false, ], "showAvatar" => [ "type" => "boolean", "default" => true, ], "showSiteInfo" => [ "type" => "boolean", "default" => true, ], "avatarSize" => [ "type" => "string", "default" => "medium", ], "alignment" => [ "type" => "string", "default" => "none", ], ], ]); wp_register_script( "git-embed-feicode-editor", plugin_dir_url(__FILE__) . "assets/block.js", [ "wp-blocks", "wp-element", "wp-editor", "wp-components", "wp-i18n", ], self::PLUGIN_VERSION, ); wp_register_style( "git-embed-feicode-style", plugin_dir_url(__FILE__) . "assets/style.css", [], self::PLUGIN_VERSION, ); wp_set_script_translations( "git-embed-feicode-editor", self::TEXT_DOMAIN, plugin_dir_path(__FILE__) . "languages", ); wp_localize_script("git-embed-feicode-editor", "gitEmbedAjax", [ "url" => admin_url("admin-ajax.php"), "nonce" => wp_create_nonce("git_embed_nonce"), "cache_nonce" => wp_create_nonce("git_embed_cache_nonce"), ]); } public function render_block(array $attributes): string { $owner = sanitize_text_field($attributes["owner"] ?? ""); $repo = sanitize_text_field($attributes["repo"] ?? ""); $platform = sanitize_text_field($attributes["platform"] ?? "github"); $custom_domain = sanitize_text_field($attributes["customDomain"] ?? ""); $custom_site_name = sanitize_text_field( $attributes["customSiteName"] ?? "", ); if (empty($owner) || empty($repo)) { return '
"; } if ( in_array($platform, ["gitea", "forgejo", "gitlab", "custom"]) && empty($custom_domain) ) { return ' "; } $repo_data = $this->fetch_repository_data( $platform, $owner, $repo, $custom_domain, $custom_site_name, ); if (!$repo_data) { return ' "; } return $this->render_repository_card($repo_data, $attributes); } private function fetch_repository_data( string $platform, string $owner, string $repo, string $custom_domain = "", string $custom_site_name = "", ): ?array { $cache_key = "git_embed_{$platform}_{$owner}_{$repo}" . ($custom_domain ? "_{$custom_domain}" : "") . ($custom_site_name ? "_{$custom_site_name}" : ""); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } $api_config = $this->get_api_config($platform, $custom_domain, $custom_site_name); if (!$api_config) { $this->log_debug("Failed to get API config for platform: {$platform}"); return null; } $repo_data = $this->fetch_repository_info($api_config, $owner, $repo, $platform); if (!$repo_data) { return null; } $latest_release = $this->fetch_latest_release($api_config, $owner, $repo, $platform); $repo_data["download_info"] = $this->generate_download_info( $platform, $api_config, $owner, $repo, $repo_data, $latest_release ); $repo_data["archive_url"] = $repo_data["download_info"]["url"]; $this->log_debug("Platform: {$platform}"); $this->log_debug("API URL: " . $api_config["api_url"]); $this->log_debug("Base URL: " . $api_config["base_url"]); $this->log_debug("Final download URL: " . $repo_data["archive_url"]); $this->log_debug("Download filename: " . $repo_data["download_info"]["filename"]); set_transient($cache_key, $repo_data, DAY_IN_SECONDS); if (!empty($repo_data["repo_avatar_url"])) { $this->cache_avatar($repo_data["repo_avatar_url"]); } elseif (!empty($repo_data["owner"]["avatar_url"])) { $this->cache_avatar($repo_data["owner"]["avatar_url"]); } return $repo_data; } private function fetch_repository_info(array $api_config, string $owner, string $repo, string $platform): ?array { $url = $api_config["api_url"] . "/repos/{$owner}/{$repo}"; $this->log_debug("Fetching repo info from: {$url}"); $response = wp_remote_get($url, [ "timeout" => 15, "headers" => [ "User-Agent" => "Git-Embed-FeiCode/" . self::PLUGIN_VERSION, "Accept" => "application/json", ], ]); if (is_wp_error($response)) { $this->log_debug("WP Error: " . $response->get_error_message()); return null; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { $this->log_debug("HTTP {$response_code} error"); return null; } $data = json_decode(wp_remote_retrieve_body($response), true); if (!$data) { $this->log_debug("Failed to parse JSON response"); return null; } return $this->normalize_repository_data($data, $platform, $api_config); } private function fetch_latest_release(array $api_config, string $owner, string $repo, string $platform): ?array { if ($platform === "gitlab") { $releases_url = $api_config["api_url"] . "/projects/" . urlencode("{$owner}/{$repo}") . "/releases"; } else { $releases_url = $api_config["api_url"] . "/repos/{$owner}/{$repo}/releases/latest"; } $this->log_debug("Fetching latest release from: {$releases_url}"); $response = wp_remote_get($releases_url, [ "timeout" => 10, "headers" => [ "User-Agent" => "Git-Embed-FeiCode/" . self::PLUGIN_VERSION, "Accept" => "application/json", ], ]); if (is_wp_error($response)) { $this->log_debug("Release fetch error: " . $response->get_error_message()); return null; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code === 404) { $this->log_debug("No releases found (404)"); return null; } if ($response_code !== 200) { $this->log_debug("Release API returned {$response_code}"); return null; } $data = json_decode(wp_remote_retrieve_body($response), true); if (!$data) { return null; } if ($platform === "gitlab") { if (is_array($data) && !empty($data)) { $latest = $data[0]; return [ "tag_name" => $latest["tag_name"], "name" => $latest["name"] ?: $latest["tag_name"], ]; } return null; } if (isset($data["tag_name"])) { return [ "tag_name" => $data["tag_name"], "name" => $data["name"] ?: $data["tag_name"], "draft" => $data["draft"] ?? false, ]; } return null; } private function generate_download_info( string $platform, array $api_config, string $owner, string $repo, array $repo_data, ?array $latest_release ): array { if ($latest_release && (!isset($latest_release["draft"]) || !$latest_release["draft"])) { $tag_name = $latest_release["tag_name"]; $this->log_debug("Using release {$tag_name} for download"); return [ "type" => "release", "version" => $tag_name, "name" => $latest_release["name"] ?: $tag_name, "url" => $this->build_archive_url($platform, $api_config, $owner, $repo, $tag_name, true), "filename" => $this->generate_filename($repo_data["name"], $tag_name, true) ]; } $default_branch = $repo_data["default_branch"] ?: $this->detect_default_branch($api_config, $owner, $repo, $platform); $this->log_debug("Using branch '{$default_branch}' for download"); return [ "type" => "branch", "version" => $default_branch, "name" => __("Latest Code", self::TEXT_DOMAIN), "url" => $this->build_archive_url($platform, $api_config, $owner, $repo, $default_branch, false), "filename" => $this->generate_filename($repo_data["name"], $default_branch, false) ]; } private function build_archive_url( string $platform, array $api_config, string $owner, string $repo, string $ref, bool $is_release ): string { $base_url = $api_config["base_url"]; $api_url = $api_config["api_url"]; switch ($platform) { case "github": $prefix = $is_release ? "refs/tags" : "refs/heads"; return "https://github.com/{$owner}/{$repo}/archive/{$prefix}/{$ref}.zip"; case "gitlab": return "{$base_url}/{$owner}/{$repo}/-/archive/{$ref}/{$repo}-{$ref}.zip"; case "gitea": case "forgejo": case "custom": return "{$api_url}/repos/{$owner}/{$repo}/archive/{$ref}.zip"; default: return ""; } } private function detect_default_branch(array $api_config, string $owner, string $repo, string $platform): string { $branches_url = $api_config["api_url"] . "/repos/{$owner}/{$repo}/branches"; $response = wp_remote_get($branches_url, [ "timeout" => 10, "headers" => [ "User-Agent" => "Git-Embed-FeiCode/" . self::PLUGIN_VERSION, "Accept" => "application/json", ], ]); if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) { $branches = json_decode(wp_remote_retrieve_body($response), true); if (is_array($branches) && !empty($branches)) { foreach ($branches as $branch) { if (isset($branch["name"]) && in_array($branch["name"], ["main", "master"])) { return $branch["name"]; } } if (isset($branches[0]["name"])) { return $branches[0]["name"]; } } } return "main"; } private function generate_filename(string $repo_name, string $ref, bool $is_release): string { $safe_repo = preg_replace('/[^a-zA-Z0-9\-_.]/', '-', $repo_name); $clean_ref = $ref; if (strpos($ref, 'refs/heads/') === 0) { $clean_ref = substr($ref, 11); } elseif (strpos($ref, 'refs/tags/') === 0) { $clean_ref = substr($ref, 10); } $safe_ref = preg_replace('/[^a-zA-Z0-9\-_.]/', '-', $clean_ref); if ($is_release) { $safe_ref = preg_replace('/^v/', '', $safe_ref); } return "{$safe_repo}-{$safe_ref}.zip"; } private function log_debug(string $message): void { if (WP_DEBUG) { error_log("Git Embed Debug: {$message}"); } } private function get_api_config( string $platform, string $custom_domain = "", string $custom_site_name = "", ): ?array { switch ($platform) { case "github": return [ "api_url" => "https://api.github.com", "base_url" => "https://github.com", "site_info" => [ "name" => "GitHub", "url" => "https://github.com", "favicon" => "https://github.com/favicon.ico", "color" => "#24292f", ], ]; case "gitea": if (empty($custom_domain)) { return null; } $domain = $this->normalize_domain($custom_domain); $site_name = $custom_site_name ?: $this->get_site_name($domain, "Gitea"); return [ "api_url" => "https://{$domain}/api/v1", "base_url" => "https://{$domain}", "site_info" => [ "name" => $site_name, "url" => "https://{$domain}", "favicon" => "https://{$domain}/assets/img/favicon.png", "color" => "#609926", ], ]; case "forgejo": if (empty($custom_domain)) { return null; } $domain = $this->normalize_domain($custom_domain); $site_name = $custom_site_name ?: $this->get_site_name($domain, "Forgejo"); return [ "api_url" => "https://{$domain}/api/v1", "base_url" => "https://{$domain}", "site_info" => [ "name" => $site_name, "url" => "https://{$domain}", "favicon" => "https://{$domain}/assets/img/favicon.png", "color" => "#fb923c", ], ]; case "gitlab": if (empty($custom_domain)) { return null; } $domain = $this->normalize_domain($custom_domain); $site_name = $custom_site_name ?: $this->get_site_name($domain, "GitLab"); return [ "api_url" => "https://{$domain}/api/v4", "base_url" => "https://{$domain}", "site_info" => [ "name" => $site_name, "url" => "https://{$domain}", "favicon" => "https://{$domain}/assets/favicon.ico", "color" => "#fc6d26", ], ]; case "custom": if (empty($custom_domain)) { return null; } $domain = $this->normalize_domain($custom_domain); $site_name = $custom_site_name ?: $this->get_site_name($domain, __("Git Service", self::TEXT_DOMAIN)); return [ "api_url" => "https://{$domain}/api/v1", "base_url" => "https://{$domain}", "site_info" => [ "name" => $site_name, "url" => "https://{$domain}", "favicon" => "https://{$domain}/favicon.ico", "color" => "#6366f1", ], ]; default: return null; } } private function get_site_name(string $domain, string $fallback): string { $cache_key = "git_embed_site_name_" . md5($domain); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } $response = wp_remote_get("https://{$domain}", [ "timeout" => 10, "headers" => [ "User-Agent" => "Git-Embed-FeiCode/" . self::PLUGIN_VERSION, ], ]); if ( !is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200 ) { $body = wp_remote_retrieve_body($response); if (preg_match("/