Code
Publisium
A modern, fast CMS for building article and blog websites.
Version history
Version 1.0.0.8 2026-10-01
Browse filesFiles changed11
Lines added+51
Lines removed-8
assets/site.css
Not shown (binary file or too large to diff).
category.php
⋮ 58 unchanged lines ⋮ $pageDescription = $category['description'] ?? ''; $showBanner = false; $pageType = 'category'; + $rssFeedUrl = $settings['sitemap_rss_enabled'] === '1' && $settings['rss_category_feeds_enabled'] === '1' + ? 'rss.php?category=' . rawurlencode((string) $category['slug']) + : null; require __DIR__ . '/includes/front-header.php'; ⋮ 1 unchanged line ⋮ <h1 class="category-title"><?= htmlspecialchars($category['name']) ?></h1> <?php if (!empty($category['description'])): ?> <p class="category-description"><?= htmlspecialchars($category['description']) ?></p> + <?php endif; ?> + <?php if ($rssFeedUrl !== null): ?> + <a href="<?= htmlspecialchars($rssFeedUrl, ENT_QUOTES) ?>" class="category-rss-link"> + <svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/></svg> + <?= htmlspecialchars(Language::get('category_rss', 'RSS feed for this category')) ?> + </a> <?php endif; ?> <?= SiteFront::renderAds('category-top') ?> ⋮ 21 unchanged lines ⋮
dashboard-actions/save-settings.php
⋮ 116 unchanged lines ⋮ 'tags_enabled' => isset($_POST['tags_enabled']) ? '1' : '0', 'related_articles_enabled' => isset($_POST['related_articles_enabled']) ? '1' : '0', 'sitemap_rss_enabled' => isset($_POST['sitemap_rss_enabled']) ? '1' : '0', + 'rss_category_feeds_enabled' => isset($_POST['rss_category_feeds_enabled']) ? '1' : '0', 'article_loading_mode' => in_array($_POST['article_loading_mode'] ?? 'scroll', ['scroll', 'static'], true) ? $_POST['article_loading_mode'] : 'scroll', 'pagination_style' => in_array($_POST['pagination_style'] ?? 'numbered', ['numbered', 'load_more', 'infinite_scroll'], true) ? $_POST['pagination_style'] : 'numbered', 'articles_per_page_home' => (string) $articlesPerPageHome, ⋮ 118 unchanged lines ⋮
dashboard-views/profile.php
⋮ 72 unchanged lines ⋮ <option value="Person" <?= $schemaType === 'Person' ? 'selected' : '' ?>>A person (Person)</option> <option value="Organization" <?= $schemaType === 'Organization' ? 'selected' : '' ?>>An organization, e.g. an editorial team or agency (Organization)</option> </select> - <p style="font-size:12px;color:var(--muted);margin-top:6px;">Used in the article data Google reads. It changes right away, without review.</p> + <p style="font-size:12px;color:var(--muted);margin-top:6px;">Shown to search engines with your articles. It changes right away, without review.</p> <?php endif; ?> <p style="font-size:12px;color:var(--muted);margin-top:10px;">Your profile photo is set by an administrator in Team.</p> ⋮ 8 unchanged lines ⋮
includes/front-header.php
⋮ 36 unchanged lines ⋮ <?php if (!empty($pageSchema)): ?> <?= $pageSchema ?> <?php endif; ?> + <?php if (!empty($rssFeedUrl)): ?> + <link rel="alternate" type="application/rss+xml" title="<?= htmlspecialchars($resolvedPageTitle, ENT_QUOTES) ?>" href="<?= htmlspecialchars($rssFeedUrl, ENT_QUOTES) ?>"> + <?php endif; ?> <?= Asset::favicon() ?> <?php if (($settings['pwa_enabled'] ?? '0') === '1'): ?> <?php $pwaIconUrl = !empty($settings['pwa_icon_url']) ? $settings['pwa_icon_url'] : ($settings['site_logo_url'] ?? ''); ?> ⋮ 129 unchanged lines ⋮
includes/language.php
⋮ 99 unchanged lines ⋮ 'comments_coming_soon' => 'Comments are coming soon.', 'category_not_found' => 'We couldn’t find this category.', 'category_no_articles' => 'No articles in this category yet.', + 'category_rss' => 'RSS feed for this category', 'account_password_too_short' => 'Your new password needs at least 8 characters.', 'account_profile_saved' => 'Your profile is saved.', 'account_preferences_saved' => 'Your choices are saved.', ⋮ 99 unchanged lines ⋮
includes/site-front.php
⋮ 264 unchanged lines ⋮ 'indexnow_enabled' => '0', 'indexnow_key' => '', 'sitemap_rss_enabled' => '1', + 'rss_category_feeds_enabled' => '0', 'comments_enabled' => '0', 'comments_provider' => 'disqus', 'comments_shortname' => '', ⋮ 961 unchanged lines ⋮
rss.php
⋮ 15 unchanged lines ⋮ $db = Database::site(); $baseUrl = rtrim(Config::get('APP_URL', ''), '/') . Config::get('APP_BASE_PATH', ''); - $articles = $db->query( - "SELECT a.id, a.title, a.slug, a.excerpt, a.published_at FROM articles a WHERE a.status = 'published'" . Languages::articleFilterSql('a') . " ORDER BY a.published_at DESC LIMIT 30" - )->fetchAll(); - $articles = Languages::articles($articles); + $category = null; + if (isset($_GET['category'])) { + if ($settings['rss_category_feeds_enabled'] !== '1') { + http_response_code(404); + exit; + } + $categoryStatement = $db->prepare('SELECT * FROM categories WHERE slug = :slug LIMIT 1'); + $categoryStatement->execute(['slug' => trim((string) $_GET['category'])]); + $category = $categoryStatement->fetch() ?: null; + if ($category === null) { + http_response_code(404); + exit; + } + $category = Languages::categories([$category])[0]; + } + + $articlesStatement = $db->prepare( + "SELECT a.id, a.title, a.slug, a.excerpt, a.published_at, a.category_id, c.name AS category_name FROM articles a LEFT JOIN categories c ON c.id = a.category_id WHERE a.status = 'published'" + . ($category !== null ? ' AND a.category_id = :category_id' : '') + . Languages::articleFilterSql('a') . " ORDER BY a.published_at DESC LIMIT 30" + ); + $articlesStatement->execute($category !== null ? ['category_id' => (int) $category['id']] : []); + $articles = Languages::articles($articlesStatement->fetchAll()); + header('Content-Type: application/rss+xml; charset=utf-8'); echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; echo '<rss version="2.0"><channel>' . "\n"; - echo '<title>' . htmlspecialchars($settings['site_name']) . '</title>' . "\n"; + echo '<title>' . htmlspecialchars($category !== null ? $category['name'] . ' - ' . $settings['site_name'] : $settings['site_name']) . '</title>' . "\n"; $channelLanguage = Languages::current(); $channelLink = $channelLanguage === '' ? rtrim((string) Config::get('APP_URL', ''), '/') . SiteFront::homeUrl('') : rtrim((string) Config::get('APP_URL', ''), '/') . Languages::homeUrl($channelLanguage); + if ($category !== null) { + $channelLink = rtrim((string) Config::get('APP_URL', ''), '/') . rtrim((string) Config::get('APP_BASE_PATH', ''), '/') . Languages::prefix() . '/category.php?slug=' . rawurlencode((string) $category['slug']); + } echo '<link>' . htmlspecialchars($channelLink) . '</link>' . "\n"; if ($channelLanguage !== '') { echo '<language>' . htmlspecialchars($channelLanguage) . '</language>' . "\n"; } - echo '<description>' . htmlspecialchars($settings['site_description']) . '</description>' . "\n"; + $channelDescription = $category !== null && !empty($category['description']) ? (string) $category['description'] : $settings['site_description']; + echo '<description>' . htmlspecialchars($channelDescription) . '</description>' . "\n"; foreach ($articles as $article) { $url = rtrim(Config::get('APP_URL', ''), '/') . SiteFront::articleUrl($article['slug']); ⋮ 2 unchanged lines ⋮ echo '<link>' . htmlspecialchars($url) . '</link>'; echo '<guid>' . htmlspecialchars($url) . '</guid>'; echo '<description>' . htmlspecialchars((string) ($article['excerpt'] ?? '')) . '</description>'; + if (!empty($article['category_name'])) { + echo '<category>' . htmlspecialchars((string) $article['category_name']) . '</category>'; + } echo '<pubDate>' . htmlspecialchars(date(DATE_RSS, strtotime($article['published_at']))) . '</pubDate>'; echo '</item>' . "\n"; } ⋮ 3 unchanged lines ⋮
settings.php
Not shown (binary file or too large to diff).
translations/language.php
⋮ 140 unchanged lines ⋮ 'comments_coming_soon' => 'Comments are coming soon.', 'category_not_found' => 'We couldn’t find this category.', 'category_no_articles' => 'No articles in this category yet.', + 'category_rss' => 'RSS feed for this category', 'account_password_too_short' => 'Your new password needs at least 8 characters.', 'account_profile_saved' => 'Your profile is saved.', 'account_preferences_saved' => 'Your choices are saved.', ⋮ 65 unchanged lines ⋮
version.txt
- 1.0.0.6 + 1.0.0.8