git-it-write/includes/webhook.php
Developer 7728f489bd feat: 本地文件模式 + 多仓库自动发现 (v3.0)
替换 Forgejo API 数据源为本地文件系统读取,支持 30+ 仓库自动发现。
新增 WP-Cron 定时同步、WP-CLI 命令、多站点支持、SEO 字段写入。
base_directory 为空时保留原 API 模式向后兼容。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 00:37:12 +08:00

124 lines
No EOL
4.5 KiB
PHP

<?php
if( ! defined( 'ABSPATH' ) ) exit;
class GIW_Webhook{
public static function init(){
add_action( 'rest_api_init', function () {
register_rest_route( 'giw/v1', '/publish', array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'handle_webhook'),
'permission_callback' => array( __CLASS__, 'check_permission' )
));
});
}
public static function handle_webhook( WP_REST_Request $req ){
if( $req->get_header( 'X-Gitea-Delivery' ) ){
GIW_Utils::log('Got webhook delivery ' . $req->get_header( 'X-Gitea-Delivery' ) );
}
// User agent check
if( !$req->get_header( 'User-Agent' ) ){
return self::error( 'no_user_agent', 'No user agent', array( 'status' => 401 ) );
}
$ua = $req->get_header( 'User-Agent' );
if( strpos( $ua, 'Gitea' ) === false && strpos( $ua, 'Forgejo' ) === false ){
return self::error( 'who_are_you', 'Who are you ?', array( 'status' => 403 ) );
}
// Forgejo event check
if( !$req->get_header( 'X-Gitea-Event' ) ){
return self::error( 'no_event', 'No event', array( 'status' => 400 ) );
}
$event = $req->get_header( 'X-Gitea-Event' );
if( !in_array( $event, array( 'ping', 'push' ) ) ){
return self::error( 'unsupported_event', 'Unsupported event', array( 'status' => 400 ) );
}
// Check signature (Forgejo uses HMAC-SHA256 via X-Gitea-Signature)
if( !$req->get_header( 'X-Gitea-Signature' ) ){
return self::error( 'no_secret_configured', 'No secret configured', array( 'status' => 401 ) );
}
$got_signature = $req->get_header( 'X-Gitea-Signature' );
$settings = Git_It_Write::general_settings();
$secret = trim( $settings[ 'webhook_secret' ] );
if( empty( $secret ) ){
return self::error( 'no_server_secret', 'No secret configured on server', array( 'status' => 500 ) );
}
if( !hash_equals( hash_hmac( 'sha256', $req->get_body(), $secret ), $got_signature ) ){
return self::error( 'signature_mismatch', 'Signature mismatch', array( 'status' => 400 ) );
}
if( $event == 'ping' ){
return 'pong';
}
$json = $req->get_json_params();
if( !isset( $json[ 'repository' ] ) || !isset( $json[ 'repository' ][ 'full_name' ] ) ){
return self::error( 'invalid_data', 'Invalid data', array( 'status' => 400 ) );
}
// Load media related WP files to upload images when in REST API mode
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
$repo_full_name = $json[ 'repository' ][ 'full_name' ];
// Local mode: extract repo name and sync from filesystem.
if( Git_It_Write::is_local_mode() ){
// full_name is like "org/repo-name", extract the repo part.
$parts = explode( '/', $repo_full_name );
$repo_name = end( $parts );
GIW_Utils::log( 'Webhook local mode: syncing repo [' . $repo_name . ']' );
$result = GIW_Publish_Handler::publish_by_dir_name( $repo_name );
if( !$result ){
// Try with full_name replaced slashes (org-repo pattern).
GIW_Utils::log( 'Webhook: repo not found by name, trying full_name match' );
$result = GIW_Publish_Handler::publish_by_dir_name( str_replace( '/', '-', $repo_full_name ) );
}
GIW_Utils::log( 'Successfully honored webhook event (local mode).' );
return $result ? $result : self::error( 'repo_not_found', 'Repository not found in base_directory', array( 'status' => 404 ) );
}
// Legacy API mode.
$result = GIW_Publish_Handler::publish_by_repo_full_name( $repo_full_name );
GIW_Utils::log( 'Successfully honored webhook event.' );
return $result;
}
public static function error( $code, $message, $data ){
GIW_Utils::log( 'Error - ' . $message );
return new WP_Error( $code, $message, $data );
}
public static function check_permission( $request ){
// No authentication needed right now. The main callback has various checks on the requestor.
// TODO - Move the checks in main callback here
return true;
}
}
GIW_Webhook::init();
?>