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>
763 lines
29 KiB
PHP
763 lines
29 KiB
PHP
<?php
|
|
|
|
if( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
class GIW_Admin{
|
|
|
|
private static $pagehook = 'settings_page_git-it-write';
|
|
|
|
public static function init(){
|
|
|
|
add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
|
|
|
|
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
|
|
|
|
}
|
|
|
|
public static function admin_menu(){
|
|
|
|
add_options_page( 'Git it Write', 'Git it Write', 'manage_options', 'git-it-write', array( __CLASS__, 'admin_page' ) );
|
|
|
|
}
|
|
|
|
public static function enqueue_scripts( $hook ){
|
|
|
|
if( $hook == self::$pagehook ){
|
|
|
|
wp_enqueue_style( 'giw-admin-css', GIW_ADMIN_URL . '/css/style.css', array(), GIW_VERSION );
|
|
|
|
wp_enqueue_script( 'jquery' );
|
|
wp_enqueue_script( 'sc-admin-js', GIW_ADMIN_URL . '/js/script.js', array( 'jquery' ), GIW_VERSION );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public static function admin_page(){
|
|
|
|
echo '<div class="wrap">';
|
|
echo '<div class="head_wrap">';
|
|
echo '<h1 class="giw_title">Git it Write <span class="title-count">' . GIW_VERSION . '</span></h1>';
|
|
echo '</div>';
|
|
|
|
echo '<div id="main">';
|
|
|
|
echo '<div id="content">';
|
|
|
|
$g = self::clean_get();
|
|
$action = isset( $g[ 'action' ] ) ? $g[ 'action' ] : 'manage';
|
|
|
|
if( $action != 'manage' ){
|
|
echo '<p class="toolbar">';
|
|
echo '<a href="' . esc_url( self::link() ) . '" class="button"><span class="dashicons dashicons-arrow-left-alt"></span>Back</a>';
|
|
self::toolbar_extra();
|
|
echo '</p>';
|
|
}
|
|
|
|
if( $action == 'manage' || empty( $action ) ){
|
|
if( Git_It_Write::is_local_mode() ){
|
|
self::manage_local();
|
|
}else{
|
|
self::manage_repo();
|
|
}
|
|
}
|
|
|
|
if( $action == 'edit' ){
|
|
self::edit_repo();
|
|
}
|
|
|
|
if( $action == 'new' ){
|
|
self::new_repo();
|
|
}
|
|
|
|
if( $action == 'delete' ){
|
|
self::delete_repo();
|
|
}
|
|
|
|
if( $action == 'pull' ){
|
|
self::pull_posts();
|
|
}
|
|
|
|
if( $action == 'sync' ){
|
|
self::sync_local();
|
|
}
|
|
|
|
if( $action == 'logs' ){
|
|
self::logs();
|
|
}
|
|
|
|
echo '</div>'; // #content
|
|
|
|
echo '<div id="sidebar">';
|
|
self::sidebar();
|
|
echo '</div>';
|
|
|
|
echo '</div>'; // #main
|
|
|
|
echo '</div>'; // .wrap
|
|
|
|
}
|
|
|
|
// ==================== LOCAL MODE UI ====================
|
|
|
|
public static function manage_local(){
|
|
|
|
$settings = Git_It_Write::general_settings();
|
|
$base_dir = $settings['base_directory'];
|
|
$repos = class_exists( 'GIW_Discovery' ) ? GIW_Discovery::discover( $base_dir ) : array();
|
|
$timestamps = get_option( 'giw_sync_timestamps', array() );
|
|
|
|
echo '<p class="toolbar">';
|
|
echo '<a href="' . esc_url( self::link( 'sync', false, array( 'target' => 'all', '_wpnonce' => wp_create_nonce( 'giw_sync_nonce' ) ) ) ) . '" class="button button-primary"><span class="dashicons dashicons-update"></span> Sync All Repositories</a>';
|
|
echo '<a href="' . esc_url( self::link( 'logs' ) ) . '" class="button"><span class="dashicons dashicons-text"></span> Logs</a>';
|
|
self::toolbar_extra();
|
|
echo '</p>';
|
|
|
|
echo '<h2>Discovered Repositories</h2>';
|
|
echo '<p class="description">Scanning: <code>' . esc_html( $base_dir ) . '</code> — ' . count( $repos ) . ' repositories found</p>';
|
|
|
|
if( empty( $repos ) ){
|
|
echo '<p class="description">No repositories found. Clone git repositories into the base directory above.</p>';
|
|
}else{
|
|
|
|
echo '<table class="wp-list-table widefat fixed striped">';
|
|
echo '<thead>
|
|
<tr>
|
|
<th>Repository</th>
|
|
<th>Post Type</th>
|
|
<th>Folder</th>
|
|
<th>Category</th>
|
|
<th>Files</th>
|
|
<th>Last Synced</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>';
|
|
|
|
foreach( $repos as $repo ){
|
|
|
|
$name = $repo['name'];
|
|
$config = $repo['config'];
|
|
$last_ts = isset( $timestamps[ $name ] ) ? $timestamps[ $name ] : 0;
|
|
|
|
// Count markdown files.
|
|
$file_count = self::count_files( $repo['path'], $config['folder'] );
|
|
|
|
echo '<tr>';
|
|
echo '<td><strong>' . esc_html( $name ) . '</strong>';
|
|
if( is_readable( $repo['path'] . '/.giw.yml' ) ){
|
|
echo ' <span class="dashicons dashicons-yes-alt" title="Has .giw.yml config" style="color:#46b450;"></span>';
|
|
}
|
|
echo '</td>';
|
|
echo '<td>' . esc_html( $config['post_type'] ) . '</td>';
|
|
echo '<td>' . ( empty( $config['folder'] ) ? 'Root' : esc_html( $config['folder'] ) ) . '</td>';
|
|
echo '<td>' . ( empty( $config['category'] ) ? '-' : esc_html( $config['category'] ) ) . '</td>';
|
|
echo '<td>' . intval( $file_count ) . '</td>';
|
|
echo '<td>' . ( $last_ts == 0 ? 'Never' : esc_html( human_time_diff( $last_ts ) ) . ' ago' ) . '</td>';
|
|
echo '<td>';
|
|
echo '<a class="button button-small" href="' . esc_url( self::link( 'sync', false, array( 'target' => $name, '_wpnonce' => wp_create_nonce( 'giw_sync_nonce' ) ) ) ) . '">Sync</a> ';
|
|
echo '<a class="button button-small" href="' . esc_url( self::link( 'sync', false, array( 'target' => $name, 'force' => '1', '_wpnonce' => wp_create_nonce( 'giw_sync_nonce' ) ) ) ) . '">Force Sync</a>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
}
|
|
|
|
echo '</tbody>';
|
|
echo '</table>';
|
|
|
|
}
|
|
|
|
self::general_settings();
|
|
|
|
}
|
|
|
|
/**
|
|
* Handle sync action from admin UI.
|
|
*/
|
|
public static function sync_local(){
|
|
|
|
$g = self::clean_get();
|
|
|
|
if( !isset( $g['target'] ) || !check_admin_referer( 'giw_sync_nonce' ) ){
|
|
self::print_notice( 'Invalid sync request.', 'error' );
|
|
return;
|
|
}
|
|
|
|
$target = $g['target'];
|
|
$force = isset( $g['force'] ) && $g['force'] == '1';
|
|
|
|
define( 'GIW_ON_GUI', true );
|
|
if( $force && !defined( 'GIW_PUBLISH_FORCE' ) ){
|
|
define( 'GIW_PUBLISH_FORCE', true );
|
|
}
|
|
|
|
$settings = Git_It_Write::general_settings();
|
|
|
|
// Switch blog for multisite.
|
|
if( is_multisite() && !empty( $settings['target_blog_id'] ) ){
|
|
switch_to_blog( (int) $settings['target_blog_id'] );
|
|
}
|
|
|
|
echo '<h2>Syncing: ' . esc_html( $target ) . ( $force ? ' (Force)' : '' ) . '</h2>';
|
|
echo '<div class="log_wrap">';
|
|
|
|
if( $target === 'all' ){
|
|
$results = GIW_Sync::sync_all( $force );
|
|
echo '<p><strong>Synced ' . count( $results ) . ' repositories.</strong></p>';
|
|
}else{
|
|
$result = GIW_Publish_Handler::publish_by_dir_name( $target, $force );
|
|
if( $result ){
|
|
echo '<p><strong>Result:</strong> ' . esc_html( $result['message'] ) . '</p>';
|
|
}else{
|
|
echo '<p><strong>Repository not found: ' . esc_html( $target ) . '</strong></p>';
|
|
}
|
|
}
|
|
|
|
echo '</div>';
|
|
|
|
if( is_multisite() && !empty( $settings['target_blog_id'] ) ){
|
|
restore_current_blog();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Count markdown files in a repo path (optionally within a subfolder).
|
|
*/
|
|
private static function count_files( $repo_path, $folder = '' ){
|
|
$target = rtrim( $repo_path, '/' );
|
|
if( !empty( $folder ) ){
|
|
$target .= '/' . $folder;
|
|
}
|
|
if( !is_dir( $target ) ){
|
|
return 0;
|
|
}
|
|
$count = 0;
|
|
$iterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator( $target, RecursiveDirectoryIterator::SKIP_DOTS )
|
|
);
|
|
foreach( $iterator as $file ){
|
|
if( $file->isFile() && strtolower( $file->getExtension() ) === 'md' ){
|
|
$count++;
|
|
}
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
// ==================== LEGACY API MODE UI ====================
|
|
|
|
public static function manage_repo(){
|
|
|
|
$all_repos = Git_It_Write::all_repositories();
|
|
|
|
echo '<p class="toolbar">';
|
|
echo '<a href="' . esc_url( self::link( 'new' ) ) . '" class="button button-primary"><span class="dashicons dashicons-plus"></span> Add a new repository to publish posts from</a>';
|
|
echo '<a href="' . esc_url( self::link( 'logs' ) ) . '" class="button"><span class="dashicons dashicons-text"></span> Logs</a>';
|
|
self::toolbar_extra();
|
|
echo '</p>';
|
|
|
|
echo '<h2>Configured repositories</h2>';
|
|
|
|
if( empty( $all_repos ) || count( $all_repos ) == 1 ){
|
|
echo '<p class="description">No repositories configured. Go ahead and add one ! See <a href="https://www.aakashweb.com/docs/git-it-write/" target="_blank">getting started</a> for more information.</p>';
|
|
}else{
|
|
|
|
echo '<table class="wp-list-table widefat fixed striped">';
|
|
echo '<thead>
|
|
<tr>
|
|
<th width="40px">ID</th>
|
|
<th>Repository</th>
|
|
<th>Branch</th>
|
|
<th>Folder to publish</th>
|
|
<th>Post type to publish under</th>
|
|
<th>Last published</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>';
|
|
|
|
foreach( $all_repos as $id => $config ){
|
|
|
|
if( $id == 0 ){
|
|
continue;
|
|
}
|
|
|
|
echo '<tr>';
|
|
|
|
echo '<th>' . esc_html( $id ) . '</th>';
|
|
|
|
echo '<td class="title column-title has-row-actions column-primary page-title">';
|
|
echo '<a href="' . esc_url( self::link( 'edit', $id ) ) . '" class="row-title">' . esc_html( $config[ 'username' ] . '/' . $config[ 'repository' ] ) . '</a>';
|
|
echo '<div class="row-actions">';
|
|
echo '<span><a href="' . esc_url( self::link( 'edit', $id ) ) . '">Edit</a> | </span>';
|
|
echo '<span><a href="' . esc_url( self::link( 'pull', $id ) ) . '">Pull posts</a> | </span>';
|
|
echo '<span class="trash"><a href="' . esc_url( self::link( 'delete', $id, array( '_wpnonce' => wp_create_nonce( 'giw_delete_nonce' ) ) ) ) . '">Delete</a></span>';
|
|
echo '</div>';
|
|
echo '</td>';
|
|
|
|
echo '<td>' . ( empty( $config[ 'branch' ] ) ? 'master' : esc_html( $config[ 'branch' ] ) ) . '</td>';
|
|
echo '<td>' . ( empty( $config[ 'folder' ] ) ? 'Root' : esc_html( $config[ 'folder' ] ) ) . '</td>';
|
|
echo '<td>' . esc_html( $config[ 'post_type' ] ) . '</td>';
|
|
echo '<td>' . ( $config[ 'last_publish' ] == 0 ? '-' : esc_html( human_time_diff( $config[ 'last_publish' ] ) ) . ' ago' ) . '</td>';
|
|
|
|
echo '</tr>';
|
|
}
|
|
|
|
echo '</tbody>';
|
|
echo '</table>';
|
|
|
|
} // End if
|
|
|
|
self::general_settings();
|
|
|
|
}
|
|
|
|
public static function new_repo(){
|
|
self::edit_repo( 'new' );
|
|
}
|
|
|
|
public static function edit_repo( $action = 'edit' ){
|
|
|
|
self::save_repo_settings();
|
|
|
|
$all_repos = Git_It_Write::all_repositories();
|
|
$g = self::clean_get();
|
|
$id = 0;
|
|
|
|
$page_title = ( $action == 'edit' ) ? 'Edit repository settings' : 'Add new repository to publish posts from';
|
|
$save_button = ( $action == 'edit' ) ? 'Save settings' : 'Add repository' ;
|
|
|
|
$values = Git_It_Write::default_config();
|
|
|
|
if( $action == 'edit' ){
|
|
|
|
if( !isset( $g[ 'id' ] ) || empty( $g[ 'id' ] ) ){
|
|
self::print_notice( 'Invalid ID provided to edit settings', 'error' );
|
|
return;
|
|
}
|
|
|
|
$id = $g[ 'id' ];
|
|
|
|
if( !isset( $all_repos[ $id ] ) ){
|
|
self::print_notice( 'Unable to find repository settings', 'error' );
|
|
return;
|
|
}
|
|
|
|
$values = $all_repos[ $id ];
|
|
|
|
}
|
|
|
|
echo '<h2>' . esc_html( $page_title ) . '</h2>';
|
|
|
|
echo '<form method="post">';
|
|
|
|
echo '<table class="form-table widefat">';
|
|
echo '<tbody>';
|
|
|
|
echo '<tr>';
|
|
echo '<td style="width: 300px">Forgejo owner/organization</td>';
|
|
echo '<td><input type="text" name="giw_username" value="' . esc_attr( $values[ 'username' ] ) . '" required="required" />';
|
|
echo '<p class="description">The owner or organization name on the Forgejo instance</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Repository name</td>';
|
|
echo '<td><input type="text" name="giw_repository" value="' . esc_attr( $values[ 'repository' ] ) . '" required="required" />';
|
|
echo '<p class="description">The name of the Forgejo repository to pull and publish posts from</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Branch to publish from</td>';
|
|
echo '<td><input type="text"name="giw_branch" value="' . esc_attr( $values[ 'branch' ] ) . '" />';
|
|
echo '<p class="description">The name of the repository branch to pull and publish posts from. Leave blank to default to "master". Example: main</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Folder to publish from</td>';
|
|
echo '<td><input type="text"name="giw_folder" value="' . esc_attr( $values[ 'folder' ] ) . '" />';
|
|
echo '<p class="description">The folder in the repository from which posts have to be published. Leave blank to publish from the root of the repository. Example: website/main/docs</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Post type to publish to</td>';
|
|
echo '<td>' . GIW_utils::post_type_selector( 'giw_post_type', $values[ 'post_type' ] );
|
|
echo '<p class="description">The post type to publish the posts under. Hierarchial post types are preferred as they support level by level pages.</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Author to set for the posts</td>';
|
|
echo '<td>' . wp_dropdown_users( array('name' => 'giw_post_author', 'selected' => $values[ 'post_author' ], 'echo' => false ) );
|
|
echo '<p class="description">The user to be set as post author for all the posts pulled from this repository</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Post content template</td>';
|
|
echo '<td>';
|
|
wp_editor( $values[ 'content_template' ], 'giw_content_template', array(
|
|
'media_buttons' => false,
|
|
'teeny' => true,
|
|
'textarea_rows' => 4
|
|
));
|
|
echo '<p class="description">The template of the post content. Use any text, HTML, shortcode you would like to be added to all the posts when they are published. Supported placeholder <code>%%content%%</code> (The HTML of the pulled post). Use shortcode <code>[giw_edit_link]</code> to insert a link of the source file on Forgejo to edit and collaborate. You might need to "Pull all the files" to update the post if the template is changed.</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '</tbody>';
|
|
echo '</table>';
|
|
|
|
echo '<input type="hidden" name="giw_id" value="' . esc_attr( $id ) . '" />';
|
|
|
|
wp_nonce_field( 'giw_edit_nonce' );
|
|
|
|
echo '<p><button type="submit" class="button button-primary">' . esc_html( $save_button ) . '</button></p>';
|
|
|
|
echo '</form>';
|
|
|
|
}
|
|
|
|
public static function delete_repo(){
|
|
|
|
$g = self::clean_get();
|
|
|
|
if( isset( $g[ 'id' ] ) && check_admin_referer( 'giw_delete_nonce' ) ){
|
|
|
|
$all_repos = $all_repos = Git_It_Write::all_repositories();
|
|
$id = $g[ 'id' ];
|
|
if( isset( $all_repos[ $id ] ) ){
|
|
unset( $all_repos[ $id ] );
|
|
}else{
|
|
self::print_notice( 'Invalid repository to delete !', 'error' );
|
|
return;
|
|
}
|
|
|
|
if( update_option( 'giw_repositories', $all_repos ) ){
|
|
self::print_notice( 'Deleted repository configuration !' );
|
|
}else{
|
|
self::print_notice( 'Failed to delete repository configuration !', 'error' );
|
|
}
|
|
|
|
}else{
|
|
self::print_notice( 'No repository ID provided to delete.', 'error' );
|
|
}
|
|
|
|
}
|
|
|
|
public static function pull_posts(){
|
|
|
|
$g = self::clean_get();
|
|
|
|
if( !isset( $g[ 'id' ] ) ){
|
|
self::print_notice( 'No repository ID provided to pull posts from', 'error' );
|
|
return;
|
|
}
|
|
|
|
$id = $g[ 'id' ];
|
|
|
|
echo '<h2>Pull posts from Forgejo for [' . esc_html( $id ) . ']</h2>';
|
|
|
|
echo '<table class="widefat striped">';
|
|
echo '<tbody>
|
|
<tr>
|
|
<th>To pull only the latest changes made to the repository and publish posts, select this option</td>
|
|
<td><a class="button" href="' . esc_url( self::link( 'pull', $id, array( 'pull' => 'changes', '_wpnonce' => wp_create_nonce( 'giw_pull_nonce' ) ) ) ) . '">Pull only changes</a></td>
|
|
</tr>
|
|
<tr>
|
|
<th>To pull all the items even though unchanged and to overwrite all the published posts related to this repository, select this option</td>
|
|
<td><a class="button" href="' . esc_url( self::link( 'pull', $id, array( 'pull' => 'force', '_wpnonce' => wp_create_nonce( 'giw_pull_nonce' ) ) ) ) . '">Pull all the files</a></td>
|
|
</tr>
|
|
</tbody>';
|
|
echo '</table>';
|
|
|
|
if( !isset( $g[ 'pull' ] ) || !check_admin_referer( 'giw_pull_nonce' ) ){
|
|
return;
|
|
}
|
|
|
|
$all_repos = Git_It_Write::all_repositories();
|
|
if( !isset( $all_repos[ $id ] ) ){
|
|
self::print_notice( 'Invalid repository ID', 'error' );
|
|
return;
|
|
}
|
|
|
|
$pull_type = $g[ 'pull' ];
|
|
if( !in_array( $pull_type, array( 'force', 'changes' ) ) ){
|
|
self::print_notice( 'Invalid pull type', 'error' );
|
|
return;
|
|
}
|
|
|
|
echo '<h2>Pulling posts [' . esc_html( $g[ 'pull' ] ) . ']</h2>';
|
|
|
|
define( 'GIW_ON_GUI', true );
|
|
if( $g[ 'pull' ] == 'force' ){
|
|
define( 'GIW_PUBLISH_FORCE', true );
|
|
}
|
|
|
|
echo '<div class="log_wrap">';
|
|
GIW_Publish_Handler::publish_by_id( $id );
|
|
echo '</div>';
|
|
|
|
}
|
|
|
|
public static function logs(){
|
|
|
|
echo '<div class="log_wrap">';
|
|
|
|
$lines = GIW_Utils::read_log();
|
|
foreach( $lines as $line ){
|
|
echo '<p>' . esc_html( $line ) . '</p>';
|
|
}
|
|
|
|
echo '</div>';
|
|
|
|
}
|
|
|
|
public static function general_settings(){
|
|
|
|
self::save_general_settings();
|
|
|
|
$values = Git_It_Write::general_settings();
|
|
$is_local = Git_It_Write::is_local_mode();
|
|
|
|
echo '<h2>General settings</h2>';
|
|
|
|
echo '<form method="post">';
|
|
|
|
echo '<table class="form-table widefat">';
|
|
echo '<tbody>';
|
|
|
|
// ---- Local Mode Settings ----
|
|
echo '<tr><td colspan="2"><h4>Local File Mode</h4></td></tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td style="width: 200px">Base directory</td>';
|
|
echo '<td><input type="text" name="giw_base_directory" value="' . esc_attr( $values['base_directory'] ) . '" placeholder="/www/wwwroot/site/wp-content/git-repos/" style="width:400px;" />';
|
|
echo '<p class="description">Absolute path to the directory containing cloned git repositories. Set this to enable local file mode (disables API mode). Leave empty for legacy API mode.</p></td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Default post type</td>';
|
|
echo '<td>' . GIW_Utils::post_type_selector( 'giw_default_post_type', $values['default_post_type'] );
|
|
echo '<p class="description">Default post type for repos without <code>.giw.yml</code></p></td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Default post author</td>';
|
|
echo '<td>' . wp_dropdown_users( array( 'name' => 'giw_default_post_author', 'selected' => $values['default_post_author'], 'echo' => false ) );
|
|
echo '<p class="description">Default author for repos without <code>.giw.yml</code></p></td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Sync interval</td>';
|
|
echo '<td>';
|
|
$intervals = array(
|
|
'every_30_minutes' => 'Every 30 minutes',
|
|
'hourly' => 'Hourly',
|
|
'every_6_hours' => 'Every 6 hours',
|
|
'every_12_hours' => 'Every 12 hours',
|
|
'daily' => 'Daily',
|
|
);
|
|
echo '<select name="giw_sync_interval">';
|
|
foreach( $intervals as $key => $label ){
|
|
echo '<option value="' . esc_attr( $key ) . '"' . selected( $values['sync_interval'], $key, false ) . '>' . esc_html( $label ) . '</option>';
|
|
}
|
|
echo '</select>';
|
|
echo '<p class="description">How often WP-Cron should run the automatic sync</p></td>';
|
|
echo '</tr>';
|
|
|
|
if( is_multisite() ){
|
|
echo '<tr>';
|
|
echo '<td>Target blog ID</td>';
|
|
echo '<td><input type="number" name="giw_target_blog_id" value="' . esc_attr( $values['target_blog_id'] ) . '" min="0" />';
|
|
echo '<p class="description">Blog ID to publish posts to in a multisite install. 0 = current site.</p></td>';
|
|
echo '</tr>';
|
|
}
|
|
|
|
// ---- Legacy API Settings (hide in local mode) ----
|
|
if( !$is_local ){
|
|
|
|
echo '<tr><td colspan="2"><h4>Webhook & Forgejo Connection</h4></td></tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Webhook secret</td>';
|
|
echo '<td><input type="password" class="webhook_secret" name="giw_webhook_secret" value="' . esc_attr( $values[ 'webhook_secret' ] ) . '" autocomplete="new-password" /> <button class="button">Toggle view</button>';
|
|
echo '<p class="description">Go to Forgejo repository settings → Webhooks and add a webhook for the payload URL <code>' . esc_html( rest_url( '/giw/v1/publish' ) ) . '</code> if you would like to automatically publish the changes whenever repository is updated.</p>';
|
|
echo '<p class="description">Select content-type as <code>application/json</code> and enter a secret text. Provide the same secret text in the above field. Select "Just the push event" for the webhook trigger. Make sure all the repositories you would like to automatically update have the same payload URL and the secret.</p>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Forgejo URL</td>';
|
|
echo '<td><input type="text" name="giw_forgejo_url" value="' . esc_attr( $values[ 'forgejo_url' ] ) . '" placeholder="https://feicode.com" />';
|
|
echo '<p class="description">The URL of your Forgejo instance (e.g. <code>https://feicode.com</code>)</p></td>';
|
|
echo '</tr>';
|
|
|
|
echo '<tr>';
|
|
echo '<td>Forgejo Access Token</td>';
|
|
echo '<td><input type="password" name="giw_forgejo_access_token" value="' . esc_attr( $values[ 'forgejo_access_token' ] ) . '" /> <button class="button">Toggle view</button>';
|
|
echo '<p class="description">Create an access token in your Forgejo instance under Settings → Applications → Generate Token. Select <code>read:repository</code> scope.</p></td>';
|
|
echo '</tr>';
|
|
|
|
}else{
|
|
|
|
// In local mode, still show webhook secret for push notifications.
|
|
echo '<tr><td colspan="2"><h4>Webhook (Optional)</h4></td></tr>';
|
|
echo '<tr>';
|
|
echo '<td>Webhook secret</td>';
|
|
echo '<td><input type="password" class="webhook_secret" name="giw_webhook_secret" value="' . esc_attr( $values['webhook_secret'] ) . '" autocomplete="new-password" /> <button class="button">Toggle view</button>';
|
|
echo '<p class="description">Payload URL: <code>' . esc_html( rest_url( '/giw/v1/publish' ) ) . '</code></p></td>';
|
|
echo '</tr>';
|
|
|
|
// Preserve hidden values.
|
|
echo '<input type="hidden" name="giw_forgejo_url" value="' . esc_attr( $values['forgejo_url'] ) . '" />';
|
|
echo '<input type="hidden" name="giw_forgejo_access_token" value="' . esc_attr( $values['forgejo_access_token'] ) . '" />';
|
|
|
|
}
|
|
|
|
echo '</tbody>';
|
|
echo '</table>';
|
|
|
|
wp_nonce_field( 'giw_gs_nonce' );
|
|
|
|
echo '<p><button type="submit" class="button button-primary">Save settings</button></p>';
|
|
|
|
echo '</form>';
|
|
|
|
}
|
|
|
|
public static function save_repo_settings(){
|
|
|
|
if( $_POST && check_admin_referer( 'giw_edit_nonce' ) ){
|
|
|
|
$all_repos = Git_It_Write::all_repositories();
|
|
$defaults = Git_It_Write::default_config();
|
|
$p = wp_parse_args( self::clean_post(), $defaults );
|
|
$values = array();
|
|
$is_new = false;
|
|
|
|
foreach( $defaults as $field => $default ){
|
|
$form_field = 'giw_' . $field;
|
|
$values[ $field ] = isset( $p[ $form_field ] ) ? wp_kses_post( $p[ $form_field ] ) : $default;
|
|
}
|
|
|
|
if( !isset( $p[ 'giw_id' ] ) || empty( $p[ 'giw_id' ] ) || !$p[ 'giw_id' ] ){ // If no ID, then new item
|
|
$is_new = true;
|
|
if( empty( $all_repos ) ){
|
|
$all_repos[1] = $values;
|
|
}else{
|
|
array_push( $all_repos, $values );
|
|
}
|
|
}else{
|
|
$id = $p[ 'giw_id' ];
|
|
$all_repos[ $id ] = $values;
|
|
}
|
|
|
|
if( update_option( 'giw_repositories', $all_repos ) ){
|
|
if( $is_new ){
|
|
self::print_notice( 'Successfully added new repository settings !' );
|
|
}else{
|
|
self::print_notice( 'Successfully saved the changes !' );
|
|
}
|
|
}else{
|
|
self::print_notice( 'Failed to save the settings !', 'error' );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public static function save_general_settings(){
|
|
|
|
if( $_POST && check_admin_referer( 'giw_gs_nonce' ) ){
|
|
|
|
$defaults = Git_It_Write::default_general_settings();
|
|
$p = wp_parse_args( self::clean_post(), $defaults );
|
|
|
|
$values = array();
|
|
|
|
foreach( $defaults as $field => $default ){
|
|
$form_field = 'giw_' . $field;
|
|
$values[ $field ] = isset( $p[ $form_field ] ) ? sanitize_text_field( $p[ $form_field ] ) : $default;
|
|
}
|
|
|
|
$old_settings = Git_It_Write::general_settings();
|
|
|
|
if( update_option( 'giw_general_settings', $values ) ){
|
|
self::print_notice( 'Successfully saved the changes !' );
|
|
|
|
// Reschedule cron if interval changed.
|
|
if( class_exists( 'GIW_Sync' ) && $old_settings['sync_interval'] !== $values['sync_interval'] ){
|
|
GIW_Sync::reschedule( $values['sync_interval'] );
|
|
}
|
|
}else{
|
|
self::print_notice( 'Failed to save the settings !', 'error' );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public static function sidebar(){
|
|
|
|
echo '<div class="side_card">';
|
|
echo '<h2><span class="dashicons dashicons-info"></span> Git it Write - Forgejo</h2>';
|
|
echo '<p>Publish markdown files from a Forgejo repository as WordPress posts. Supports automatic sync via webhooks.</p>';
|
|
|
|
if( Git_It_Write::is_local_mode() ){
|
|
echo '<hr/>';
|
|
echo '<p><strong>Mode:</strong> Local File System</p>';
|
|
$next = wp_next_scheduled( 'giw_cron_sync' );
|
|
if( $next ){
|
|
echo '<p><strong>Next sync:</strong> ' . esc_html( human_time_diff( $next ) ) . '</p>';
|
|
}
|
|
}
|
|
|
|
echo '</div>';
|
|
|
|
}
|
|
|
|
public static function toolbar_extra(){
|
|
}
|
|
|
|
public static function link( $action = false, $id = false, $more = array() ){
|
|
|
|
$params[ 'page' ] = 'git-it-write';
|
|
if( $action ) $params[ 'action' ] = $action;
|
|
if( $id ) $params[ 'id' ] = $id;
|
|
|
|
$params = array_merge( $params, $more );
|
|
|
|
return add_query_arg( $params, admin_url( 'options-general.php' ) );
|
|
|
|
}
|
|
|
|
public static function print_notice( $msg = '', $type = 'success' ){
|
|
|
|
if( $msg != '' ){
|
|
echo '<div class="notice notice-' . esc_attr( $type ) . ' is-dismissible"><p>' . esc_html( $msg ) . '</p></div>';
|
|
}
|
|
|
|
}
|
|
|
|
public static function clean_get(){
|
|
|
|
foreach( $_GET as $k=>$v ){
|
|
$_GET[$k] = sanitize_text_field( $v );
|
|
}
|
|
|
|
return $_GET;
|
|
}
|
|
|
|
public static function clean_post(){
|
|
|
|
return stripslashes_deep( $_POST );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
GIW_Admin::init();
|
|
|
|
?>
|