mirror of
https://gh.wpcy.net/https://github.com/soulsites/wp-git-installer.git
synced 2026-05-26 01:48:45 +08:00
Fügt eine neue Option hinzu, mit der nach dem Klonen/Aktualisieren eines Plugins alle in der .gitignore des Repositories aufgeführten Verzeichnisse und Dateien (z.B. vendor/, node_modules/) automatisch entfernt werden. - Neue Hilfsfunktion `apply_gitignore_cleanup()`: - Führt `git clean -fdX` aus (entfernt nicht-getrackte ignorierte Dateien) - Parst `.gitignore` und löscht auch getrackte Einträge ohne Wildcards - Pfad-Traversal-Schutz via realpath()-Überprüfung - Checkbox im Installations-Formular und im Projekt-Bearbeitungs-Modal - Option wird in Projektkonfiguration gespeichert und bei Sync berücksichtigt https://claude.ai/code/session_016Q3L4KivSaixAb69WPvdtC
911 lines
42 KiB
PHP
911 lines
42 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: GitHub Plugin Installer
|
|
* Description: Install or update WordPress plugins directly from GitHub repositories with multi-project support and modern Material Design 3 UI
|
|
* Version: 2.1
|
|
* Author: Christian Wedel
|
|
*/
|
|
|
|
// Add menu item under "Plugins"
|
|
add_action('admin_menu', 'github_plugin_installer_menu');
|
|
add_action('admin_enqueue_scripts', 'github_plugin_installer_scripts');
|
|
add_action('wp_ajax_preview_github_repo', 'preview_github_repo');
|
|
add_action('wp_ajax_get_github_versions', 'get_github_versions');
|
|
add_action('wp_ajax_check_plugin_status', 'check_plugin_status');
|
|
add_action('wp_ajax_save_github_project', 'save_github_project');
|
|
add_action('wp_ajax_delete_github_project', 'delete_github_project');
|
|
add_action('wp_ajax_sync_github_project', 'sync_github_project');
|
|
add_action('wp_ajax_update_github_project', 'update_github_project');
|
|
add_action('wp_ajax_get_github_project', 'get_github_project');
|
|
|
|
function github_plugin_installer_menu() {
|
|
add_plugins_page('GitHub Plugin Installer', 'GitHub Installer', 'manage_options', 'github-plugin-installer', 'github_plugin_installer_page');
|
|
}
|
|
|
|
function github_plugin_installer_scripts($hook) {
|
|
if ($hook != 'plugins_page_github-plugin-installer') {
|
|
return;
|
|
}
|
|
wp_enqueue_style('github-plugin-installer-admin', plugin_dir_url(__FILE__) . 'admin.css', array(), '2.0');
|
|
wp_enqueue_script('github-plugin-installer', plugin_dir_url(__FILE__) . 'installer-script.js', array('jquery'), '2.0', true);
|
|
wp_localize_script('github-plugin-installer', 'github_installer', array(
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('github_installer_nonce')
|
|
));
|
|
}
|
|
|
|
// Helper functions for project management
|
|
function get_saved_projects() {
|
|
$projects = get_option('github_installer_projects', array());
|
|
return is_array($projects) ? $projects : array();
|
|
}
|
|
|
|
function save_project($project_data) {
|
|
$projects = get_saved_projects();
|
|
$project_id = sanitize_title($project_data['name']);
|
|
$projects[$project_id] = array(
|
|
'id' => $project_id,
|
|
'name' => sanitize_text_field($project_data['name']),
|
|
'repo_url' => esc_url_raw($project_data['repo_url']),
|
|
'is_private' => (bool) $project_data['is_private'],
|
|
'access_token' => !empty($project_data['access_token']) ? sanitize_text_field($project_data['access_token']) : '',
|
|
'version' => sanitize_text_field($project_data['version']),
|
|
'respect_gitignore' => !empty($project_data['respect_gitignore']),
|
|
'last_synced' => current_time('mysql')
|
|
);
|
|
update_option('github_installer_projects', $projects);
|
|
return $project_id;
|
|
}
|
|
|
|
function delete_project($project_id) {
|
|
$projects = get_saved_projects();
|
|
if (isset($projects[$project_id])) {
|
|
unset($projects[$project_id]);
|
|
update_option('github_installer_projects', $projects);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function github_plugin_installer_page() {
|
|
if (!current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
|
|
if (isset($_POST['install_update_plugin'])) {
|
|
$repo_url = sanitize_text_field($_POST['repo_url']);
|
|
$is_private = isset($_POST['is_private']) ? true : false;
|
|
$access_token = $is_private ? sanitize_text_field($_POST['access_token']) : '';
|
|
$selected_version = sanitize_text_field($_POST['version']);
|
|
$save_as_project = isset($_POST['save_as_project']) ? true : false;
|
|
$project_name = sanitize_text_field($_POST['project_name']);
|
|
$respect_gitignore = isset($_POST['respect_gitignore']) ? true : false;
|
|
|
|
// Install/Update the plugin
|
|
install_update_github_plugin($repo_url, $access_token, $selected_version, $respect_gitignore);
|
|
|
|
// Save as project if checkbox was checked
|
|
if ($save_as_project && !empty($project_name)) {
|
|
$project_data = array(
|
|
'name' => $project_name,
|
|
'repo_url' => $repo_url,
|
|
'is_private' => $is_private,
|
|
'access_token' => $access_token,
|
|
'version' => $selected_version,
|
|
'respect_gitignore' => $respect_gitignore
|
|
);
|
|
save_project($project_data);
|
|
echo '<div class="updated"><p>Projekt erfolgreich gespeichert!</p></div>';
|
|
}
|
|
}
|
|
|
|
$saved_projects = get_saved_projects();
|
|
|
|
?>
|
|
<div class="wrap github-installer-container">
|
|
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
|
|
|
|
<!-- Installation/Update Form -->
|
|
<div class="github-card">
|
|
<h2>Plugin installieren oder aktualisieren</h2>
|
|
<form method="post" action="" id="github-project-form">
|
|
<div class="form-section">
|
|
<label for="repo_url">GitHub Repository URL *</label>
|
|
<input type="text" id="repo_url" name="repo_url" class="regular-text" required placeholder="https://github.com/username/repository.git">
|
|
<p class="description">Die vollständige URL zu Ihrem GitHub Repository</p>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="is_private" name="is_private">
|
|
<span>Privates Repository (benötigt Access Token)</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-section" id="access_token_section" style="display: none;">
|
|
<label for="access_token">GitHub Access Token</label>
|
|
<input type="password" id="access_token" name="access_token" class="regular-text">
|
|
<p class="description">
|
|
Token erstellen unter: <a href="https://github.com/settings/tokens" target="_blank">GitHub Settings → Personal access tokens</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="form-section" id="version_section" style="display: none;">
|
|
<label for="version">Version / Tag auswählen</label>
|
|
<select id="version" name="version" class="regular-text">
|
|
<option value="">-- Bitte warten, lade Versionen... --</option>
|
|
</select>
|
|
<span class="loading-indicator" id="version-loading">Lade verfügbare Versionen...</span>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="save_as_project" name="save_as_project">
|
|
<span>Als Projekt speichern (für spätere Updates)</span>
|
|
</label>
|
|
<p class="description">Wenn aktiviert, können Sie dieses Plugin später einfach über die Projektliste aktualisieren</p>
|
|
</div>
|
|
|
|
<div class="form-section" id="project_name_section" style="display: none;">
|
|
<label for="project_name">Projektname</label>
|
|
<input type="text" id="project_name" name="project_name" class="regular-text" placeholder="z.B. Mein WordPress Plugin">
|
|
<p class="description">Ein einprägsamer Name für dieses Projekt</p>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="respect_gitignore" name="respect_gitignore">
|
|
<span>.gitignore beachten (vendor, node_modules etc. entfernen)</span>
|
|
</label>
|
|
<p class="description">Nach dem Download werden Verzeichnisse und Dateien entfernt, die in der <code>.gitignore</code> des Plugins aufgeführt sind. Spart Speicherplatz, wenn der Vendor-Ordner im Repository eingecheckt ist.</p>
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
<?php submit_button('Installieren / Aktualisieren', 'primary large', 'install_update_plugin', false); ?>
|
|
</div>
|
|
|
|
<div id="plugin_status"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Saved Projects -->
|
|
<?php if (!empty($saved_projects)): ?>
|
|
<div class="github-card">
|
|
<h2>Gespeicherte Projekte</h2>
|
|
<p class="description">Diese Projekte können mit einem Klick aktualisiert werden</p>
|
|
|
|
<table class="wp-list-table widefat fixed striped projects-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 15%;">Projektname</th>
|
|
<th style="width: 20%;">Repository</th>
|
|
<th style="width: 12%;">Version</th>
|
|
<th style="width: 10%;">Typ</th>
|
|
<th style="width: 15%;">Letzte Aktualisierung</th>
|
|
<th style="width: 28%;">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($saved_projects as $project): ?>
|
|
<tr data-project-id="<?php echo esc_attr($project['id']); ?>">
|
|
<td><strong><?php echo esc_html($project['name']); ?></strong></td>
|
|
<td><code><?php echo esc_html($project['repo_url']); ?></code></td>
|
|
<td><?php echo esc_html($project['version']); ?></td>
|
|
<td>
|
|
<span class="<?php echo $project['is_private'] ? 'dashicons dashicons-lock' : 'dashicons dashicons-unlock'; ?>" title="<?php echo $project['is_private'] ? 'Privat' : 'Öffentlich'; ?>"></span>
|
|
<?php echo $project['is_private'] ? 'Privat' : 'Öffentlich'; ?>
|
|
</td>
|
|
<td><?php echo esc_html(date('d.m.Y H:i', strtotime($project['last_synced']))); ?></td>
|
|
<td class="project-actions">
|
|
<button class="button button-small github-edit-btn" data-project-id="<?php echo esc_attr($project['id']); ?>" title="Projekt bearbeiten">
|
|
<span class="dashicons dashicons-edit"></span>
|
|
</button>
|
|
<button class="button button-small button-primary github-sync-btn" data-project-id="<?php echo esc_attr($project['id']); ?>" title="Projekt aktualisieren">
|
|
<span class="dashicons dashicons-update"></span>
|
|
</button>
|
|
<button class="button button-small github-delete-btn" data-project-id="<?php echo esc_attr($project['id']); ?>" title="Projekt löschen">
|
|
<span class="dashicons dashicons-trash"></span>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr class="github-sync-status-row" data-project-id="<?php echo esc_attr($project['id']); ?>" style="display: none;">
|
|
<td colspan="6">
|
|
<div class="github-sync-status" data-project-id="<?php echo esc_attr($project['id']); ?>"></div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="github-card">
|
|
<div class="info-box">
|
|
<p><strong>💡 Tipp:</strong> Aktivieren Sie "Als Projekt speichern" beim Installieren eines Plugins, um es hier zur einfachen Verwaltung zu speichern.</p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Edit Project Modal -->
|
|
<div id="edit-project-modal" class="github-modal" style="display: none;">
|
|
<div class="github-modal-overlay"></div>
|
|
<div class="github-modal-content">
|
|
<div class="github-modal-header">
|
|
<h2>Projekt bearbeiten</h2>
|
|
<button class="github-modal-close" title="Schließen">
|
|
<span class="dashicons dashicons-no-alt"></span>
|
|
</button>
|
|
</div>
|
|
<div class="github-modal-body">
|
|
<form id="edit-project-form">
|
|
<input type="hidden" id="edit_project_id" name="project_id">
|
|
|
|
<div class="form-section">
|
|
<label for="edit_project_name">Projektname *</label>
|
|
<input type="text" id="edit_project_name" name="name" class="regular-text" required>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<label for="edit_repo_url">GitHub Repository URL *</label>
|
|
<input type="text" id="edit_repo_url" name="repo_url" class="regular-text" required>
|
|
<p class="description">Die vollständige URL zu Ihrem GitHub Repository</p>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="edit_is_private" name="is_private">
|
|
<span>Privates Repository (benötigt Access Token)</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-section" id="edit_access_token_section" style="display: none;">
|
|
<label for="edit_access_token">GitHub Access Token</label>
|
|
<input type="password" id="edit_access_token" name="access_token" class="regular-text" placeholder="Token eingeben oder leer lassen, um das bestehende zu behalten">
|
|
<p class="description">
|
|
Leer lassen, um das bestehende Token zu behalten. Token erstellen unter:
|
|
<a href="https://github.com/settings/tokens" target="_blank">GitHub Settings → Personal access tokens</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="form-section" id="edit_version_section">
|
|
<label for="edit_version">Version / Tag auswählen</label>
|
|
<select id="edit_version" name="version" class="regular-text">
|
|
<option value="">-- Bitte warten, lade Versionen... --</option>
|
|
</select>
|
|
<span class="loading-indicator" id="edit-version-loading">Lade verfügbare Versionen...</span>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="edit_respect_gitignore" name="respect_gitignore">
|
|
<span>.gitignore beachten (vendor, node_modules etc. entfernen)</span>
|
|
</label>
|
|
<p class="description">Bei jedem Update werden Verzeichnisse und Dateien entfernt, die in der <code>.gitignore</code> des Plugins aufgeführt sind.</p>
|
|
</div>
|
|
|
|
<div class="github-modal-footer">
|
|
<button type="button" class="button github-modal-close">Abbrechen</button>
|
|
<button type="submit" class="button button-primary" id="save-project-btn">
|
|
<span class="dashicons dashicons-yes"></span> Änderungen speichern
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
function apply_gitignore_cleanup($plugin_dir) {
|
|
$plugin_dir = realpath($plugin_dir);
|
|
if (!$plugin_dir) {
|
|
return;
|
|
}
|
|
|
|
// Remove untracked files that are ignored by .gitignore
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git clean -fdX 2>&1");
|
|
|
|
// Also remove tracked directories/files explicitly listed in .gitignore
|
|
$gitignore_file = $plugin_dir . '/.gitignore';
|
|
if (!file_exists($gitignore_file)) {
|
|
return;
|
|
}
|
|
|
|
$lines = file($gitignore_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
// Skip comments, empty lines, and negations
|
|
if (empty($line) || $line[0] === '#' || $line[0] === '!') {
|
|
continue;
|
|
}
|
|
// Skip patterns with wildcards or shell glob characters (too broad to handle safely)
|
|
if (strpos($line, '*') !== false || strpos($line, '?') !== false || strpos($line, '[') !== false) {
|
|
continue;
|
|
}
|
|
$path = rtrim($line, '/');
|
|
// Skip absolute paths or path traversal attempts
|
|
if ($path[0] === '/' || strpos($path, '..') !== false) {
|
|
continue;
|
|
}
|
|
$full_path = $plugin_dir . '/' . $path;
|
|
// Resolve and verify the path stays within the plugin directory
|
|
$real_parent = realpath(dirname($full_path));
|
|
if (!$real_parent || strpos($real_parent . '/', $plugin_dir . '/') !== 0 && $real_parent !== $plugin_dir) {
|
|
continue;
|
|
}
|
|
if (is_dir($full_path)) {
|
|
exec("rm -rf " . escapeshellarg($full_path));
|
|
} elseif (is_file($full_path)) {
|
|
unlink($full_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
function install_update_github_plugin($repo_url, $access_token, $selected_version, $respect_gitignore = false) {
|
|
if (!filter_var($repo_url, FILTER_VALIDATE_URL)) {
|
|
wp_die('Invalid GitHub URL provided.');
|
|
}
|
|
|
|
// Extract repository name from URL and convert to lowercase
|
|
$repo_name = strtolower(basename(parse_url($repo_url, PHP_URL_PATH), '.git'));
|
|
$plugin_dir = WP_PLUGIN_DIR . '/' . $repo_name;
|
|
|
|
$is_update = file_exists($plugin_dir);
|
|
|
|
if ($is_update) {
|
|
// Update existing plugin
|
|
// Update remote URL with access token if provided
|
|
if (!empty($access_token)) {
|
|
$auth_repo_url = str_replace('https://', "https://{$access_token}@", $repo_url);
|
|
$set_url_command = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($auth_repo_url) . " 2>&1";
|
|
exec($set_url_command, $url_output, $url_return);
|
|
}
|
|
|
|
// Validate version before checkout
|
|
if (!empty($selected_version)) {
|
|
// Fetch all refs, discard local changes, and checkout specific version
|
|
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch --all --tags && git checkout -f " . escapeshellarg($selected_version) . " && git clean -fd 2>&1";
|
|
exec($update_command, $output, $return_var);
|
|
|
|
if ($return_var !== 0) {
|
|
// Reset remote URL to original (without token) for security
|
|
if (!empty($access_token)) {
|
|
$reset_url_command = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($repo_url) . " 2>&1";
|
|
exec($reset_url_command, $reset_output, $reset_return);
|
|
}
|
|
wp_die('Failed to update the plugin. Error: ' . implode("\n", $output));
|
|
}
|
|
} else {
|
|
// If no version specified, get default branch and pull latest changes
|
|
$branch_command = "cd " . escapeshellarg($plugin_dir) . " && git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'";
|
|
exec($branch_command, $branch_output, $branch_return);
|
|
|
|
$default_branch = !empty($branch_output) ? trim($branch_output[0]) : 'main';
|
|
|
|
// Fetch and checkout default branch, overwriting local changes
|
|
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch origin && git checkout -f " . escapeshellarg($default_branch) . " && git reset --hard origin/" . escapeshellarg($default_branch) . " && git clean -fd 2>&1";
|
|
exec($update_command, $output, $return_var);
|
|
|
|
if ($return_var !== 0) {
|
|
// Reset remote URL to original (without token) for security
|
|
if (!empty($access_token)) {
|
|
$reset_url_command = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($repo_url) . " 2>&1";
|
|
exec($reset_url_command, $reset_output, $reset_return);
|
|
}
|
|
wp_die('Failed to update the plugin. Error: ' . implode("\n", $output));
|
|
}
|
|
}
|
|
|
|
// Reset remote URL to original (without token) for security
|
|
if (!empty($access_token)) {
|
|
$reset_url_command = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($repo_url) . " 2>&1";
|
|
exec($reset_url_command, $reset_output, $reset_return);
|
|
}
|
|
} else {
|
|
// Install new plugin
|
|
$clone_command = "git clone ";
|
|
if (!empty($access_token)) {
|
|
$repo_url_with_token = str_replace('https://', "https://{$access_token}@", $repo_url);
|
|
$clone_command .= escapeshellarg($repo_url_with_token) . " " . escapeshellarg($plugin_dir);
|
|
} else {
|
|
$clone_command .= escapeshellarg($repo_url) . " " . escapeshellarg($plugin_dir);
|
|
}
|
|
|
|
exec($clone_command, $output, $return_var);
|
|
|
|
if ($return_var !== 0) {
|
|
wp_die('Failed to clone the repository. Error: ' . implode("\n", $output));
|
|
}
|
|
|
|
// Reset remote URL to original (without token) for security
|
|
if (!empty($access_token)) {
|
|
$reset_url_command = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($repo_url) . " 2>&1";
|
|
exec($reset_url_command, $reset_output, $reset_return);
|
|
}
|
|
|
|
// Checkout the selected version
|
|
if (!empty($selected_version)) {
|
|
$checkout_command = "cd " . escapeshellarg($plugin_dir) . " && git checkout " . escapeshellarg($selected_version);
|
|
exec($checkout_command, $output, $return_var);
|
|
|
|
if ($return_var !== 0) {
|
|
wp_die('Failed to checkout version ' . $selected_version . '. Error: ' . implode("\n", $output));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove gitignored files/directories if option is enabled
|
|
if ($respect_gitignore) {
|
|
apply_gitignore_cleanup($plugin_dir);
|
|
}
|
|
|
|
// Find the main plugin file
|
|
$plugin_file = find_main_plugin_file($plugin_dir);
|
|
|
|
if (!$plugin_file) {
|
|
wp_die('Plugin main file not found. Please check the repository structure.');
|
|
}
|
|
|
|
$relative_plugin_file = $repo_name . '/' . $plugin_file;
|
|
|
|
// Provide a success message with a link to the plugins page
|
|
$plugins_page_url = admin_url('plugins.php');
|
|
$action = $is_update ? 'updated' : 'installed';
|
|
echo '<div class="updated"><p>Plugin ' . $action . ' successfully! You can <a href="' . esc_url($plugins_page_url) . '">go to the Plugins page</a> to activate or manage it.</p></div>';
|
|
}
|
|
|
|
function find_main_plugin_file($plugin_dir) {
|
|
$php_files = glob($plugin_dir . '/*.php');
|
|
|
|
foreach ($php_files as $file) {
|
|
$content = file_get_contents($file);
|
|
if (preg_match('/Plugin Name:/i', $content)) {
|
|
return basename($file);
|
|
}
|
|
}
|
|
|
|
return !empty($php_files) ? basename($php_files[0]) : false;
|
|
}
|
|
|
|
function preview_github_repo() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
$repo_url = $_POST['repo_url'];
|
|
$is_private = isset($_POST['is_private']) && $_POST['is_private'] === 'true';
|
|
$access_token = $is_private ? $_POST['access_token'] : '';
|
|
|
|
if (!filter_var($repo_url, FILTER_VALIDATE_URL)) {
|
|
wp_send_json_error('Invalid GitHub URL provided.');
|
|
}
|
|
|
|
$api_url = str_replace('github.com', 'api.github.com/repos', $repo_url);
|
|
$api_url = rtrim($api_url, '.git') . '/contents';
|
|
|
|
$args = array(
|
|
'headers' => array(
|
|
'Accept' => 'application/vnd.github.v3+json',
|
|
'User-Agent' => 'WordPress/GitHub Plugin Installer'
|
|
)
|
|
);
|
|
|
|
if ($is_private && !empty($access_token)) {
|
|
$args['headers']['Authorization'] = 'token ' . $access_token;
|
|
}
|
|
|
|
$response = wp_remote_get($api_url, $args);
|
|
|
|
if (is_wp_error($response)) {
|
|
wp_send_json_error('Failed to fetch repository content: ' . $response->get_error_message());
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body);
|
|
|
|
if (isset($data->message) && $data->message === 'Not Found') {
|
|
wp_send_json_error('Repository not found or access denied.');
|
|
}
|
|
|
|
$content = '<ul>';
|
|
foreach ($data as $item) {
|
|
$content .= '<li>' . esc_html($item->name) . ' (' . esc_html($item->type) . ')</li>';
|
|
}
|
|
$content .= '</ul>';
|
|
|
|
wp_send_json_success($content);
|
|
}
|
|
|
|
function get_github_versions() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
$repo_url = $_POST['repo_url'];
|
|
$is_private = isset($_POST['is_private']) && $_POST['is_private'] === 'true';
|
|
$access_token = $is_private ? $_POST['access_token'] : '';
|
|
|
|
if (!filter_var($repo_url, FILTER_VALIDATE_URL)) {
|
|
wp_send_json_error('Invalid GitHub URL provided.');
|
|
}
|
|
|
|
$api_url = str_replace('github.com', 'api.github.com/repos', $repo_url);
|
|
$api_url = rtrim($api_url, '.git') . '/tags';
|
|
|
|
$args = array(
|
|
'headers' => array(
|
|
'Accept' => 'application/vnd.github.v3+json',
|
|
'User-Agent' => 'WordPress/GitHub Plugin Installer'
|
|
)
|
|
);
|
|
|
|
if ($is_private && !empty($access_token)) {
|
|
$args['headers']['Authorization'] = 'token ' . $access_token;
|
|
}
|
|
|
|
$response = wp_remote_get($api_url, $args);
|
|
|
|
if (is_wp_error($response)) {
|
|
wp_send_json_error('Failed to fetch repository tags: ' . $response->get_error_message());
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$tags = json_decode($body);
|
|
|
|
if (empty($tags)) {
|
|
wp_send_json_error('No tags found in the repository.');
|
|
}
|
|
|
|
$versions = array();
|
|
foreach ($tags as $tag) {
|
|
$versions[] = $tag->name;
|
|
}
|
|
|
|
wp_send_json_success($versions);
|
|
}
|
|
|
|
function check_plugin_status() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
$repo_url = $_POST['repo_url'];
|
|
|
|
if (!filter_var($repo_url, FILTER_VALIDATE_URL)) {
|
|
wp_send_json_error('Invalid GitHub URL provided.');
|
|
}
|
|
|
|
$repo_name = strtolower(basename(parse_url($repo_url, PHP_URL_PATH), '.git'));
|
|
$plugin_dir = WP_PLUGIN_DIR . '/' . $repo_name;
|
|
|
|
if (file_exists($plugin_dir)) {
|
|
$status = 'installed';
|
|
// Get current version
|
|
$current_version = 'Unknown';
|
|
if (is_dir($plugin_dir . '/.git')) {
|
|
$version_command = "cd " . escapeshellarg($plugin_dir) . " && git describe --tags --abbrev=0";
|
|
exec($version_command, $output, $return_var);
|
|
if ($return_var === 0 && !empty($output)) {
|
|
$current_version = $output[0];
|
|
}
|
|
}
|
|
wp_send_json_success(array('status' => $status, 'version' => $current_version));
|
|
} else {
|
|
wp_send_json_success(array('status' => 'not_installed'));
|
|
}
|
|
}
|
|
|
|
function save_github_project() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions.');
|
|
}
|
|
|
|
$project_data = array(
|
|
'name' => sanitize_text_field($_POST['name']),
|
|
'repo_url' => esc_url_raw($_POST['repo_url']),
|
|
'is_private' => !empty($_POST['is_private']) && $_POST['is_private'] !== 'false' && $_POST['is_private'] !== '0',
|
|
'access_token' => isset($_POST['access_token']) ? sanitize_text_field($_POST['access_token']) : '',
|
|
'version' => sanitize_text_field($_POST['version']),
|
|
'respect_gitignore' => !empty($_POST['respect_gitignore']) && $_POST['respect_gitignore'] !== 'false' && $_POST['respect_gitignore'] !== '0'
|
|
);
|
|
|
|
if (empty($project_data['name']) || empty($project_data['repo_url'])) {
|
|
wp_send_json_error('Name und Repository URL sind erforderlich.');
|
|
}
|
|
|
|
$project_id = save_project($project_data);
|
|
wp_send_json_success(array('message' => 'Projekt erfolgreich gespeichert!', 'project_id' => $project_id));
|
|
}
|
|
|
|
function delete_github_project() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions.');
|
|
}
|
|
|
|
$project_id = sanitize_text_field($_POST['project_id']);
|
|
|
|
if (delete_project($project_id)) {
|
|
wp_send_json_success('Projekt erfolgreich gelöscht!');
|
|
} else {
|
|
wp_send_json_error('Projekt konnte nicht gelöscht werden.');
|
|
}
|
|
}
|
|
|
|
function sync_github_project() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions.');
|
|
}
|
|
|
|
$project_id = sanitize_text_field($_POST['project_id']);
|
|
$projects = get_saved_projects();
|
|
|
|
if (!isset($projects[$project_id])) {
|
|
wp_send_json_error('Projekt nicht gefunden.');
|
|
}
|
|
|
|
$project = $projects[$project_id];
|
|
|
|
$log_prefix = '[WP-Git-Installer] Sync project_id=' . $project_id . ' repo=' . ($project['repo_url'] ?? '?');
|
|
|
|
// Perform the sync
|
|
try {
|
|
$repo_url = $project['repo_url'];
|
|
$access_token = $project['access_token'];
|
|
$version = $project['version'];
|
|
|
|
$repo_name = strtolower(basename(parse_url($repo_url, PHP_URL_PATH), '.git'));
|
|
$plugin_dir = WP_PLUGIN_DIR . '/' . $repo_name;
|
|
|
|
error_log($log_prefix . ' | Plugin-Verzeichnis: ' . $plugin_dir . ' | Version: ' . ($version ?: 'latest'));
|
|
|
|
if (!file_exists($plugin_dir)) {
|
|
$msg = 'Plugin-Verzeichnis nicht gefunden: ' . $plugin_dir . '. Bitte installieren Sie es zuerst über das Formular oben.';
|
|
error_log($log_prefix . ' | FEHLER: ' . $msg);
|
|
wp_send_json_error($msg);
|
|
}
|
|
|
|
$debug_steps = [];
|
|
|
|
// Update remote URL with access token if private repository
|
|
if ($project['is_private'] && !empty($access_token)) {
|
|
$auth_repo_url = str_replace('https://', "https://{$access_token}@", $repo_url);
|
|
$set_url_cmd = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($auth_repo_url);
|
|
$set_url_result = [];
|
|
$set_url_code = 0;
|
|
exec($set_url_cmd . ' 2>&1', $set_url_result, $set_url_code);
|
|
if ($set_url_code !== 0) {
|
|
$detail = 'git remote set-url fehlgeschlagen (Exit ' . $set_url_code . '): ' . implode(' | ', array_filter(array_map('trim', $set_url_result)));
|
|
error_log($log_prefix . ' | FEHLER: ' . $detail);
|
|
wp_send_json_error('Authentifizierung fehlgeschlagen. ' . $detail);
|
|
}
|
|
}
|
|
|
|
$reset_url = function() use ($plugin_dir, $repo_url, $project, $access_token, $log_prefix) {
|
|
if ($project['is_private'] && !empty($access_token)) {
|
|
$cmd = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($repo_url);
|
|
$out = [];
|
|
$code = 0;
|
|
exec($cmd . ' 2>&1', $out, $code);
|
|
if ($code !== 0) {
|
|
error_log($log_prefix . ' | WARNUNG: Remote-URL zurücksetzen fehlgeschlagen (Exit ' . $code . '): ' . implode(' | ', array_filter(array_map('trim', $out))));
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!empty($version)) {
|
|
// --- Sync to specific version/tag ---
|
|
|
|
// Step 1: fetch
|
|
$fetch_out = [];
|
|
$fetch_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git fetch --all --tags 2>&1", $fetch_out, $fetch_code);
|
|
$debug_steps[] = 'git fetch --all --tags — Exit ' . $fetch_code . ($fetch_out ? ': ' . implode(' | ', array_filter(array_map('trim', $fetch_out))) : '');
|
|
error_log($log_prefix . ' | git fetch — Exit ' . $fetch_code . ': ' . implode(' | ', array_filter(array_map('trim', $fetch_out))));
|
|
|
|
if ($fetch_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git fetch');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Abrufen der Änderungen (git fetch).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
|
|
// Step 2: checkout
|
|
$checkout_out = [];
|
|
$checkout_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git checkout -f " . escapeshellarg($version) . " 2>&1", $checkout_out, $checkout_code);
|
|
$debug_steps[] = 'git checkout -f ' . $version . ' — Exit ' . $checkout_code . ($checkout_out ? ': ' . implode(' | ', array_filter(array_map('trim', $checkout_out))) : '');
|
|
error_log($log_prefix . ' | git checkout — Exit ' . $checkout_code . ': ' . implode(' | ', array_filter(array_map('trim', $checkout_out))));
|
|
|
|
if ($checkout_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git checkout');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Wechsel zur Version "' . $version . '" (git checkout).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
|
|
// Step 3: clean
|
|
$clean_out = [];
|
|
$clean_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git clean -fd 2>&1", $clean_out, $clean_code);
|
|
$debug_steps[] = 'git clean -fd — Exit ' . $clean_code . ($clean_out ? ': ' . implode(' | ', array_filter(array_map('trim', $clean_out))) : '');
|
|
error_log($log_prefix . ' | git clean — Exit ' . $clean_code . ': ' . implode(' | ', array_filter(array_map('trim', $clean_out))));
|
|
|
|
if ($clean_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git clean');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Bereinigen lokaler Dateien (git clean).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
} else {
|
|
// --- Sync to default branch ---
|
|
|
|
// Step 1: determine default branch
|
|
$branch_out = [];
|
|
$branch_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'", $branch_out, $branch_code);
|
|
$default_branch = !empty($branch_out) ? trim($branch_out[0]) : 'main';
|
|
$debug_steps[] = 'Ermittelter Standard-Branch: "' . $default_branch . '"';
|
|
error_log($log_prefix . ' | Standard-Branch: ' . $default_branch);
|
|
|
|
// Step 2: fetch
|
|
$fetch_out = [];
|
|
$fetch_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git fetch origin 2>&1", $fetch_out, $fetch_code);
|
|
$debug_steps[] = 'git fetch origin — Exit ' . $fetch_code . ($fetch_out ? ': ' . implode(' | ', array_filter(array_map('trim', $fetch_out))) : '');
|
|
error_log($log_prefix . ' | git fetch origin — Exit ' . $fetch_code . ': ' . implode(' | ', array_filter(array_map('trim', $fetch_out))));
|
|
|
|
if ($fetch_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git fetch');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Abrufen der Änderungen (git fetch).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
|
|
// Step 3: checkout
|
|
$checkout_out = [];
|
|
$checkout_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git checkout -f " . escapeshellarg($default_branch) . " 2>&1", $checkout_out, $checkout_code);
|
|
$debug_steps[] = 'git checkout -f ' . $default_branch . ' — Exit ' . $checkout_code . ($checkout_out ? ': ' . implode(' | ', array_filter(array_map('trim', $checkout_out))) : '');
|
|
error_log($log_prefix . ' | git checkout — Exit ' . $checkout_code . ': ' . implode(' | ', array_filter(array_map('trim', $checkout_out))));
|
|
|
|
if ($checkout_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git checkout');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Wechsel zum Branch "' . $default_branch . '" (git checkout).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
|
|
// Step 4: reset --hard
|
|
$reset_out = [];
|
|
$reset_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git reset --hard origin/" . escapeshellarg($default_branch) . " 2>&1", $reset_out, $reset_code);
|
|
$debug_steps[] = 'git reset --hard origin/' . $default_branch . ' — Exit ' . $reset_code . ($reset_out ? ': ' . implode(' | ', array_filter(array_map('trim', $reset_out))) : '');
|
|
error_log($log_prefix . ' | git reset --hard — Exit ' . $reset_code . ': ' . implode(' | ', array_filter(array_map('trim', $reset_out))));
|
|
|
|
if ($reset_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git reset --hard');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Zurücksetzen auf origin/' . $default_branch . ' (git reset --hard).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
|
|
// Step 5: clean
|
|
$clean_out = [];
|
|
$clean_code = 0;
|
|
exec("cd " . escapeshellarg($plugin_dir) . " && git clean -fd 2>&1", $clean_out, $clean_code);
|
|
$debug_steps[] = 'git clean -fd — Exit ' . $clean_code . ($clean_out ? ': ' . implode(' | ', array_filter(array_map('trim', $clean_out))) : '');
|
|
error_log($log_prefix . ' | git clean — Exit ' . $clean_code . ': ' . implode(' | ', array_filter(array_map('trim', $clean_out))));
|
|
|
|
if ($clean_code !== 0) {
|
|
$reset_url();
|
|
$detail = implode("\n", $debug_steps);
|
|
error_log($log_prefix . ' | FEHLER bei git clean');
|
|
wp_send_json_error([
|
|
'message' => 'Synchronisierung fehlgeschlagen beim Bereinigen lokaler Dateien (git clean).',
|
|
'debug' => $detail,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Reset remote URL to original (without token) for security
|
|
$reset_url();
|
|
|
|
// Remove gitignored files/directories if option is enabled
|
|
if (!empty($project['respect_gitignore'])) {
|
|
apply_gitignore_cleanup($plugin_dir);
|
|
}
|
|
|
|
// Update last_synced timestamp
|
|
$project['last_synced'] = current_time('mysql');
|
|
$projects[$project_id] = $project;
|
|
update_option('github_installer_projects', $projects);
|
|
|
|
error_log($log_prefix . ' | Synchronisierung erfolgreich.');
|
|
wp_send_json_success('Projekt erfolgreich synchronisiert!');
|
|
} catch (Exception $e) {
|
|
error_log($log_prefix . ' | EXCEPTION: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
|
|
wp_send_json_error([
|
|
'message' => 'Unerwarteter Fehler bei der Synchronisierung: ' . $e->getMessage(),
|
|
'debug' => $e->getFile() . ':' . $e->getLine(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
function get_github_project() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions.');
|
|
}
|
|
|
|
$project_id = sanitize_text_field($_POST['project_id']);
|
|
$projects = get_saved_projects();
|
|
|
|
if (!isset($projects[$project_id])) {
|
|
wp_send_json_error('Projekt nicht gefunden.');
|
|
}
|
|
|
|
wp_send_json_success($projects[$project_id]);
|
|
}
|
|
|
|
function update_github_project() {
|
|
check_ajax_referer('github_installer_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error('Insufficient permissions.');
|
|
}
|
|
|
|
$project_id = sanitize_text_field($_POST['project_id']);
|
|
$projects = get_saved_projects();
|
|
|
|
if (!isset($projects[$project_id])) {
|
|
wp_send_json_error('Projekt nicht gefunden.');
|
|
}
|
|
|
|
// Get updated project data
|
|
$updated_data = array(
|
|
'id' => $project_id,
|
|
'name' => sanitize_text_field($_POST['name']),
|
|
'repo_url' => esc_url_raw($_POST['repo_url']),
|
|
'is_private' => !empty($_POST['is_private']) && $_POST['is_private'] !== 'false' && $_POST['is_private'] !== '0',
|
|
'version' => sanitize_text_field($_POST['version']),
|
|
'respect_gitignore' => !empty($_POST['respect_gitignore']) && $_POST['respect_gitignore'] !== 'false' && $_POST['respect_gitignore'] !== '0',
|
|
'last_synced' => $projects[$project_id]['last_synced'] // Keep the existing last_synced time
|
|
);
|
|
|
|
// Handle access token - only update if a new one is provided
|
|
if (!empty($_POST['access_token'])) {
|
|
$updated_data['access_token'] = sanitize_text_field($_POST['access_token']);
|
|
} else {
|
|
$updated_data['access_token'] = $projects[$project_id]['access_token'];
|
|
}
|
|
|
|
// Validate required fields
|
|
if (empty($updated_data['name']) || empty($updated_data['repo_url'])) {
|
|
wp_send_json_error('Name und Repository URL sind erforderlich.');
|
|
}
|
|
|
|
// Update the project
|
|
$projects[$project_id] = $updated_data;
|
|
update_option('github_installer_projects', $projects);
|
|
|
|
wp_send_json_success(array('message' => 'Projekt erfolgreich aktualisiert!', 'project' => $updated_data));
|
|
}
|