Skip to content

Commit c3372ff

Browse files
committed
feed filter
1 parent 40037b1 commit c3372ff

File tree

2 files changed

+224
-16
lines changed

2 files changed

+224
-16
lines changed

app/src/Controllers/FeedController.php

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,28 +122,43 @@ public function json(ServerRequestInterface $request): ResponseInterface
122122
$page = isset($params['page']) ? max(1, (int)$params['page']) : 1;
123123
$perPage = isset($params['per_page']) ? min(100, max(1, (int)$params['per_page'])) : 20;
124124
$offset = ($page - 1) * $perPage;
125-
$categorySlug = $params['category'] ?? null;
126-
$tagSlug = $params['tag'] ?? null;
125+
126+
// Support both single and multiple categories/tags
127+
$categorySlugs = [];
128+
if (isset($params['category'])) {
129+
$categorySlugs = [$params['category']];
130+
} elseif (isset($params['categories'])) {
131+
$categorySlugs = array_filter(explode(',', $params['categories']));
132+
}
133+
134+
$tagSlugs = [];
135+
if (isset($params['tag'])) {
136+
$tagSlugs = [$params['tag']];
137+
} elseif (isset($params['tags'])) {
138+
$tagSlugs = array_filter(explode(',', $params['tags']));
139+
}
127140

128141
$whereConditions = ["fi.is_visible = 1"];
129142
$queryParams = [];
130143

131-
if ($categorySlug) {
144+
if (!empty($categorySlugs)) {
145+
$placeholders = implode(',', array_fill(0, count($categorySlugs), '%s'));
132146
$whereConditions[] = "EXISTS (
133147
SELECT 1 FROM feed_categories fc
134148
JOIN categories c ON fc.category_id = c.id
135-
WHERE fc.feed_id = f.id AND c.slug = %s
149+
WHERE fc.feed_id = f.id AND c.slug IN ($placeholders)
136150
)";
137-
$queryParams[] = $categorySlug;
151+
$queryParams = array_merge($queryParams, $categorySlugs);
138152
}
139153

140-
if ($tagSlug) {
154+
if (!empty($tagSlugs)) {
155+
$placeholders = implode(',', array_fill(0, count($tagSlugs), '%s'));
141156
$whereConditions[] = "EXISTS (
142157
SELECT 1 FROM feed_tags ft
143158
JOIN tags t ON ft.tag_id = t.id
144-
WHERE ft.feed_id = f.id AND t.slug = %s
159+
WHERE ft.feed_id = f.id AND t.slug IN ($placeholders)
145160
)";
146-
$queryParams[] = $tagSlug;
161+
$queryParams = array_merge($queryParams, $tagSlugs);
147162
}
148163

149164
$whereClause = implode(' AND ', $whereConditions);
@@ -203,28 +218,43 @@ public function rss(ServerRequestInterface $request): ResponseInterface
203218
$page = isset($params['page']) ? max(1, (int)$params['page']) : 1;
204219
$perPage = isset($params['per_page']) ? min(100, max(1, (int)$params['per_page'])) : 20;
205220
$offset = ($page - 1) * $perPage;
206-
$categorySlug = $params['category'] ?? null;
207-
$tagSlug = $params['tag'] ?? null;
221+
222+
// Support both single and multiple categories/tags
223+
$categorySlugs = [];
224+
if (isset($params['category'])) {
225+
$categorySlugs = [$params['category']];
226+
} elseif (isset($params['categories'])) {
227+
$categorySlugs = array_filter(explode(',', $params['categories']));
228+
}
229+
230+
$tagSlugs = [];
231+
if (isset($params['tag'])) {
232+
$tagSlugs = [$params['tag']];
233+
} elseif (isset($params['tags'])) {
234+
$tagSlugs = array_filter(explode(',', $params['tags']));
235+
}
208236

209237
$whereConditions = ["fi.is_visible = 1"];
210238
$queryParams = [];
211239

212-
if ($categorySlug) {
240+
if (!empty($categorySlugs)) {
241+
$placeholders = implode(',', array_fill(0, count($categorySlugs), '%s'));
213242
$whereConditions[] = "EXISTS (
214243
SELECT 1 FROM feed_categories fc
215244
JOIN categories c ON fc.category_id = c.id
216-
WHERE fc.feed_id = f.id AND c.slug = %s
245+
WHERE fc.feed_id = f.id AND c.slug IN ($placeholders)
217246
)";
218-
$queryParams[] = $categorySlug;
247+
$queryParams = array_merge($queryParams, $categorySlugs);
219248
}
220249

