Compare commits

..

No commits in common. "198324cf016b151faae0d474bb9f1ee03fa6be65" and "49d3ff746db5fb9f2d9242fc7522f8bb13522061" have entirely different histories.

3 changed files with 47 additions and 550 deletions

View file

@ -14,16 +14,12 @@ This plugin is designed for:

## Features

- **Multi-Project Support**: Save and manage multiple GitHub projects in one place
- **Project Cards**: Visual card-based interface showing all your saved projects
- **One-Click Sync**: Synchronize any saved project to pull the latest version from Git
- Install plugins directly from public or private GitHub repositories
- Update existing plugins installed from GitHub
- Preview repository contents before installation
- Select specific versions (tags) of a plugin to install
- Support for private repositories using GitHub Personal Access Tokens
- Automatic plugin folder naming based on the repository name
- Track last synchronization time for each project

## Installation

@ -33,33 +29,12 @@ This plugin is designed for:

## Usage

### Installing a New Plugin

1. In the WordPress admin panel, go to Plugins > GitHub Installer.
2. Enter a project name (optional) to save the project for later use.
3. Enter the GitHub repository URL of the plugin you want to install.
4. If it's a private repository, check the "Private Repository?" box and enter your GitHub Personal Access Token.
5. The plugin will fetch available versions and provide a preview of the repository contents.
6. Select the version you want to install from the dropdown menu.
7. Click "Install/Update Plugin" to proceed with the installation or update.
8. Optionally, click "Als Projekt speichern" (Save as Project) to save this configuration for future use.

### Managing Saved Projects

Once you've saved projects, they will appear as cards below the installation form:

- **View Project Details**: Each card shows the project name, repository URL, version, privacy status, and last sync time.
- **Synchronize**: Click the "Synchronisieren" button to pull the latest version from Git for that specific project.
- **Delete**: Click the "Löschen" button to remove a saved project from your list (this won't uninstall the plugin, just removes it from the saved projects).

### Synchronizing Projects

The synchronization feature allows you to quickly update any saved project:

1. Find the project card you want to sync.
2. Click the "Synchronisieren" (Synchronize) button.
3. The plugin will automatically fetch the latest version from Git and update the plugin.
4. You'll see a status message indicating success or failure.
2. Enter the GitHub repository URL of the plugin you want to install.
3. If it's a private repository, check the "Private Repository?" box and enter your GitHub Personal Access Token.
4. The plugin will fetch available versions and provide a preview of the repository contents.
5. Select the version you want to install from the dropdown menu.
6. Click "Install/Update Plugin" to proceed with the installation or update.

## Requirements

@ -82,10 +57,8 @@ No additional configuration is required after installation. However, for private
## Security Considerations

- The plugin uses nonces and capability checks to ensure only authorized users can install plugins.
- **Personal Access Tokens are stored in the WordPress database** when you save a project. Ensure your WordPress installation is secure.
- Only users with the `manage_options` capability (typically administrators) can access the plugin.
- Personal Access Tokens are not stored by the plugin and must be entered each time for private repositories.
- Always review the contents of a repository before installing to ensure it's from a trusted source.
- Saved projects and their tokens are stored using WordPress options API with proper sanitization.

## Limitations


View file

@ -1,8 +1,8 @@
<?php
/**
* Plugin Name: GitHub Plugin Installer
* Description: Install or update WordPress plugins directly from GitHub repositories with multi-project support
* Version: 2.0
* Description: Install or update WordPress plugins directly from GitHub repositories
* Version: 1.4
* Author: Christian Wedel
*/

@ -12,9 +12,6 @@ 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');

function github_plugin_installer_menu() {
add_plugins_page('GitHub Plugin Installer', 'GitHub Installer', 'manage_options', 'github-plugin-installer', 'github_plugin_installer_page');
@ -31,38 +28,6 @@ function github_plugin_installer_scripts($hook) {
));
}

// 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']),
'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;
@ -73,201 +38,46 @@ function github_plugin_installer_page() {
$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']);

install_update_github_plugin($repo_url, $access_token, $selected_version);
}

$saved_projects = get_saved_projects();

