- Create SQL migration for prompt templates used in article and image generation. - Add migration to change publish interval from days to hours in the sites table. - Implement InstallerController to handle installation requests and validation. - Develop FtpService for FTP connections and file uploads. - Create InstallerService to manage the WordPress installation process, including downloading, extracting, and configuring WordPress. - Add index view for the installer with form inputs for FTP, database, and WordPress admin settings. - Implement progress tracking for the installation process with AJAX polling.
286 lines
13 KiB
PHP
286 lines
13 KiB
PHP
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2>Kategorie WordPress: <?= htmlspecialchars($site['name']) ?></h2>
|
|
<a href="/sites/<?= $site['id'] ?>/edit" class="text-muted small">← Powrót do strony</a>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<?php if ($hasTopicsWithoutCategory): ?>
|
|
<button class="btn btn-success" id="btnFromTopics" onclick="createFromTopics()">
|
|
<i class="bi bi-magic me-1"></i>Utwórz kategorie z tematyk
|
|
</button>
|
|
<?php endif; ?>
|
|
<form method="post" action="/sites/<?= $site['id'] ?>/categories/sync">
|
|
<button type="submit" class="btn btn-outline-primary">
|
|
<i class="bi bi-arrow-repeat me-1"></i>Synchronizuj
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-lg-8">
|
|
<!-- Alert for auto-create results -->
|
|
<div class="alert alert-success d-none" id="resultAlert">
|
|
<i class="bi bi-check-circle me-1"></i><span id="resultMessage"></span>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body p-0">
|
|
<table class="table table-hover mb-0" id="categoriesTable">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nazwa</th>
|
|
<th>Slug</th>
|
|
<th>Parent</th>
|
|
<th>Posty</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($categories) || $categories === false): ?>
|
|
<tr id="emptyRow"><td colspan="5" class="text-center text-muted py-4">Kliknij "Synchronizuj" aby pobrać kategorie z WordPressa</td></tr>
|
|
<?php else: ?>
|
|
<?php
|
|
// Sort: parents first (alphabetically), then children under their parent
|
|
$catNames = [];
|
|
$parents = [];
|
|
$children = [];
|
|
foreach ($categories as $cat) {
|
|
$catNames[$cat['id']] = $cat['name'];
|
|
if (empty($cat['parent'])) {
|
|
$parents[$cat['id']] = $cat;
|
|
} else {
|
|
$children[$cat['parent']][] = $cat;
|
|
}
|
|
}
|
|
// Sort parents by name
|
|
uasort($parents, fn($a, $b) => strcasecmp($a['name'], $b['name']));
|
|
// Sort children by name within each group
|
|
foreach ($children as &$group) {
|
|
usort($group, fn($a, $b) => strcasecmp($a['name'], $b['name']));
|
|
}
|
|
unset($group);
|
|
// Build ordered list: parent -> its children -> next parent...
|
|
$sorted = [];
|
|
foreach ($parents as $p) {
|
|
$sorted[] = $p;
|
|
if (isset($children[$p['id']])) {
|
|
foreach ($children[$p['id']] as $c) {
|
|
$sorted[] = $c;
|
|
}
|
|
}
|
|
}
|
|
// Orphan children (parent not in list)
|
|
foreach ($children as $pid => $group) {
|
|
if (!isset($parents[$pid])) {
|
|
foreach ($group as $c) {
|
|
$sorted[] = $c;
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<?php foreach ($sorted as $cat): ?>
|
|
<tr>
|
|
<td><code><?= $cat['id'] ?></code></td>
|
|
<td>
|
|
<?php if (!empty($cat['parent'])): ?>
|
|
<span class="text-muted ms-3">└ </span>
|
|
<?php else: ?>
|
|
<strong>
|
|
<?php endif; ?>
|
|
<?= htmlspecialchars($cat['name']) ?>
|
|
<?php if (empty($cat['parent'])): ?>
|
|
</strong>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="text-muted small"><?= htmlspecialchars($cat['slug']) ?></td>
|
|
<td class="text-muted small"><?= !empty($cat['parent']) ? htmlspecialchars($catNames[$cat['parent']] ?? $cat['parent']) : '-' ?></td>
|
|
<td><?= $cat['count'] ?? 0 ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info mt-3">
|
|
<i class="bi bi-info-circle me-1"></i>
|
|
Użyj <strong>ID kategorii</strong> przy konfiguracji tematów, aby artykuły trafiały do odpowiednich kategorii w WordPressie.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-4">
|
|
<!-- Create single category -->
|
|
<div class="card">
|
|
<div class="card-header"><h5 class="mb-0">Utwórz kategorię</h5></div>
|
|
<div class="card-body">
|
|
<form id="createCategoryForm" onsubmit="return createCategory(event)">
|
|
<div class="mb-3">
|
|
<label for="cat_name" class="form-label">Nazwa</label>
|
|
<input type="text" class="form-control" id="cat_name" name="name" required placeholder="np. Technologia">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="cat_parent" class="form-label">Kategoria nadrzędna</label>
|
|
<select class="form-select" id="cat_parent" name="parent">
|
|
<option value="0">-- brak (główna) --</option>
|
|
<?php if (!empty($categories) && $categories !== false): ?>
|
|
<?php foreach ($categories as $cat): ?>
|
|
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100" id="btnCreateCategory">
|
|
<i class="bi bi-plus-lg me-1"></i>Utwórz w WordPress
|
|
</button>
|
|
</form>
|
|
<div class="alert alert-success d-none mt-3 mb-0 py-2" id="createResult"></div>
|
|
<div class="alert alert-danger d-none mt-3 mb-0 py-2" id="createError"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($hasTopicsWithoutCategory): ?>
|
|
<!-- Auto-create info -->
|
|
<div class="card mt-3">
|
|
<div class="card-header"><h5 class="mb-0">Auto-tworzenie z tematyk</h5></div>
|
|
<div class="card-body">
|
|
<p class="small text-muted mb-2">
|
|
Automatycznie utworzy kategorie w WordPress na podstawie tematyk przypisanych do tej strony.
|
|
Struktura parent/child zostanie odtworzona z biblioteki tematów.
|
|
</p>
|
|
<p class="small text-muted mb-0">
|
|
Istniejące kategorie (o tej samej nazwie) nie będą duplikowane.
|
|
Po utworzeniu, <code>wp_category_id</code> zostanie przypisane do odpowiednich tematów.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var siteId = <?= $site['id'] ?>;
|
|
|
|
function createCategory(e) {
|
|
e.preventDefault();
|
|
var form = document.getElementById('createCategoryForm');
|
|
var btn = document.getElementById('btnCreateCategory');
|
|
var resultEl = document.getElementById('createResult');
|
|
var errorEl = document.getElementById('createError');
|
|
resultEl.classList.add('d-none');
|
|
errorEl.classList.add('d-none');
|
|
|
|
var name = form.name.value.trim();
|
|
if (!name) return false;
|
|
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Tworzenie...';
|
|
|
|
var data = new FormData(form);
|
|
|
|
fetch('/sites/' + siteId + '/categories/create', {
|
|
method: 'POST',
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
body: data
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="bi bi-plus-lg me-1"></i>Utwórz w WordPress';
|
|
|
|
if (data.success) {
|
|
resultEl.textContent = data.message;
|
|
resultEl.classList.remove('d-none');
|
|
form.name.value = '';
|
|
|
|
// Add row to table
|
|
var cat = data.category;
|
|
var emptyRow = document.getElementById('emptyRow');
|
|
if (emptyRow) emptyRow.remove();
|
|
|
|
var tbody = document.querySelector('#categoriesTable tbody');
|
|
var tr = document.createElement('tr');
|
|
tr.style.opacity = '0';
|
|
tr.style.transition = 'opacity .3s';
|
|
var parentText = cat.parent ? (document.querySelector('#cat_parent option[value="' + cat.parent + '"]')?.textContent || cat.parent) : '-';
|
|
tr.innerHTML = '<td><code>' + cat.id + '</code></td>'
|
|
+ '<td>' + (cat.parent ? '<span class="text-muted">└ </span>' : '') + escapeHtml(cat.name) + '</td>'
|
|
+ '<td class="text-muted small">' + escapeHtml(cat.slug) + '</td>'
|
|
+ '<td class="text-muted small">' + escapeHtml(parentText.trim()) + '</td>'
|
|
+ '<td>0</td>';
|
|
tbody.appendChild(tr);
|
|
setTimeout(function() { tr.style.opacity = '1'; }, 10);
|
|
|
|
// Add to parent select
|
|
var opt = document.createElement('option');
|
|
opt.value = cat.id;
|
|
opt.textContent = cat.name;
|
|
document.getElementById('cat_parent').appendChild(opt);
|
|
} else {
|
|
errorEl.textContent = data.message;
|
|
errorEl.classList.remove('d-none');
|
|
}
|
|
})
|
|
.catch(function() {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="bi bi-plus-lg me-1"></i>Utwórz w WordPress';
|
|
errorEl.textContent = 'Błąd połączenia';
|
|
errorEl.classList.remove('d-none');
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
function createFromTopics() {
|
|
if (!confirm('Utworzyć kategorie w WordPress na podstawie przypisanych tematyk?\n\nIstniejące kategorie nie będą duplikowane.')) return;
|
|
|
|
var btn = document.getElementById('btnFromTopics');
|
|
var resultAlert = document.getElementById('resultAlert');
|
|
var resultMessage = document.getElementById('resultMessage');
|
|
resultAlert.classList.add('d-none');
|
|
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Tworzenie kategorii...';
|
|
|
|
fetch('/sites/' + siteId + '/categories/from-topics', {
|
|
method: 'POST',
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="bi bi-magic me-1"></i>Utwórz kategorie z tematyk';
|
|
|
|
if (data.success) {
|
|
resultMessage.textContent = data.message;
|
|
resultAlert.classList.remove('d-none');
|
|
|
|
if (data.errors && data.errors.length > 0) {
|
|
resultAlert.classList.remove('alert-success');
|
|
resultAlert.classList.add('alert-warning');
|
|
}
|
|
|
|
// Reload page after short delay to refresh table
|
|
setTimeout(function() { location.reload(); }, 1500);
|
|
} else {
|
|
resultAlert.classList.remove('alert-success');
|
|
resultAlert.classList.add('alert-danger');
|
|
resultMessage.textContent = data.message;
|
|
resultAlert.classList.remove('d-none');
|
|
}
|
|
})
|
|
.catch(function() {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="bi bi-magic me-1"></i>Utwórz kategorie z tematyk';
|
|
alert('Błąd połączenia');
|
|
});
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
</script>
|