v1.0.0.8
Publisium
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/../includes/font-library.php';
- require_once __DIR__ . '/../includes/activity-log.php';
- require_once __DIR__ . '/../includes/currency.php';
-
- $db = Database::site();
-
- function saveSettingsFields(PDO $db, array $fields): void
- {
- $db->exec(
- 'CREATE TABLE IF NOT EXISTS site_settings (
- setting_key VARCHAR(120) NOT NULL PRIMARY KEY,
- setting_value LONGTEXT NULL,
- updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
- );
-
- $statement = $db->prepare(
- 'INSERT INTO site_settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)'
- );
-
- foreach ($fields as $key => $value) {
- $statement->execute(['key' => $key, 'value' => $value]);
- }
- }
-
- function sanitizeLogoUrl(string $url): string
- {
- $url = trim($url);
-
- if ($url === '') {
- return '';
- }
-
- if (str_starts_with($url, 'https://') || str_starts_with($url, 'http://')) {
- return (string) (filter_var($url, FILTER_SANITIZE_URL) ?: '');
- }
-
- if (preg_match('#^[a-zA-Z][a-zA-Z0-9+.-]*:#', $url) === 1) {
- return '';
- }
-
- if (str_starts_with($url, '//')) {
- return '';
- }
-
- $url = ltrim($url, '/');
-
- return (string) (filter_var($url, FILTER_SANITIZE_URL) ?: '');
- }
-
- $settingsTab = (string) ($_POST['settings_tab'] ?? 'general');
-
- if ($settingsTab === 'general') {
- saveSettingsFields($db, [
- 'site_name' => trim((string) ($_POST['site_name'] ?? '')),
- 'site_description' => trim((string) ($_POST['site_description'] ?? '')),
- 'site_logo_url' => sanitizeLogoUrl((string) ($_POST['site_logo_url'] ?? '')),
- 'site_show_name_with_logo' => isset($_POST['site_show_name_with_logo']) ? '1' : '0',
- 'site_short_name' => trim((string) ($_POST['site_short_name'] ?? '')),
- 'site_short_name_enabled' => isset($_POST['site_short_name_enabled']) ? '1' : '0',
- 'registration_enabled' => isset($_POST['registration_enabled']) ? '1' : '0',
- 'login_enabled' => isset($_POST['login_enabled']) ? '1' : '0',
- 'activity_log_enabled' => isset($_POST['activity_log_enabled']) ? '1' : '0',
- 'subscription_name' => trim((string) ($_POST['subscription_name'] ?? 'Full access')),
- 'subscription_price_cents' => Currency::toMinor((int) ($_POST['subscription_price_major'] ?? 0), (int) ($_POST['subscription_price_minor'] ?? 0), Currency::normalize((string) ($_POST['subscription_currency'] ?? ''))),
- 'subscription_interval' => in_array($_POST['subscription_interval'] ?? 'month', ['month', 'year', 'lifetime'], true) ? $_POST['subscription_interval'] : 'month',
- 'subscription_currency' => Currency::normalize((string) ($_POST['subscription_currency'] ?? '')),
- 'subscription_description' => trim((string) ($_POST['subscription_description'] ?? '')),
- 'subscription_checkout_url' => trim((string) ($_POST['subscription_checkout_url'] ?? '')),
- 'payment_provider' => in_array($_POST['payment_provider'] ?? 'stripe', ['stripe', 'polar'], true) ? $_POST['payment_provider'] : 'stripe',
- 'payment_webhook_secret' => trim((string) ($_POST['payment_webhook_secret'] ?? '')),
- ]);
-
- require_once __DIR__ . '/../includes/contact.php';
- $aiSaveError = ContactForm::saveFromRequest($db, $_POST);
- }
-
- if ($settingsTab === 'layout') {
- $headingFontInput = trim((string) ($_POST['heading_font'] ?? 'Plus Jakarta Sans'));
- $bodyFontInput = trim((string) ($_POST['body_font'] ?? 'PT Serif'));
- $interfaceFontInput = trim((string) ($_POST['interface_font'] ?? 'Plus Jakarta Sans'));
- $validHeadingFont = FontLibrary::findByDisplayName($headingFontInput) !== null ? $headingFontInput : 'Plus Jakarta Sans';
- $validBodyFont = FontLibrary::findByDisplayName($bodyFontInput) !== null ? $bodyFontInput : 'PT Serif';
- $validInterfaceFont = FontLibrary::findByDisplayName($interfaceFontInput) !== null ? $interfaceFontInput : 'Plus Jakarta Sans';
-
- $articlesPerPageHome = max(1, min(100, (int) ($_POST['articles_per_page_home'] ?? 12)));
- $articlesPerPageCategory = max(1, min(100, (int) ($_POST['articles_per_page_category'] ?? 12)));
-
- saveSettingsFields($db, [
- 'menu_position' => in_array($_POST['menu_position'] ?? 'top', ['top', 'bottom', 'side'], true) ? $_POST['menu_position'] : 'top',
- 'banner_enabled' => isset($_POST['banner_enabled']) ? '1' : '0',
- 'featured_banner_enabled' => isset($_POST['featured_banner_enabled']) ? '1' : '0',
- 'featured_banner_mode' => in_array($_POST['featured_banner_mode'] ?? 'static', ['static', 'automatic'], true) ? $_POST['featured_banner_mode'] : 'static',
- 'featured_banner_hide_duplicates' => isset($_POST['featured_banner_hide_duplicates']) ? '1' : '0',
- 'featured_banner_article_1' => max(0, (int) ($_POST['featured_banner_article_1'] ?? 0)),
- 'featured_banner_article_2' => max(0, (int) ($_POST['featured_banner_article_2'] ?? 0)),
- 'featured_banner_article_3' => max(0, (int) ($_POST['featured_banner_article_3'] ?? 0)),
- 'featured_banner_article_4' => max(0, (int) ($_POST['featured_banner_article_4'] ?? 0)),
- 'featured_banner_article_5' => max(0, (int) ($_POST['featured_banner_article_5'] ?? 0)),
- 'featured_banner_auto_window_value' => max(1, min(720, (int) ($_POST['featured_banner_auto_window_value'] ?? 24))),
- 'featured_banner_auto_window_unit' => in_array($_POST['featured_banner_auto_window_unit'] ?? 'hours', ['minutes', 'hours'], true) ? $_POST['featured_banner_auto_window_unit'] : 'hours',
- 'featured_banner_auto_category_mode' => in_array($_POST['featured_banner_auto_category_mode'] ?? 'mixed', ['mixed', 'one_per_category', 'no_limit'], true) ? $_POST['featured_banner_auto_category_mode'] : 'mixed',
- 'article_reading_time_enabled' => isset($_POST['article_reading_time_enabled']) ? '1' : '0',
- 'article_time_enabled' => isset($_POST['article_time_enabled']) ? '1' : '0',
- 'article_time_format' => ($_POST['article_time_format'] ?? '24') === '12' ? '12' : '24',
- 'back_to_top_enabled' => isset($_POST['back_to_top_enabled']) ? '1' : '0',
- 'back_to_top_progress_enabled' => isset($_POST['back_to_top_progress_enabled']) ? '1' : '0',
- 'toc_enabled' => isset($_POST['toc_enabled']) ? '1' : '0',
- 'breadcrumbs_enabled' => isset($_POST['breadcrumbs_enabled']) ? '1' : '0',
- 'author_box_enabled' => isset($_POST['author_box_enabled']) ? '1' : '0',
- 'account_consents_enabled' => isset($_POST['account_consents_enabled']) ? '1' : '0',
- 'author_pages_enabled' => isset($_POST['author_pages_enabled']) ? '1' : '0',
- '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,
- 'articles_per_page_category' => (string) $articlesPerPageCategory,
- 'heading_font' => $validHeadingFont,
- 'body_font' => $validBodyFont,
- 'interface_font' => $validInterfaceFont,
- 'share_enabled' => isset($_POST['share_enabled']) ? '1' : '0',
- 'share_facebook' => isset($_POST['share_facebook']) ? '1' : '0',
- 'share_twitter' => isset($_POST['share_twitter']) ? '1' : '0',
- 'share_linkedin' => isset($_POST['share_linkedin']) ? '1' : '0',
- 'share_whatsapp' => isset($_POST['share_whatsapp']) ? '1' : '0',
- 'share_telegram' => isset($_POST['share_telegram']) ? '1' : '0',
- 'share_email' => isset($_POST['share_email']) ? '1' : '0',
- 'share_copy_link' => isset($_POST['share_copy_link']) ? '1' : '0',
- 'adblock_notice_enabled' => isset($_POST['adblock_notice_enabled']) ? '1' : '0',
- 'adblock_notice_mode' => array_key_exists((string) ($_POST['adblock_notice_mode'] ?? ''), SiteFront::ADBLOCK_MODES) ? (string) $_POST['adblock_notice_mode'] : 'session',
- ]);
-
- require_once __DIR__ . '/../includes/read-aloud.php';
- ReadAloud::saveGlobalFields($db, $_POST);
- }
-
- if ($settingsTab === 'apps') {
- function sanitizeHexColor(string $color, string $fallback): string
- {
- $color = trim($color);
- return preg_match('/^#[0-9a-fA-F]{6}$/', $color) === 1 ? $color : $fallback;
- }
-
- saveSettingsFields($db, [
- 'pwa_enabled' => isset($_POST['pwa_enabled']) ? '1' : '0',
- 'pwa_name' => trim((string) ($_POST['pwa_name'] ?? '')),
- 'pwa_short_name' => trim((string) ($_POST['pwa_short_name'] ?? '')),
- 'pwa_description' => trim((string) ($_POST['pwa_description'] ?? '')),
- 'pwa_icon_url' => sanitizeLogoUrl((string) ($_POST['pwa_icon_url'] ?? '')),
- 'pwa_theme_color' => sanitizeHexColor((string) ($_POST['pwa_theme_color'] ?? '#ffffff'), '#ffffff'),
- 'pwa_background_color' => sanitizeHexColor((string) ($_POST['pwa_background_color'] ?? '#ffffff'), '#ffffff'),
- 'app_banner_enabled' => isset($_POST['app_banner_enabled']) ? '1' : '0',
- 'app_android_url' => trim((string) ($_POST['app_android_url'] ?? '')),
- 'app_ios_url' => trim((string) ($_POST['app_ios_url'] ?? '')),
- ]);
- }
-
- if ($settingsTab === 'search') {
- saveSettingsFields($db, [
- 'search_enabled' => isset($_POST['search_enabled']) ? '1' : '0',
- 'search_match_title' => isset($_POST['search_match_title']) ? '1' : '0',
- 'search_match_excerpt' => isset($_POST['search_match_excerpt']) ? '1' : '0',
- 'search_match_content' => isset($_POST['search_match_content']) ? '1' : '0',
- 'search_style' => ($_POST['search_style'] ?? 'icon') === 'field' ? 'field' : 'icon',
- 'search_show_in_nav' => isset($_POST['search_show_in_nav']) ? '1' : '0',
- 'search_show_in_footer' => isset($_POST['search_show_in_footer']) ? '1' : '0',
- ]);
- }
-
- if ($settingsTab === 'comments') {
- require_once __DIR__ . '/../includes/comments.php';
- saveSettingsFields($db, Comments::sanitizeSettings($_POST));
- }
-
- if ($settingsTab === 'custom-code') {
- saveSettingsFields($db, [
- 'custom_css' => (string) ($_POST['custom_css'] ?? ''),
- 'custom_js' => (string) ($_POST['custom_js'] ?? ''),
- 'custom_head_html' => (string) ($_POST['custom_head_html'] ?? ''),
- 'custom_head_html_index' => isset($_POST['custom_head_html_index']) ? '1' : '0',
- 'custom_head_html_article' => isset($_POST['custom_head_html_article']) ? '1' : '0',
- 'custom_head_html_category' => isset($_POST['custom_head_html_category']) ? '1' : '0',
- 'custom_head_html_pages' => isset($_POST['custom_head_html_pages']) ? '1' : '0',
- 'custom_body_html' => (string) ($_POST['custom_body_html'] ?? ''),
- 'custom_body_html_index' => isset($_POST['custom_body_html_index']) ? '1' : '0',
- 'custom_body_html_article' => isset($_POST['custom_body_html_article']) ? '1' : '0',
- 'custom_body_html_category' => isset($_POST['custom_body_html_category']) ? '1' : '0',
- 'custom_body_html_pages' => isset($_POST['custom_body_html_pages']) ? '1' : '0',
- ]);
- }
-
- if ($settingsTab === 'ai') {
- require_once __DIR__ . '/../includes/ai.php';
- $aiSaveError = Ai::saveFromRequest($db, $_POST);
- require_once __DIR__ . '/../includes/read-aloud.php';
- $readAloudSaveError = ReadAloud::saveProviderSettings($db, $_POST);
- $aiSaveError = $aiSaveError ?? $readAloudSaveError;
- }
-
- if ($settingsTab === 'seo') {
- $indexNowKeyStatement = $db->prepare('SELECT setting_value FROM site_settings WHERE setting_key = :key LIMIT 1');
- $indexNowKeyStatement->execute(['key' => 'indexnow_key']);
- $indexNowKey = (string) ($indexNowKeyStatement->fetchColumn() ?: '');
- if (isset($_POST['indexnow_enabled']) && $indexNowKey === '') {
- $indexNowKey = bin2hex(random_bytes(16));
- }
-
- saveSettingsFields($db, [
- 'indexnow_enabled' => isset($_POST['indexnow_enabled']) ? '1' : '0',
- 'indexnow_key' => $indexNowKey,
- 'seo_title' => trim((string) ($_POST['seo_title'] ?? '')),
- 'seo_description' => trim((string) ($_POST['seo_description'] ?? '')),
- 'seo_keywords' => trim((string) ($_POST['seo_keywords'] ?? '')),
- 'seo_author' => trim((string) ($_POST['seo_author'] ?? '')),
- 'seo_reply_to' => trim((string) ($_POST['seo_reply_to'] ?? '')),
- 'seo_application_name' => trim((string) ($_POST['seo_application_name'] ?? '')),
- 'seo_robots_index' => isset($_POST['seo_robots_index']) ? '1' : '0',
- 'seo_robots_follow' => isset($_POST['seo_robots_follow']) ? '1' : '0',
- 'seo_og_article_image_enabled' => isset($_POST['seo_og_article_image_enabled']) ? '1' : '0',
- 'seo_social_tags_enabled' => isset($_POST['seo_social_tags_enabled']) ? '1' : '0',
- 'seo_twitter_handle' => preg_match('/^@?([A-Za-z0-9_]{1,15})$/', trim((string) ($_POST['seo_twitter_handle'] ?? '')), $twitterHandle) === 1 ? '@' . $twitterHandle[1] : '',
- 'seo_article_schema_enabled' => isset($_POST['seo_article_schema_enabled']) ? '1' : '0',
- 'clean_article_urls' => isset($_POST['clean_article_urls']) ? '1' : '0',
- 'clean_page_urls' => isset($_POST['clean_page_urls']) ? '1' : '0',
- 'clean_author_urls' => isset($_POST['clean_author_urls']) ? '1' : '0',
- 'clean_home_url' => isset($_POST['clean_home_url']) ? '1' : '0',
- 'llms_txt_enabled' => isset($_POST['llms_txt_enabled']) ? '1' : '0',
- 'seo_preview_enabled' => isset($_POST['seo_preview_enabled']) ? '1' : '0',
- ]);
- }
-
- ActivityLog::record('settings.save', 'settings', null, $settingsTab);
- CleanUrls::sync();
-