forked from modiqi/git-it-write
替换 Forgejo API 数据源为本地文件系统读取,支持 30+ 仓库自动发现。 新增 WP-Cron 定时同步、WP-CLI 命令、多站点支持、SEO 字段写入。 base_directory 为空时保留原 API 模式向后兼容。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
No EOL
4.5 KiB
PHP
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();
|
|
|
|
?>
|