221-
if ($tagSlug) {
250+
if (!empty($tagSlugs)) {
251+
$placeholders = implode(',', array_fill(0, count($tagSlugs), '%s'));
222252
$whereConditions[] = "EXISTS (
223253
SELECT 1 FROM feed_tags ft
224254
JOIN tags t ON ft.tag_id = t.id
225-
WHERE ft.feed_id = f.id AND t.slug = %s
255+
WHERE ft.feed_id = f.id AND t.slug IN ($placeholders)
226256
)";
227-
$queryParams[] = $tagSlug;
257+
$queryParams = array_merge($queryParams, $tagSlugs);
228258
}
229259

230260
$whereClause = implode(' AND ', $whereConditions);
@@ -248,6 +278,7 @@ public function rss(ServerRequestInterface $request): ResponseInterface
248278
$channel->addChild('language', 'pt-br');
249279
$channel->addChild('pubDate', date('r'));
250280

281+
251282
foreach ($items as $item) {
252283
$xmlItem = $channel->addChild('item');
253284
$xmlItem->addChild('title', htmlspecialchars($item['title']));
@@ -279,4 +310,21 @@ public function rss(ServerRequestInterface $request): ResponseInterface
279310

280311
return new XmlResponse($xmlString);
281312
}
313+
314+
public function feedBuilder(ServerRequestInterface $request): ResponseInterface
315+
{
316+
// Get all categories with item counts from the cached column
317+
$categories = DB::query("SELECT * FROM categories ORDER BY name");
318+
319+
// Get all tags with item counts from the cached column
320+
$tags = DB::query("SELECT * FROM tags ORDER BY name");
321+
322+
$html = $this->templates->render('feed-builder', [
323+
'categories' => $categories,
324+
'tags' => $tags,
325+
'title' => 'Construtor de Feed'
326+
]);
327+
328+
return new HtmlResponse($html);
329+
}
282330
}