?>
<style>
.github-projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.github-project-card {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
transition: box-shadow 0.2s ease;
}
.github-project-card:hover {
box-shadow: 0 2px 6px rgba(0,0,0,.1);
}
.github-project-card h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
color: #1d2327;
}
.github-project-info {
font-size: 13px;
color: #50575e;
margin-bottom: 15px;
}
.github-project-info p {
margin: 5px 0;
word-break: break-all;
}
.github-project-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.github-sync-btn {
background: #2271b1;
border-color: #2271b1;
color: #fff;
padding: 6px 12px;
border-radius: 3px;
cursor: pointer;
border: 1px solid;
font-size: 13px;
transition: background 0.2s ease;
}
.github-sync-btn:hover {
background: #135e96;
border-color: #135e96;
}
.github-sync-btn:disabled {
background: #c3c4c7;
border-color: #c3c4c7;
cursor: not-allowed;
}
.github-delete-btn {
background: #fff;
border-color: #c3c4c7;
color: #b32d2e;
padding: 6px 12px;
border-radius: 3px;
cursor: pointer;
border: 1px solid;
font-size: 13px;
transition: all 0.2s ease;
}
.github-delete-btn:hover {
background: #b32d2e;
border-color: #b32d2e;
color: #fff;
}
.github-sync-status {
font-size: 12px;
margin-top: 10px;
padding: 8px;
border-radius: 3px;
display: none;
}
.github-sync-status.success {
background: #d7f0db;
color: #00631e;
border: 1px solid #00631e;
}
.github-sync-status.error {
background: #fcf0f1;
color: #b32d2e;
border: 1px solid #b32d2e;
}
.github-sync-status.loading {
background: #f0f6fc;
color: #1d2327;
border: 1px solid #2271b1;
}
.github-add-project-section {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 20px;
margin-bottom: 30px;
}
.github-save-project-btn {
background: #00a32a;
border-color: #00a32a;
color: #fff;
margin-left: 10px;
}
.github-save-project-btn:hover {
background: #008a20;
border-color: #008a20;
}
</style>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>

<div class="github-add-project-section">
<h2>Neues Projekt hinzufügen</h2>
<form method="post" action="" id="github-project-form">
<table class="form-table">
<tr>
<th scope="row"><label for="project_name">Projektname</label></th>
<td><input type="text" id="project_name" name="project_name" class="regular-text" placeholder="Mein GitHub Plugin"></td>
</tr>
<tr>
<th scope="row"><label for="repo_url">GitHub Repository URL</label></th>
<td><input type="text" id="repo_url" name="repo_url" class="regular-text" required placeholder="https://github.com/username/repo.git"></td>
</tr>
<tr>
<th scope="row"><label for="is_private">Privates Repository?</label></th>
<td><input type="checkbox" id="is_private" name="is_private"></td>
</tr>
<tr id="access_token_row" style="display: none;">
<th scope="row"><label for="access_token">GitHub Access Token</label></th>
<td>
<input type="password" id="access_token" name="access_token" class="regular-text">
<p class="description">
To generate a Personal Access Token, go to
<a href="https://github.com/settings/tokens" target="_blank">GitHub Settings > Developer settings > Personal access tokens</a>.
Create a new token with the 'repo' scope for private repositories.
</p>
</td>
</tr>
<tr id="version_row" style="display: none;">
<th scope="row"><label for="version">Version</label></th>
<td><select id="version" name="version"></select></td>
</tr>
</table>
<div id="plugin_status"></div>
<?php submit_button('Installieren/Aktualisieren', 'primary', 'install_update_plugin', false); ?>
<button type="button" id="save_project_btn" class="button github-save-project-btn" style="display:none;">Als Projekt speichern</button>
</form>
<div id="repo_preview" style="margin-top: 20px; padding: 10px; border: 1px solid #ccc; display: none;">
<h3>Repository Preview</h3>
<div id="repo_content"></div>
</div>
<form method="post" action="">
<table class="form-table">
<tr>
<th scope="row"><label for="repo_url">GitHub Repository URL</label></th>
<td><input type="text" id="repo_url" name="repo_url" class="regular-text" required placeholder="https://github.com/username/repo.git"></td>
</tr>
<tr>
<th scope="row"><label for="is_private">Private Repository?</label></th>
<td><input type="checkbox" id="is_private" name="is_private"></td>
</tr>
<tr id="access_token_row" style="display: none;">
<th scope="row"><label for="access_token">GitHub Access Token</label></th>
<td>
<input type="password" id="access_token" name="access_token" class="regular-text">
<p class="description">
To generate a Personal Access Token, go to
<a href="https://github.com/settings/tokens" target="_blank">GitHub Settings > Developer settings > Personal access tokens</a>.
Create a new token with the 'repo' scope for private repositories.
</p>
</td>
</tr>
<tr id="version_row" style="display: none;">
<th scope="row"><label for="version">Version</label></th>
<td><select id="version" name="version"></select></td>
</tr>
</table>
<div id="plugin_status"></div>
<?php submit_button('Install/Update Plugin', 'primary', 'install_update_plugin'); ?>
</form>
<div id="repo_preview" style="margin-top: 20px; padding: 10px; border: 1px solid #ccc; display: none;">
<h2>Repository Preview</h2>
<div id="repo_content"></div>
</div>

