Files
backPRO/templates/categories/index.php
Jacek Pyziak 4d5e220b3c Add BackPRO News theme and update database schema for article tracking
- Introduced a new WordPress theme "BackPRO News" with a lightweight magazine-style design.
- Added columns for tracking retry attempts and timestamps for unpublished/generated articles in the articles table.
- Included remote service metadata fields in the sites table for better management.
- Created log files for image replacements, installer actions, OpenAI article generation, and publishing processes.
- Implemented a dashboard template for site management, including permalink settings and theme installation options.
2026-02-17 20:08:02 +01:00

291 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">&larr; 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;
}
async function createFromTopics() {
var ok = await backproConfirm(
'Utworzyc kategorie w WordPress na podstawie przypisanych tematyk? Istniejace kategorie nie beda duplikowane.',
{ title: 'Tworzenie kategorii', confirmText: 'Utworz', cancelText: 'Anuluj', confirmClass: 'btn-success' }
);
if (!ok) 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';
backproNotify('Blad polaczenia', 'danger');
});
}
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>