app/templates/feed-builder.php

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php $this->layout('layout', ['title' => 'Construtor de Feed']) ?>
2+
3+
<?php $this->start('active') ?>feed-builder<?php $this->stop() ?>
4+
5+
<div class="card shadow-sm">
6+
<div class="card-header">
7+
<h3 class="fs-5 fw-medium mb-1 mt-1">
8+
<i class="bi bi-braces me-1"></i>
9+
Construtor de Feed
10+
</h3>
11+
</div>
12+
<div class="card-body">
13+
<div class="row">
14+
<div class="col-md-6 mb-4">
15+
<h5 class="fs-6 fw-medium mb-3">
16+
<i class="bi bi-folder me-1"></i>
17+
Categorias
18+
</h5>
19+
<div class="list-group" style="max-height: 292px; overflow-y: auto;">
20+
<?php foreach ($categories as $category): ?>
21+
<label class="list-group-item d-flex justify-content-between align-items-center">
22+
<div class="d-flex align-items-center">
23+
<input class="form-check-input me-2 category-checkbox" type="checkbox" value="<?= $this->e($category['slug']) ?>" data-name="<?= $this->e($category['name']) ?>">
24+
<span><?= $this->e($category['name']) ?></span>
25+
</div>
26+
<span class="badge bg-info text-dark rounded-pill"><?= $category['item_count'] ?></span>
27+
</label>
28+
<?php endforeach; ?>
29+
</div>
30+
</div>
31+
32+
<div class="col-md-6 mb-4">
33+
<h5 class="fs-6 fw-medium mb-3">
34+
<i class="bi bi-tags me-1"></i>
35+
Tags
36+
</h5>
37+
<div class="list-group" style="max-height: 292px; overflow-y: auto;">
38+
<?php foreach ($tags as $tag): ?>
39+
<label class="list-group-item d-flex justify-content-between align-items-center">
40+
<div class="d-flex align-items-center">
41+
<input class="form-check-input me-2 tag-checkbox" type="checkbox" value="<?= $this->e($tag['slug']) ?>" data-name="<?= $this->e($tag['name']) ?>">
42+
<span><?= $this->e($tag['name']) ?></span>
43+
</div>
44+
<span class="badge bg-secondary rounded-pill"><?= $tag['item_count'] ?></span>
45+
</label>
46+
<?php endforeach; ?>
47+
</div>
48+
</div>
49+
</div>
50+
51+
<div class="row border-top pt-4">
52+
<div class="col-12 col-md-6">
53+
<label class="form-label fw-medium">
54+
<i class="bi bi-rss me-1"></i>
55+
Feed RSS
56+
</label>
57+
<div class="input-group">
58+
<input type="text" class="form-control" id="rssUrl" readonly value="<?= $_ENV['APP_URL'] ?>/feed/rss">
59+
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('rssUrl')">
60+
<i class="bi bi-clipboard"></i>
61+
</button>
62+
<a href="<?= $_ENV['APP_URL'] ?>/feed/rss" target="_blank" class="btn btn-outline-primary" id="rssLink">
63+
<i class="bi bi-box-arrow-up-right"></i>
64+
</a>
65+
</div>
66+
</div>
67+
68+
<div class="col-12 col-md-6">
69+
<label class="form-label fw-medium">
70+
<i class="bi bi-braces me-1"></i>
71+
Feed JSON
72+
</label>
73+
<div class="input-group">
74+
<input type="text" class="form-control" id="jsonUrl" readonly value="<?= $_ENV['APP_URL'] ?>/feed/json">
75+
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('jsonUrl')">
76+
<i class="bi bi-clipboard"></i>
77+
</button>
78+
<a href="<?= $_ENV['APP_URL'] ?>/feed/json" target="_blank" class="btn btn-outline-primary" id="jsonLink">
79+
<i class="bi bi-box-arrow-up-right"></i>
80+
</a>
81+
</div>
82+
</div>
83+
</div>
84+
85+
</div>
86+
</div>
87+
88+
<?php $this->start('scripts') ?>
89+
<script>
90+
document.addEventListener('DOMContentLoaded', function() {
91+
const categoryCheckboxes = document.querySelectorAll('.category-checkbox');
92+
const tagCheckboxes = document.querySelectorAll('.tag-checkbox');
93+
const rssUrlInput = document.getElementById('rssUrl');
94+
const jsonUrlInput = document.getElementById('jsonUrl');
95+
const rssLink = document.getElementById('rssLink');
96+
const jsonLink = document.getElementById('jsonLink');
97+
98+
const baseUrl = '<?= $_ENV['APP_URL'] ?>';
99+
100+
function updateUrls() {
101+
const selectedCategories = Array.from(categoryCheckboxes)
102+
.filter(cb => cb.checked)
103+
.map(cb => cb.value);
104+
105+
const selectedTags = Array.from(tagCheckboxes)
106+
.filter(cb => cb.checked)
107+
.map(cb => cb.value);
108+
109+
const params = [];
110+
111+
if (selectedCategories.length > 0) {
112+
params.push('categories=' + selectedCategories.join(','));
113+
}
114+
115+
if (selectedTags.length > 0) {
116+
params.push('tags=' + selectedTags.join(','));
117+
}
118+
119+
const queryString = params.length > 0 ? '?' + params.join('&') : '';
120+
121+
const rssUrl = baseUrl + '/feed/rss' + queryString;
122+
const jsonUrl = baseUrl + '/feed/json' + queryString;
123+
124+
rssUrlInput.value = rssUrl;
125+
jsonUrlInput.value = jsonUrl;
126+
rssLink.href = rssUrl;
127+
jsonLink.href = jsonUrl;
128+
}
129+
130+
categoryCheckboxes.forEach(cb => {
131+
cb.addEventListener('change', updateUrls);
132+
});
133+
134+
tagCheckboxes.forEach(cb => {
135+
cb.addEventListener('change', updateUrls);
136+
});
137+
});
138+
139+
function copyToClipboard(inputId) {
140+
const input = document.getElementById(inputId);
141+
input.select();
142+
input.setSelectionRange(0, 99999);
143+
144+
navigator.clipboard.writeText(input.value)
145+
.then(() => {
146+
const btn = event.target.closest('button');
147+
const originalHtml = btn.innerHTML;
148+
btn.innerHTML = '<i class="bi bi-check-lg"></i>';
149+
150+
setTimeout(() => {
151+
btn.innerHTML = originalHtml;
152+
}, 2000);
153+
})
154+
.catch(err => {
155+
console.error('Erro ao copiar: ', err);
156+
alert('Não foi possível copiar a URL.');
157+
});
158+
}
159+
</script>
160+
<?php $this->stop() ?>

0 commit comments

Comments
 (0)