<?php if (!empty($saved_projects)): ?>
<h2>Gespeicherte Projekte</h2>
<div class="github-projects-grid">
<?php foreach ($saved_projects as $project): ?>
<div class="github-project-card" data-project-id="<?php echo esc_attr($project['id']); ?>">
<h3><?php echo esc_html($project['name']); ?></h3>
<div class="github-project-info">
<p><strong>Repository:</strong> <?php echo esc_html($project['repo_url']); ?></p>
<p><strong>Version:</strong> <?php echo esc_html($project['version']); ?></p>
<p><strong>Status:</strong> <?php echo $project['is_private'] ? 'Privat' : 'Öffentlich'; ?></p>
<p><strong>Zuletzt synchronisiert:</strong> <?php echo esc_html($project['last_synced']); ?></p>
</div>
<div class="github-project-actions">
<button class="github-sync-btn" data-project-id="<?php echo esc_attr($project['id']); ?>">
Synchronisieren
</button>
<button class="github-delete-btn" data-project-id="<?php echo esc_attr($project['id']); ?>">
Löschen
</button>
</div>
<div class="github-sync-status" data-project-id="<?php echo esc_attr($project['id']); ?>"></div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="notice notice-info">
<p>Keine gespeicherten Projekte vorhanden. Fügen Sie oben ein neues Projekt hinzu.</p>
</div>
<?php endif; ?>
</div>
<?php
}
@ -285,55 +95,19 @@ function install_update_github_plugin($repo_url, $access_token, $selected_versio

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);
}
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch --all && git checkout " . escapeshellarg($selected_version);
exec($update_command, $output, $return_var);

// Validate version before checkout
if (!empty($selected_version)) {
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch --all && git checkout " . escapeshellarg($selected_version) . " 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, just fetch and pull the default branch
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch --all && git pull 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);
if ($return_var !== 0) {
wp_die('Failed to update the plugin. Error: ' . implode("\n", $output));
}
} 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);
$repo_url = str_replace('https://', "https://{$access_token}@", $repo_url);
}
$clone_command .= escapeshellarg($repo_url) . " " . escapeshellarg($plugin_dir);

exec($clone_command, $output, $return_var);

@ -341,12 +115,6 @@ function install_update_github_plugin($repo_url, $access_token, $selected_versio
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);
@ -506,124 +274,4 @@ function check_plugin_status() {
} 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'])
);

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];

// 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;

if (!file_exists($plugin_dir)) {
wp_send_json_error('Plugin ist nicht installiert. Bitte installieren Sie es zuerst über das Formular oben.');
}

// 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_command = "cd " . escapeshellarg($plugin_dir) . " && git remote set-url origin " . escapeshellarg($auth_repo_url) . " 2>&1";
exec($set_url_command, $url_output, $url_return);
}

// Update existing plugin - validate version before checkout
if (!empty($version)) {
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch --all && git checkout " . escapeshellarg($version) . " 2>&1";
exec($update_command, $output, $return_var);

if ($return_var !== 0) {
// Reset remote URL to original (without token) for security
if ($project['is_private'] && !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_send_json_error('Synchronisierung fehlgeschlagen: ' . implode("\n", $output));
}
} else {
// If no version specified, just fetch and pull the default branch
$update_command = "cd " . escapeshellarg($plugin_dir) . " && git fetch --all && git pull 2>&1";
exec($update_command, $output, $return_var);

if ($return_var !== 0) {
// Reset remote URL to original (without token) for security
if ($project['is_private'] && !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_send_json_error('Synchronisierung fehlgeschlagen: ' . implode("\n", $output));
}
}

// Reset remote URL to original (without token) for security
if ($project['is_private'] && !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);
}

// Update last_synced timestamp
$project['last_synced'] = current_time('mysql');
$projects[$project_id] = $project;
update_option('github_installer_projects', $projects);

wp_send_json_success('Projekt erfolgreich synchronisiert!');
} catch (Exception $e) {
wp_send_json_error('Fehler bei der Synchronisierung: ' . $e->getMessage());
}
}

