git-it-write/includes/sync.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

204 lines
6 KiB
PHP

<?php
if( ! defined( 'ABSPATH' ) ) exit;
class GIW_Sync{
const CRON_HOOK = 'giw_cron_sync';
public static function init(){
add_filter( 'cron_schedules', array( __CLASS__, 'add_interval' ) );
add_action( self::CRON_HOOK, array( __CLASS__, 'sync_all' ) );
// Schedule if not already scheduled.
if( !wp_next_scheduled( self::CRON_HOOK ) ){
$settings = Git_It_Write::general_settings();
$interval = !empty( $settings['sync_interval'] ) ? $settings['sync_interval'] : 'hourly';
wp_schedule_event( time(), $interval, self::CRON_HOOK );
}
}
public static function add_interval( $schedules ){
$schedules['every_30_minutes'] = array(
'interval' => 1800,
'display' => 'Every 30 minutes',
);
$schedules['every_6_hours'] = array(
'interval' => 21600,
'display' => 'Every 6 hours',
);
$schedules['every_12_hours'] = array(
'interval' => 43200,
'display' => 'Every 12 hours',
);
return $schedules;
}
/**
* Sync all discovered repositories.
*/
public static function sync_all( $force = false ){
$settings = Git_It_Write::general_settings();
$base_dir = isset( $settings['base_directory'] ) ? $settings['base_directory'] : '';
if( empty( $base_dir ) ){
GIW_Utils::log( 'Sync: base_directory not configured, skipping' );
return array();
}
self::maybe_switch_blog( $settings );
// Load media functions for image uploads.
if( !function_exists( 'media_handle_sideload' ) ){
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
}
$repos = GIW_Discovery::discover( $base_dir );
$results = array();
GIW_Utils::log( '========== Starting sync of ' . count( $repos ) . ' repositories ==========' );
foreach( $repos as $repo ){
$results[ $repo['name'] ] = self::sync_repo( $repo, $force );
}
GIW_Utils::log( '========== Sync complete ==========' );
self::maybe_restore_blog();
return $results;
}
/**
* Sync a single repository.
*
* @param array $repo Repo array from GIW_Discovery (name, path, config).
* @param bool $force Force full sync ignoring SHA.
* @return array Publisher result.
*/
public static function sync_repo( $repo, $force = false ){
GIW_Utils::log( '---------- Syncing repo: ' . $repo['name'] . ' ----------' );
if( $force ){
if( !defined( 'GIW_PUBLISH_FORCE' ) ){
define( 'GIW_PUBLISH_FORCE', true );
}
}
// Load media functions for image uploads.
if( !function_exists( 'media_handle_sideload' ) ){
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
}
$local_repo = new GIW_Local_Repository( $repo['path'], $repo['name'] );
$config = $repo['config'];
// Build a repo_config array compatible with GIW_Publisher.
$repo_config = array(
'post_type' => !empty( $config['post_type'] ) ? $config['post_type'] : 'post',
'branch' => !empty( $config['branch'] ) ? $config['branch'] : 'master',
'folder' => isset( $config['folder'] ) ? $config['folder'] : '',
'post_author' => !empty( $config['post_author'] ) ? (int) $config['post_author'] : 1,
'content_template' => !empty( $config['content_template'] ) ? $config['content_template'] : '%%content%%',
);
$publisher = new GIW_Publisher( $local_repo, $repo_config );
// Set default category if specified.
if( !empty( $config['category'] ) ){
add_action( 'wp_insert_post', function( $post_id, $post ) use ( $config ){
if( $post->post_type === $config['post_type'] && empty( wp_get_object_terms( $post_id, 'category' ) ) ){
wp_set_object_terms( $post_id, $config['category'], 'category' );
}
}, 10, 2 );
}
$result = $publisher->publish();
// Update sync timestamp.
$timestamps = get_option( 'giw_sync_timestamps', array() );
$timestamps[ $repo['name'] ] = time();
update_option( 'giw_sync_timestamps', $timestamps );
GIW_Utils::log( '---------- Repo sync done: ' . $repo['name'] . ' ----------' );
return $result;
}
/**
* Switch to target blog in multisite.
*/
private static function maybe_switch_blog( $settings = array() ){
if( !is_multisite() ){
return;
}
if( empty( $settings ) ){
$settings = Git_It_Write::general_settings();
}
$blog_id = isset( $settings['target_blog_id'] ) ? (int) $settings['target_blog_id'] : 0;
if( $blog_id > 0 ){
GIW_Utils::log( 'Switching to blog ID: ' . $blog_id );
switch_to_blog( $blog_id );
}
}
private static function maybe_restore_blog(){
if( !is_multisite() ){
return;
}
restore_current_blog();
}
/**
* Reschedule cron when interval changes.
*/
public static function reschedule( $interval ){
$timestamp = wp_next_scheduled( self::CRON_HOOK );
if( $timestamp ){
wp_unschedule_event( $timestamp, self::CRON_HOOK );
}
wp_schedule_event( time(), $interval, self::CRON_HOOK );
}
/**
* Unschedule cron (plugin deactivation).
*/
public static function unschedule(){
$timestamp = wp_next_scheduled( self::CRON_HOOK );
if( $timestamp ){
wp_unschedule_event( $timestamp, self::CRON_HOOK );
}
}
}
?>