v1.0.0.1
StocketBase
- <?php
-
- declare(strict_types=1);
-
- if (count(get_included_files()) === 1) {
- http_response_code(403);
- exit;
- }
-
- require_once __DIR__ . '/../includes/font-library.php';
- require_once __DIR__ . '/../includes/activity-log.php';
- require_once __DIR__ . '/../includes/currency.php';
- require_once __DIR__ . '/../includes/contact-form.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') {
- $contactTopicNames = is_array($_POST['contact_subject_name'] ?? null) ? array_values($_POST['contact_subject_name']) : [];
- $contactTopicEmails = is_array($_POST['contact_subject_email'] ?? null) ? array_values($_POST['contact_subject_email']) : [];
- $contactTopicRows = [];
- foreach ($contactTopicNames as $topicIndex => $topicName) {
- $contactTopicRows[] = ['name' => (string) $topicName, 'email' => (string) ($contactTopicEmails[$topicIndex] ?? '')];
- }
- $contactSubjectsJson = (string) json_encode(ContactForm::normalizeSubjectEntries($contactTopicRows), JSON_UNESCAPED_UNICODE);
-
- 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',
- 'require_account_to_purchase' => isset($_POST['require_account_to_purchase']) ? '1' : '0',
- 'download_link_expiry_days' => (string) max(0, min(3650, (int) ($_POST['download_link_expiry_days'] ?? 30))),
- 'download_link_max_downloads' => (string) max(0, min(1000, (int) ($_POST['download_link_max_downloads'] ?? 10))),
- 'contact_enabled' => isset($_POST['contact_enabled']) ? '1' : '0',
- 'contact_recipient_email' => filter_var(trim((string) ($_POST['contact_recipient_email'] ?? '')), FILTER_VALIDATE_EMAIL) ?: '',
- 'contact_provider' => in_array($_POST['contact_provider'] ?? 'php_mail', ContactForm::PROVIDERS, true) ? $_POST['contact_provider'] : 'php_mail',
- 'contact_subject_mode' => in_array($_POST['contact_subject_mode'] ?? 'free', ['free', 'list_other', 'list_only'], true) ? $_POST['contact_subject_mode'] : 'free',
- 'contact_subjects' => $contactSubjectsJson,
- 'contact_from_email' => filter_var(trim((string) ($_POST['contact_from_email'] ?? '')), FILTER_VALIDATE_EMAIL) ?: '',
- 'contact_resend_api_key' => trim((string) ($_POST['contact_resend_api_key'] ?? '')),
- 'contact_postmark_token' => trim((string) ($_POST['contact_postmark_token'] ?? '')),
- 'activity_log_enabled' => isset($_POST['activity_log_enabled']) ? '1' : '0',
- 'store_category' => in_array($_POST['store_category'] ?? 'general', ['general', 'saas'], true) ? $_POST['store_category'] : 'general',
- 'pricing_model' => in_array($_POST['pricing_model'] ?? 'per_product', ['per_product', 'subscription'], true) ? $_POST['pricing_model'] : 'per_product',
- 'subscription_interval' => in_array($_POST['subscription_interval'] ?? 'month', ['month', 'year', 'lifetime'], true) ? $_POST['subscription_interval'] : 'month',
- 'store_currency' => Currency::normalize((string) ($_POST['store_currency'] ?? '')),
- 'payment_provider' => in_array($_POST['payment_provider'] ?? 'stripe', ['stripe', 'polar'], true) ? $_POST['payment_provider'] : 'stripe',
- 'payment_webhook_secret' => trim((string) ($_POST['payment_webhook_secret'] ?? '')),
- 'payment_polar_webhook_secret' => trim((string) ($_POST['payment_polar_webhook_secret'] ?? '')),
- 'payment_stripe_secret_key' => trim((string) ($_POST['payment_stripe_secret_key'] ?? '')),
- 'payment_stripe_publishable_key' => trim((string) ($_POST['payment_stripe_publishable_key'] ?? '')),
- 'payment_polar_access_token' => trim((string) ($_POST['payment_polar_access_token'] ?? '')),
- 'payment_polar_organization_id' => trim((string) ($_POST['payment_polar_organization_id'] ?? '')),
- ]);
-
- if (($_POST['store_category'] ?? 'general') !== 'saas' && ($_POST['payment_provider'] ?? 'stripe') === 'polar') {
- saveSettingsFields($db, ['payment_provider' => 'stripe']);
- }
- }
-
- if ($settingsTab === 'shop_icon') {
- $shopIconChoiceInput = (string) ($_POST['shop_icon_choice'] ?? 'cart');
- $shopIconPresetKeys = ['cart', 'cart-basket2', 'cart-trolley', 'cart-bag', 'cart-tote', 'cart-box', 'cart-wallet', 'cart-tag', 'cart-storefront', 'cart-percent'];
- $shopIconChoiceValid = in_array($shopIconChoiceInput, $shopIconPresetKeys, true) || $shopIconChoiceInput === 'custom' ? $shopIconChoiceInput : 'cart';
- $shopIconCustomSvg = $shopIconChoiceValid === 'custom'
- ? SocialIcons::sanitizeCustomSvg(mb_substr((string) ($_POST['shop_icon_custom_svg'] ?? ''), 0, 20000))
- : '';
- if ($shopIconChoiceValid === 'custom' && $shopIconCustomSvg === '') {
- $shopIconChoiceValid = 'cart';
- }
-
- saveSettingsFields($db, [
- 'shop_icon_choice' => $shopIconChoiceValid,
- 'shop_icon_custom_svg' => $shopIconCustomSvg,
- ]);
- }
-
- 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';
-
- $productsPerPageHome = max(1, min(100, (int) ($_POST['products_per_page_home'] ?? 12)));
- $productsPerPageCategory = max(1, min(100, (int) ($_POST['products_per_page_category'] ?? 12)));
-
- saveSettingsFields($db, [
- 'menu_position' => in_array($_POST['menu_position'] ?? 'top', ['top', 'bottom', 'side'], true) ? $_POST['menu_position'] : 'top',
- '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_product_1' => max(0, (int) ($_POST['featured_banner_product_1'] ?? 0)),
- 'featured_banner_product_2' => max(0, (int) ($_POST['featured_banner_product_2'] ?? 0)),
- 'featured_banner_product_3' => max(0, (int) ($_POST['featured_banner_product_3'] ?? 0)),
- 'featured_banner_product_4' => max(0, (int) ($_POST['featured_banner_product_4'] ?? 0)),
- 'featured_banner_product_5' => max(0, (int) ($_POST['featured_banner_product_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',
- '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',
- 'breadcrumbs_enabled' => isset($_POST['breadcrumbs_enabled']) ? '1' : '0',
- 'tags_enabled' => isset($_POST['tags_enabled']) ? '1' : '0',
- 'related_products_enabled' => isset($_POST['related_products_enabled']) ? '1' : '0',
- 'product_show_buyers' => isset($_POST['product_show_buyers']) ? '1' : '0',
- 'product_show_downloads' => isset($_POST['product_show_downloads']) ? '1' : '0',
- 'sitemap_rss_enabled' => isset($_POST['sitemap_rss_enabled']) ? '1' : '0',
- 'product_loading_mode' => in_array($_POST['product_loading_mode'] ?? 'scroll', ['scroll', 'static'], true) ? $_POST['product_loading_mode'] : 'scroll',
- 'pagination_style' => in_array($_POST['pagination_style'] ?? 'numbered', ['numbered', 'load_more', 'infinite_scroll'], true) ? $_POST['pagination_style'] : 'numbered',
- 'products_per_page_home' => (string) $productsPerPageHome,
- 'products_per_page_category' => (string) $productsPerPageCategory,
- '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',
- ]);
- }
-
- 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 === 'reviews') {
- saveSettingsFields($db, [
- 'reviews_enabled' => isset($_POST['reviews_enabled']) ? '1' : '0',
- ]);
- }
-
- 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_product' => isset($_POST['custom_head_html_product']) ? '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_product' => isset($_POST['custom_body_html_product']) ? '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 === 'email') {
- saveSettingsFields($db, [
- 'mail_transport' => in_array($_POST['mail_transport'] ?? 'smtp', ['smtp', 'resend', 'postmark'], true) ? $_POST['mail_transport'] : 'smtp',
- 'mail_smtp_host' => trim((string) ($_POST['mail_smtp_host'] ?? '')),
- 'mail_smtp_port' => (string) max(1, min(65535, (int) ($_POST['mail_smtp_port'] ?? 587))),
- 'mail_smtp_username' => trim((string) ($_POST['mail_smtp_username'] ?? '')),
- 'mail_smtp_password' => (string) ($_POST['mail_smtp_password'] ?? ''),
- 'mail_smtp_encryption' => in_array($_POST['mail_smtp_encryption'] ?? 'tls', ['tls', 'ssl', 'none'], true) ? $_POST['mail_smtp_encryption'] : 'tls',
- 'mail_resend_api_key' => trim((string) ($_POST['mail_resend_api_key'] ?? '')),
- 'mail_postmark_server_token' => trim((string) ($_POST['mail_postmark_server_token'] ?? '')),
- 'mail_from_email' => trim((string) ($_POST['mail_from_email'] ?? '')),
- 'mail_from_name' => trim((string) ($_POST['mail_from_name'] ?? '')),
- 'mail_reply_to' => trim((string) ($_POST['mail_reply_to'] ?? '')),
- ]);
- }
-
- if ($settingsTab === 'ai') {
- require_once __DIR__ . '/../includes/ai.php';
- $aiSaveError = Ai::saveFromRequest($db, $_POST);
- }
-
- 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_product_image_enabled' => isset($_POST['seo_og_product_image_enabled']) ? '1' : '0',
- 'clean_product_urls' => isset($_POST['clean_product_urls']) ? '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);
-