array ( 'app_name' => 'CargoLink', 'nav_home' => 'Overview', 'nav_shipper' => 'Shipper Desk', 'nav_owner' => 'Truck Owner Desk', 'nav_admin' => 'Admin Panel', 'hero_title' => 'Move cargo faster with verified trucks.', 'hero_subtitle' => 'Post shipments, collect offers, and pay via Thawani or bank transfer. Built for local and nearby cross-border moves.', 'hero_tagline' => 'Multilingual Logistics Marketplace', 'register_shipper' => 'Register as Shipper', 'register_owner' => 'Register as Truck Owner', 'cta_shipper' => 'Post a shipment', 'cta_owner' => 'Find loads', 'cta_admin' => 'Open admin', 'stats_shipments' => 'Shipments posted', 'stats_offers' => 'Active offers', 'stats_confirmed' => 'Confirmed trips', 'section_workflow' => 'How it works', 'recent_shipments' => 'Recent shipments', 'step_post' => 'Shipper posts cargo details and preferred payment.', 'step_offer' => 'Truck owners respond with their best rate.', 'step_confirm' => 'Admin confirms booking and status.', 'step_confirm_desc' => 'Secure booking and track the delivery until completion.', 'shipper_dashboard' => 'Shipper Dashboard', 'new_shipment' => 'Create shipment', 'shipper_name' => 'Shipper name', 'shipper_company' => 'Company', 'origin' => 'Origin city', 'destination' => 'Destination city', 'cargo' => 'Cargo description', 'cargo_placeholder' => 'e.g. 20 Pallets of Electronics', 'weight' => 'Weight (tons)', 'pickup_date' => 'Pickup date', 'delivery_date' => 'Delivery date', 'payment_method' => 'Payment method', 'payment_thawani' => 'Thawani online payment', 'payment_bank' => 'Bank transfer', 'submit_shipment' => 'Submit shipment', 'shipments_list' => 'Your latest shipments', 'status' => 'Status', 'offer' => 'Best offer', 'actions' => 'Actions', 'view' => 'View', 'owner_dashboard' => 'Truck Owner Dashboard', 'available_shipments' => 'Available shipments', 'offer_price' => 'Offer price', 'offer_owner' => 'Truck owner name', 'submit_offer' => 'Send offer', 'admin_dashboard' => 'Admin Dashboard', 'update_status' => 'Update status', 'save' => 'Save', 'shipment_detail' => 'Shipment detail', 'created_at' => 'Created', 'best_offer' => 'Best offer', 'assign_owner' => 'Assigned owner', 'no_shipments' => 'No shipments yet. Create the first one to get started.', 'no_offers' => 'No offers yet.', 'success_shipment' => 'Shipment posted successfully.', 'success_offer' => 'Offer submitted to the shipper.', 'success_status' => 'Status updated.', 'error_required' => 'Please fill in all required fields.', 'error_invalid' => 'Please enter valid values.', 'status_posted' => 'Posted', 'status_offered' => 'Offered', 'status_confirmed' => 'Confirmed', 'status_in_transit' => 'In transit', 'status_delivered' => 'Delivered', 'footer_note' => 'This is the initial MVP slice. Payments are not yet connected.', 'marketing_title_1' => 'For Shippers', 'marketing_desc_1' => 'Find the right truck for your cargo quickly and securely. Post your load and get offers instantly.', 'marketing_title_2' => 'For Truck Owners', 'marketing_desc_2' => 'Maximize your earnings and eliminate empty miles. Browse available shipments and offer your rate.', 'motivation_phrase' => 'Empowering the logistics of tomorrow.', 'why_choose_us' => 'Why Choose CargoLink?', 'feature_1_title' => 'Fast Matching', 'feature_1_desc' => 'Connect with available trucks or shipments in minutes.', 'feature_2_title' => 'Secure Payments', 'feature_2_desc' => 'Your transactions are protected with security.', 'feature_3_title' => 'Verified Users', 'feature_3_desc' => 'We verify all truck owners to ensure peace of mind.', 'view_faq' => 'View FAQ', 'faq_title' => 'Have Questions?', 'faq_subtitle' => 'Check out our Frequently Asked Questions to learn more about how our platform works.', 'motivation_title' => 'Ready to transform your logistics?', 'motivation_subtitle' => 'Join our platform today to find reliable trucks or secure the best shipments in the market.', 'company' => 'Company', 'about_us' => 'About Us', 'careers' => 'Careers', 'contact' => 'Contact', 'resources' => 'Resources', 'help_center' => 'Help Center / FAQ', 'terms_of_service' => 'Terms of Service', 'privacy_policy' => 'Privacy Policy', 'language' => 'Language', 'all_rights_reserved' => 'All rights reserved.', 'dashboard' => 'Dashboard', 'settings' => 'Settings', 'company_setting' => 'Company Setting', 'integrations' => 'Integrations', 'locations' => 'Locations', 'countries' => 'Countries', 'cities' => 'Cities', 'users' => 'Users', 'shippers' => 'Shippers', 'truck_owners' => 'Truck Owners', 'user_registration' => 'User Registration', 'pages' => 'Pages', 'faqs' => 'FAQs', 'landing_pages' => 'Landing Pages', 'login_title' => 'Welcome Back', 'login_subtitle' => 'Sign in to your account to continue', 'email_address' => 'Email address', 'email_placeholder' => 'name@example.com', 'password' => 'Password', 'forgot_password' => 'Forgot password?', 'password_placeholder' => 'Enter your password', 'change_password' => 'Change Password', 'new_password' => 'New Password', 'confirm_password' => 'Confirm Password', 'passwords_do_not_match' => 'Passwords do not match.', 'password_too_short' => 'Password must be at least 6 characters.', 'password_updated' => 'Password updated successfully.', 'sign_in' => 'Sign In', 'dont_have_account' => 'Don\'t have an account?', 'register_now' => 'Register now', 'reset_password_title' => 'Reset Password', 'reset_password_subtitle' => 'Enter your email and we\'ll send you a link to reset your password', 'send_reset_link' => 'Send Reset Link', 'back_to_login' => 'Back to login', 'invalid_image' => 'Invalid image format. Please upload JPG, PNG, GIF, or WEBP.', 'upload_failed' => 'Failed to save uploaded file.', 'profile_updated' => 'Profile updated successfully.', 'my_profile' => 'My Profile', 'profile_picture' => 'Profile Picture', 'change_picture' => 'Change Picture', 'picture_hint' => 'JPG, PNG, or GIF up to 5MB', 'full_name' => 'Full Name', 'email_hint' => 'Email address cannot be changed.', 'account_role' => 'Account Role', 'save_changes' => 'Save Changes', 'reg_title' => 'Create your logistics account', 'reg_subtitle' => 'Shippers and truck owners can self-register with full profile details.', 'reg_success_pending' => 'Registration completed successfully. Your account is pending admin approval.', 'reg_success' => 'Registration completed successfully.', 'role' => 'Role', 'shipper' => 'Shipper', 'truck_owner' => 'Truck Owner', 'email' => 'Email', 'phone' => 'Phone', 'country' => 'Country', 'select_country' => 'Select country', 'city' => 'City', 'select_city' => 'Select city', 'address' => 'Address', 'shipper_details' => 'Shipper details', 'company_name' => 'Company name', 'truck_details' => 'Truck owner details', 'truck_type' => 'Truck type', 'load_capacity' => 'Load capacity (tons)', 'plate_no' => 'Plate number', 'bank_account' => 'Bank Account / IBAN', 'bank_name' => 'Bank Name', 'bank_branch' => 'Bank Branch', 'id_card_front' => 'ID card (Front Face)', 'id_card_back' => 'ID card (Back Face)', 'truck_reg_front' => 'Truck Registration (Front Face)', 'truck_reg_back' => 'Truck Registration (Back Face)', 'truck_picture' => 'Clear Truck Photo (showing plate number)', 'create_account' => 'Create account', 'back_to_admin' => 'Back to admin', 'welcome_back' => 'Welcome to your dashboard. Manage your cargo shipments here.', 'total_shipments_posted' => 'Total Shipments', 'active_shipments' => 'Active Shipments', 'delivered_shipments' => 'Delivered Shipments', 'route_label' => 'Route', 'total_label' => 'total', 'welcome_back_owner' => 'Find loads and submit your best rate.', 'total_offers' => 'Total Offers', 'won_shipments' => 'Won Shipments', 'nav_platform_users' => 'Platform Users', 'notification_templates' => 'Notification Templates', 'manage_permissions' => 'Manage Permissions', 'create_user' => 'Create User', 'edit_user' => 'Edit User', 'delete_user' => 'Delete User', 'confirm_delete' => 'Are you sure you want to delete this user?', 'permissions' => 'Permissions', 'no_users' => 'No platform users found.', 'user_created' => 'User created successfully.', 'user_updated' => 'User updated successfully.', 'user_deleted' => 'User deleted successfully.', 'error_email_exists' => 'Email already exists.', 'shipper_shipments' => 'Shipper Shipments', 'truck_owners_statements' => 'Truck Owner Statements' ), "ar" => array ( 'app_name' => 'CargoLink', 'nav_home' => 'نظرة عامة', 'nav_shipper' => 'لوحة الشاحن', 'nav_owner' => 'لوحة مالك الشاحنة', 'nav_admin' => 'لوحة الإدارة', 'hero_title' => 'انقل شحنتك بسرعة مع شاحنات موثوقة.', 'hero_subtitle' => 'أنشئ شحنة، استلم عروضاً، وادفع عبر ثواني أو التحويل البنكي.', 'hero_tagline' => 'منصة لوجستية متعددة اللغات', 'register_shipper' => 'التسجيل كشاحن', 'register_owner' => 'التسجيل كمالك شاحنة', 'cta_shipper' => 'إنشاء شحنة', 'cta_owner' => 'البحث عن الشحنات', 'cta_admin' => 'الدخول للإدارة', 'stats_shipments' => 'الشحنات المنشورة', 'stats_offers' => 'العروض الحالية', 'stats_confirmed' => 'الرحلات المؤكدة', 'section_workflow' => 'طريقة العمل', 'recent_shipments' => 'أحدث الشحنات', 'step_post' => 'يقوم الشاحن بإدخال تفاصيل الشحنة وطريقة الدفع.', 'step_offer' => 'يرسل أصحاب الشاحنات أفضل عروضهم.', 'step_confirm' => 'تؤكد الإدارة الحجز وتحدث الحالة.', 'step_confirm_desc' => 'حجز آمن وتتبع التسليم حتى الانتهاء.', 'shipper_dashboard' => 'لوحة الشاحن', 'new_shipment' => 'إنشاء شحنة', 'shipper_name' => 'اسم الشاحن', 'shipper_company' => 'الشركة', 'origin' => 'مدينة الانطلاق', 'destination' => 'مدينة الوصول', 'cargo' => 'وصف الحمولة', 'cargo_placeholder' => 'مثال: 20 منصة إلكترونيات', 'weight' => 'الوزن (طن)', 'pickup_date' => 'تاريخ الاستلام', 'delivery_date' => 'تاريخ التسليم', 'payment_method' => 'طريقة الدفع', 'payment_thawani' => 'الدفع الإلكتروني عبر ثواني', 'payment_bank' => 'تحويل بنكي', 'submit_shipment' => 'إرسال الشحنة', 'shipments_list' => 'أحدث الشحنات', 'status' => 'الحالة', 'offer' => 'أفضل عرض', 'actions' => 'إجراءات', 'view' => 'عرض', 'owner_dashboard' => 'لوحة مالك الشاحنة', 'available_shipments' => 'الشحنات المتاحة', 'offer_price' => 'سعر العرض', 'offer_owner' => 'اسم مالك الشاحنة', 'submit_offer' => 'إرسال العرض', 'admin_dashboard' => 'لوحة الإدارة', 'update_status' => 'تحديث الحالة', 'save' => 'حفظ', 'shipment_detail' => 'تفاصيل الشحنة', 'created_at' => 'تم الإنشاء', 'best_offer' => 'أفضل عرض', 'assign_owner' => 'المالك المعتمد', 'no_shipments' => 'لا توجد شحنات بعد. ابدأ بإنشاء أول شحنة.', 'no_offers' => 'لا توجد عروض بعد.', 'success_shipment' => 'تم نشر الشحنة بنجاح.', 'success_offer' => 'تم إرسال العرض إلى الشاحن.', 'success_status' => 'تم تحديث الحالة.', 'error_required' => 'يرجى تعبئة جميع الحقول المطلوبة.', 'error_invalid' => 'يرجى إدخال قيم صحيحة.', 'status_posted' => 'منشورة', 'status_offered' => 'بعرض', 'status_confirmed' => 'مؤكدة', 'status_in_transit' => 'قيد النقل', 'status_delivered' => 'تم التسليم', 'footer_note' => 'هذه هي النسخة الأولية. الدفع غير متصل بعد.', 'marketing_title_1' => 'للشاحنين', 'marketing_desc_1' => 'ابحث عن الشاحنة المناسبة لحمولتك بسرعة وأمان.', 'marketing_title_2' => 'لأصحاب الشاحنات', 'marketing_desc_2' => 'عظّم أرباحك وتجنب العودة فارغاً.', 'motivation_phrase' => 'تمكين الخدمات اللوجستية للمستقبل.', 'why_choose_us' => 'لماذا تختار كارجو لينك؟', 'feature_1_title' => 'مطابقة سريعة', 'feature_1_desc' => 'تواصل مع الشاحنات المتاحة في دقائق.', 'feature_2_title' => 'مدفوعات آمنة', 'feature_2_desc' => 'معاملاتك محمية بأعلى معايير الأمان.', 'feature_3_title' => 'مستخدمون موثوقون', 'feature_3_desc' => 'نقوم بالتحقق من جميع أصحاب الشاحنات لضمان راحتك.', 'view_faq' => 'عرض الأسئلة الشائعة', 'faq_title' => 'لديك أسئلة؟', 'faq_subtitle' => 'اطلع على الأسئلة الشائعة لمعرفة المزيد حول كيفية عمل منصتنا.', 'motivation_title' => 'هل أنت مستعد لتحويل خدماتك اللوجستية؟', 'motivation_subtitle' => 'انضم إلى منصتنا اليوم للعثور على شاحنات موثوقة أو تأمين أفضل الشحنات في السوق.', 'company' => 'الشركة', 'about_us' => 'معلومات عنا', 'careers' => 'الوظائف', 'contact' => 'اتصل بنا', 'resources' => 'الموارد', 'help_center' => 'مركز المساعدة / الأسئلة الشائعة', 'terms_of_service' => 'شروط الخدمة', 'privacy_policy' => 'سياسة الخصوصية', 'language' => 'اللغة', 'all_rights_reserved' => 'جميع الحقوق محفوظة.', 'dashboard' => 'لوحة القيادة', 'settings' => 'الإعدادات', 'company_setting' => 'إعدادات الشركة', 'integrations' => 'التكاملات', 'locations' => 'المواقع', 'countries' => 'البلدان', 'cities' => 'المدن', 'users' => 'المستخدمون', 'shippers' => 'الشاحنون', 'truck_owners' => 'أصحاب الشاحنات', 'user_registration' => 'تسجيل المستخدم', 'pages' => 'الصفحات', 'faqs' => 'الأسئلة الشائعة', 'landing_pages' => 'إعدادات الصفحة الرئيسية', 'login_title' => 'مرحبًا بعودتك', 'login_subtitle' => 'قم بتسجيل الدخول إلى حسابك للمتابعة', 'email_address' => 'البريد الإلكتروني', 'email_placeholder' => 'name@example.com', 'password' => 'كلمة المرور', 'forgot_password' => 'هل نسيت كلمة المرور؟', 'password_placeholder' => 'أدخل كلمة المرور', 'change_password' => 'تغيير كلمة المرور', 'new_password' => 'كلمة المرور الجديدة', 'confirm_password' => 'تأكيد كلمة المرور', 'passwords_do_not_match' => 'كلمات المرور غير متطابقة.', 'password_too_short' => 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل.', 'password_updated' => 'تم تحديث كلمة المرور بنجاح.', 'sign_in' => 'تسجيل الدخول', 'dont_have_account' => 'ليس لديك حساب؟', 'register_now' => 'سجل الآن', 'reset_password_title' => 'إعادة تعيين كلمة المرور', 'reset_password_subtitle' => 'أدخل بريدك الإلكتروني وسنرسل لك رابطًا لإعادة تعيين كلمة المرور', 'send_reset_link' => 'إرسال رابط إعادة التعيين', 'back_to_login' => 'العودة لتسجيل الدخول', 'invalid_image' => 'صيغة صورة غير صالحة. يرجى تحميل JPG أو PNG أو GIF أو WEBP.', 'upload_failed' => 'فشل في حفظ الملف المحمل.', 'profile_updated' => 'تم تحديث الملف الشخصي بنجاح.', 'my_profile' => 'ملفي الشخصي', 'profile_picture' => 'صورة الملف الشخصي', 'change_picture' => 'تغيير الصورة', 'picture_hint' => 'JPG، PNG، أو GIF حتى 5 ميغابايت', 'full_name' => 'الاسم الكامل', 'email_hint' => 'لا يمكن تغيير عنوان البريد الإلكتروني.', 'account_role' => 'دور الحساب', 'save_changes' => 'حفظ التغييرات', 'reg_title' => 'أنشئ حسابك اللوجستي', 'reg_subtitle' => 'يمكن للشاحنين وأصحاب الشاحنات التسجيل الذاتي ببيانات الملف الشخصي الكاملة.', 'reg_success_pending' => 'اكتمل التسجيل بنجاح. حسابك في انتظار موافقة الإدارة.', 'reg_success' => 'اكتمل التسجيل بنجاح.', 'role' => 'الدور', 'shipper' => 'شاحن', 'truck_owner' => 'مالك شاحنة', 'email' => 'البريد الإلكتروني', 'phone' => 'الهاتف', 'country' => 'البلد', 'select_country' => 'اختر البلد', 'city' => 'المدينة', 'select_city' => 'اختر المدينة', 'address' => 'العنوان', 'shipper_details' => 'تفاصيل الشاحن', 'company_name' => 'اسم الشركة', 'truck_details' => 'تفاصيل مالك الشاحنة', 'truck_type' => 'نوع الشاحنة', 'load_capacity' => 'سعة الحمولة (طن)', 'plate_no' => 'رقم اللوحة', 'bank_account' => 'الحساب البنكي / الآيبان', 'bank_name' => 'اسم البنك', 'bank_branch' => 'فرع البنك', 'id_card_front' => 'البطاقة الشخصية (الوجه الأمامي)', 'id_card_back' => 'البطاقة الشخصية (الوجه الخلفي)', 'truck_reg_front' => 'تسجيل الشاحنة (الوجه الأمامي)', 'truck_reg_back' => 'تسجيل الشاحنة (الوجه الخلفي)', 'truck_picture' => 'صورة واضحة للشاحنة (تظهر رقم اللوحة)', 'create_account' => 'إنشاء حساب', 'back_to_admin' => 'العودة للإدارة', 'welcome_back' => 'مرحبًا بك في لوحة القيادة الخاصة بك. قم بإدارة شحناتك هنا.', 'total_shipments_posted' => 'إجمالي الشحنات', 'active_shipments' => 'الشحنات النشطة', 'delivered_shipments' => 'الشحنات المسلمة', 'route_label' => 'المسار', 'total_label' => 'المجموع', 'welcome_back_owner' => 'ابحث عن الأحمال وقدم أفضل سعر لديك.', 'total_offers' => 'إجمالي العروض', 'won_shipments' => 'الشحنات الفائزة', 'nav_platform_users' => 'مستخدمو المنصة', 'notification_templates' => 'قوالب الإشعارات', 'manage_permissions' => 'إدارة الصلاحيات', 'create_user' => 'إنشاء مستخدم', 'edit_user' => 'تعديل المستخدم', 'delete_user' => 'حذف المستخدم', 'confirm_delete' => 'هل أنت متأكد أنك تريد حذف هذا المستخدم؟', 'permissions' => 'الصلاحيات', 'no_users' => 'لم يتم العثور على مستخدمين.', 'user_created' => 'تم إنشاء المستخدم بنجاح.', 'user_updated' => 'تم تحديث المستخدم بنجاح.', 'user_deleted' => 'تم حذف المستخدم بنجاح.', 'error_email_exists' => 'البريد الإلكتروني موجود بالفعل.', 'shipper_shipments' => 'شحنات الشاحنين', 'truck_owners_statements' => 'كشوفات أصحاب الشاحنات' ) ]; function t(string $key): string { global $translations, $lang; return $translations[$lang][$key] ?? $key; } function e($value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function ensure_schema(): void { db()->exec("\n CREATE TABLE IF NOT EXISTS landing_sections ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, subtitle TEXT NULL, content TEXT NULL, image_path VARCHAR(255) NULL, layout ENUM('text_left', 'text_right', 'center') NOT NULL DEFAULT 'text_left', button_text VARCHAR(100) NULL, button_link VARCHAR(255) NULL, section_order INT NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); try { db()->exec("ALTER TABLE landing_sections ADD COLUMN title_ar VARCHAR(255) NULL AFTER title"); } catch (Exception $e) {} try { db()->exec("ALTER TABLE landing_sections ADD COLUMN subtitle_ar TEXT NULL AFTER subtitle"); } catch (Exception $e) {} try { db()->exec("ALTER TABLE landing_sections ADD COLUMN content_ar TEXT NULL AFTER content"); } catch (Exception $e) {} try { db()->exec("ALTER TABLE landing_sections ADD COLUMN button_text_ar VARCHAR(100) NULL AFTER button_text"); } catch (Exception $e) {} $sql = <<exec($sql); try { db()->exec("ALTER TABLE users ADD COLUMN status ENUM('pending','active','rejected') NOT NULL DEFAULT 'active'"); } catch (Exception $e) {} try { db()->exec("ALTER TABLE users ADD COLUMN profile_picture VARCHAR(255) NULL AFTER full_name"); } catch (Exception $e) {} // Payment fields for shipments try { db()->exec("ALTER TABLE shipments ADD COLUMN platform_fee DECIMAL(10,2) DEFAULT 0.00 AFTER offer_price"); } catch (Exception $e) {} try { db()->exec("ALTER TABLE shipments ADD COLUMN total_price DECIMAL(10,2) DEFAULT 0.00 AFTER platform_fee"); } catch (Exception $e) {} try { db()->exec("ALTER TABLE shipments ADD COLUMN payment_status ENUM('unpaid', 'paid') DEFAULT 'unpaid' AFTER status"); } catch (Exception $e) {} db()->exec( "CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, full_name VARCHAR(255) NOT NULL, role ENUM('admin','shipper','truck_owner') NOT NULL, status ENUM('pending','active','rejected') NOT NULL DEFAULT 'active', profile_picture VARCHAR(255) NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" ); db()->exec( "CREATE TABLE IF NOT EXISTS shipper_profiles ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL UNIQUE, company_name VARCHAR(255) NOT NULL, phone VARCHAR(40) NOT NULL, country_id INT NULL, city_id INT NULL, address_line VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_shipper_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" ); db()->exec( "CREATE TABLE IF NOT EXISTS truck_owner_profiles ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL UNIQUE, phone VARCHAR(40) NOT NULL, country_id INT NULL, city_id INT NULL, address_line VARCHAR(255) NOT NULL, truck_type VARCHAR(120) NOT NULL, load_capacity DECIMAL(10,2) NOT NULL, plate_no VARCHAR(80) NOT NULL, bank_account VARCHAR(100) NULL, bank_name VARCHAR(100) NULL, bank_branch VARCHAR(100) NULL, id_card_path TEXT NOT NULL, truck_pic_path VARCHAR(255) NOT NULL, registration_path TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_owner_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" ); } function set_flash(string $type, string $message): void { $_SESSION['flash'] = ['type' => $type, 'message' => $message]; } function get_flash(): ?array { if (!empty($_SESSION['flash'])) { $flash = $_SESSION['flash']; unset($_SESSION['flash']); return $flash; } return null; } function url_with_lang(string $path, array $params = []): string { global $lang; $params = array_merge(['lang' => $lang], $params); return $path . '?' . http_build_query($params); } function current_url_with_lang(string $newLang): string { $params = $_GET; $params['lang'] = $newLang; $path = basename($_SERVER['PHP_SELF'] ?? 'index.php'); return $path . '?' . http_build_query($params); } function status_label(string $status): string { $map = [ 'posted' => t('status_posted'), 'offered' => t('status_offered'), 'confirmed' => t('status_confirmed'), 'in_transit' => t('status_in_transit'), 'delivered' => t('status_delivered'), ]; return $map[$status] ?? $status; } function get_settings(): array { static $settings = null; if ($settings !== null) { return $settings; } $settings = []; try { $stmt = db()->query("SELECT setting_key, setting_value FROM settings"); while ($row = $stmt->fetch()) { $settings[$row['setting_key']] = $row['setting_value']; } } catch (Throwable $e) { } return $settings; } function get_setting(string $key, $default = ''): string { $settings = get_settings(); return $settings[$key] ?? $default; } function has_permission(string $permissionSlug, ?int $userId = null): bool { if ($userId === null) { if (!isset($_SESSION['user_id'])) { return false; } $userId = $_SESSION['user_id']; } static $cache = []; $key = $userId . ':' . $permissionSlug; if (isset($cache[$key])) { return $cache[$key]; } try { $stmt = db()->prepare( "SELECT 1 FROM user_permissions up JOIN permissions p ON up.permission_id = p.id WHERE up.user_id = ? AND p.slug = ?" ); $stmt->execute([$userId, $permissionSlug]); $result = (bool) $stmt->fetchColumn(); $cache[$key] = $result; return $result; } catch (Throwable $e) { return false; } }