View file

@ -17,15 +17,6 @@ jQuery(document).ready(function() {
previewTimer = setTimeout(updatePreviewAndVersions, 500);
});

// Show "Save as Project" button when version is selected
jQuery('#version').on('change', function() {
if(jQuery(this).val()) {
jQuery('#save_project_btn').show();
} else {
jQuery('#save_project_btn').hide();
}
});

function updatePreviewAndVersions() {
previewRepo();
getVersions();
@ -87,137 +78,22 @@ jQuery(document).ready(function() {
var versions = response.data;
var versionSelect = jQuery('#version');
versionSelect.empty();
versionSelect.append(jQuery('<option></option>').attr('value', '').text('-- Wählen Sie eine Version --'));
jQuery.each(versions, function(index, version) {
versionSelect.append(jQuery('<option></option>').attr('value', version).text(version));
});
jQuery('#version_row').show();
} else {
jQuery('#version_row').hide();
jQuery('#save_project_btn').hide();
console.error('Failed to fetch versions:', response.data);
}
},
error: function() {
jQuery('#version_row').hide();
jQuery('#save_project_btn').hide();
console.error('An error occurred while fetching the repository versions.');
}
});
} else {
jQuery('#version_row').hide();
jQuery('#save_project_btn').hide();
}
}

// Save project button handler
jQuery('#save_project_btn').on('click', function(e) {
e.preventDefault();

var projectName = jQuery('#project_name').val();
var repoUrl = jQuery('#repo_url').val();
var isPrivate = jQuery('#is_private').is(':checked');
var accessToken = jQuery('#access_token').val();
var version = jQuery('#version').val();

if (!projectName) {
alert('Bitte geben Sie einen Projektnamen ein.');
return;
}

if (!repoUrl || !version) {
alert('Bitte füllen Sie alle erforderlichen Felder aus.');
return;
}

jQuery.ajax({
url: github_installer.ajax_url,
type: 'POST',
data: {
action: 'save_github_project',
nonce: github_installer.nonce,
name: projectName,
repo_url: repoUrl,
is_private: isPrivate,
access_token: accessToken,
version: version
},
success: function(response) {
if (response.success) {
alert(response.data.message);
location.reload();
} else {
alert('Fehler: ' + response.data);
}
},
error: function() {
alert('Ein Fehler ist beim Speichern des Projekts aufgetreten.');
}
});
});

// Sync project button handler
jQuery(document).on('click', '.github-sync-btn', function() {
var projectId = jQuery(this).data('project-id');
var button = jQuery(this);
var statusDiv = jQuery('.github-sync-status[data-project-id="' + projectId + '"]');

button.prop('disabled', true);
statusDiv.removeClass('success error').addClass('loading').text('Synchronisierung läuft...').show();

jQuery.ajax({
url: github_installer.ajax_url,
type: 'POST',
data: {
action: 'sync_github_project',
nonce: github_installer.nonce,
project_id: projectId
},
success: function(response) {
button.prop('disabled', false);
if (response.success) {
statusDiv.removeClass('loading error').addClass('success').text(response.data);
setTimeout(function() {
location.reload();
}, 1500);
} else {
statusDiv.removeClass('loading success').addClass('error').text('Fehler: ' + response.data);
}
},
error: function() {
button.prop('disabled', false);
statusDiv.removeClass('loading success').addClass('error').text('Ein Fehler ist aufgetreten.');
}
});
});

// Delete project button handler
jQuery(document).on('click', '.github-delete-btn', function() {
var projectId = jQuery(this).data('project-id');

if (!confirm('Möchten Sie dieses Projekt wirklich löschen?')) {
return;
}

jQuery.ajax({
url: github_installer.ajax_url,
type: 'POST',
data: {
action: 'delete_github_project',
nonce: github_installer.nonce,
project_id: projectId
},
success: function(response) {
if (response.success) {
alert(response.data);
location.reload();
} else {
alert('Fehler: ' + response.data);
}
},
error: function() {
alert('Ein Fehler ist beim Löschen des Projekts aufgetreten.');
}
});
});
});