array ( 'app_name' => 'CargoLink', 'nav_home' => 'Overview', 'nav_login' => 'Login', 'nav_shipper' => 'Shipper Desk', 'nav_owner' => 'Truck Owner Desk', 'nav_admin' => 'Admin Panel', 'nav_theme' => 'Theme', 'nav_get_started' => 'Get Started', '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', 'shipments' => 'Shipments', 'reports' => 'Reports', 'summary_report' => 'Summary Report', 'shipments_by_origin_country' => 'Shipments by Origin Country', 'shipments_by_dest_country' => 'Shipments by Destination Country', 'shipments_by_origin_city' => 'Shipments by Origin City', 'shipments_by_dest_city' => 'Shipments by Destination City', 'shipments_by_shipper' => 'Shipments by Shipper', 'generated' => 'Generated', 'report_type' => 'Report Type', 'period' => 'Period', 'analyze_performance' => 'Analyze platform performance and metrics.', 'print' => 'Print', 'start_date' => 'Start Date', 'end_date' => 'End Date', 'apply_filter' => 'Apply Filter', 'no_paid_shipments' => 'No paid shipments found for this period.', 'name' => 'Name', 'total_amount' => 'Total Amount', 'profit' => 'Profit', 'total_label' => 'Total', 'printed_by' => 'Printed By', 'analytics' => 'Analytics', 'shipper_shipments' => 'Shipper Shipments', 'truck_owners_statements' => 'Truck Owners Statements', 'nav_platform_users' => 'Platform Users', 'settings' => 'Settings', 'company_setting' => 'Company Setting', 'integrations' => 'Integrations', 'notification_templates' => 'Notification Templates', 'locations' => 'Locations', 'countries' => 'Countries', 'cities' => 'Cities', 'users' => 'Users', 'shippers' => 'Shippers', 'truck_owners' => 'Truck Owners', 'trucks' => 'Trucks', 'view_truck_docs_in_edit' => 'View truck documents in the Edit page.', 'user_registration' => 'User Registration', 'pages' => 'Pages', 'faqs' => 'FAQs', 'landing_pages' => 'Landing Pages', 'edit_homepage' => 'Edit homepage content and sections', 'view_details' => 'View Details', 'manage_shippers' => 'Manage Shippers', 'manage_registered_shippers' => 'Manage registered shippers.', 'search_placeholder_shipper' => 'Search name, email, company...', 'all_statuses' => 'All Statuses', 'pending' => 'Pending', 'rejected' => 'Rejected', 'active' => 'Active', 'no_shippers_criteria' => 'No shippers found matching your criteria.', 'no_shippers_registered' => 'No shippers registered yet.', 'name_company' => 'Name / Company', 'location' => 'Location', 'edit_shipper' => 'Edit Shipper', 'approve' => 'Approve', 'reject' => 'Reject', 'delete' => 'Delete', 'delete_confirm_shipper' => 'Delete this shipper forever?', 'loading' => 'Loading...', 'manage_truck_owners' => 'Manage Truck Owners', 'review_registrations' => 'Review registrations and approve truck owners.', 'search_placeholder_owner' => 'Search name, email, plate...', 'no_owners_criteria' => 'No truck owners found matching your criteria.', 'no_owners_registered' => 'No truck owners registered yet.', 'name_email' => 'Name / Email', 'truck_info' => 'Truck Info', 'documents' => 'Documents', 'view_docs' => 'View Docs', 'edit_owner' => 'Edit Owner', 'delete_confirm_owner' => 'Delete this truck owner forever?', 'docs_for' => 'Documents for', 'truck_reg' => 'Truck Registration', 'no_picture' => 'No picture uploaded.', 'cap' => 'Cap', 'manage_shipments' => 'Manage Shipments', 'shipments_header' => 'Shipments', 'shipments_subtitle' => 'Manage all shipments across the platform.', 'flash_shipment_deleted' => 'Shipment deleted successfully.', 'search_shipments_placeholder' => 'Search shipments...', 'search_label' => 'Search', 'sort_by' => 'Sort By', 'sort_newest' => 'Newest First', 'sort_oldest' => 'Oldest First', 'sort_pickup_soonest' => 'Pickup Date (Soonest)', 'sort_pickup_latest' => 'Pickup Date (Latest)', 'no_shipments_found_criteria' => 'No shipments found matching your criteria.', 'no_shipments_platform' => 'No shipments found on the platform yet.', 'id_col' => 'ID', 'dates_col' => 'Dates', 'from_label' => 'From:', 'to_label' => 'To:', 'pick_label' => 'Pick:', 'drop_label' => 'Drop:', 'view_shipment' => 'View Shipment', 'edit_shipment_tooltip' => 'Edit Shipment', 'confirm_delete_shipment' => 'Delete this shipment?', 'showing' => 'Showing', 'of' => 'of', 'previous' => 'Previous', 'next' => 'Next', 'error_occurred' => 'An error occurred', 'failed_load_form' => 'Failed to load form.', 'edit_shipment_title' => 'Edit Shipment #', 'update_shipment_details' => 'Update shipment details and status.', 'shipment_not_found' => 'Shipment not found', 'shipment_updated_success' => 'Shipment updated successfully.', 'invalid_id' => 'Invalid ID', 'back' => 'Back', 'shipment_details' => 'Shipment Details', 'origin_country' => 'Origin Country', 'destination_country' => 'Destination Country', 'select_country_placeholder' => 'Select Country', 'select_city_placeholder' => 'Select City', 'loading_cities' => 'Loading...', 'error_loading_cities' => 'Error loading cities', 'cancel' => 'Cancel', 'my_profile' => 'My Profile', 'profile_picture' => 'Profile Picture', 'change_picture' => 'Change Picture', 'picture_hint' => 'Click the camera icon to update.', 'full_name' => 'Full Name', 'email_address' => 'Email Address', 'email_hint' => 'Email cannot be changed.', 'account_role' => 'Account Role', 'change_password' => 'Change Password', 'new_password' => 'New Password', 'confirm_password' => 'Confirm Password', 'save_changes' => 'Save Changes', 'passwords_do_not_match' => 'Passwords do not match.', 'password_too_short' => 'Password must be at least 6 characters.', 'profile_updated' => 'Profile updated successfully.', 'password_updated' => 'Password updated successfully.', 'upload_failed' => 'File upload failed.', 'invalid_image' => 'Invalid image format. Allowed: JPG, PNG, GIF, WEBP.', 'login_title' => 'Login', 'login_subtitle' => 'Access your account', 'forgot_password' => 'Forgot Password?', 'email_placeholder' => 'name@example.com', 'password_placeholder' => 'Enter your password', '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 to receive a reset link', 'send_reset_link' => 'Send Reset Link', 'back_to_login' => 'Back to Login', 'timezone' => 'Timezone', 'whatsapp_settings' => 'WhatsApp Settings', 'enable_whatsapp' => 'Enable WhatsApp Notifications', 'wablas_domain' => 'Wablas Domain', 'wablas_api_token' => 'Wablas API Token', 'reg_title' => 'Registration', 'reg_subtitle' => 'Create an account to get started', 'reg_success_pending' => 'Registration successful. Your account is pending approval.', 'reg_success' => 'Registration successful. You can now login.', 'role' => 'Role', 'shipper' => 'Shipper', 'truck_owner' => 'Truck Owner', 'email' => 'Email', 'password' => 'Password', 'phone' => 'Phone Number', '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_type' => 'Truck Type', 'load_capacity' => 'Load Capacity (Tons)', 'plate_no' => 'Plate Number', 'bank_account' => 'Bank Account Number', 'bank_name' => 'Bank Name', 'bank_branch' => 'Branch Name', 'id_card_front' => 'ID Card (Front)', 'id_card_back' => 'ID Card (Back)', 'truck_reg_front' => 'Truck Registration (Front)', 'truck_reg_back' => 'Truck Registration (Back)', 'truck_picture' => 'Truck Picture', 'create_account' => 'Create Account', 'back_to_admin' => 'Back to Admin', 'back_to_home' => 'Back to Home', 'total_revenue' => 'Total Revenue', 'shipments_analytics' => 'Shipments Analytics', 'export_csv' => 'Export CSV', 'edit_template' => 'Edit Template', 'back_to_list' => 'Back to List', 'template_not_found' => 'Template not found.', 'english_required' => 'English subject and body are required.', 'template_updated' => 'Template updated successfully.', 'english_version' => 'English', 'arabic_version' => 'Arabic', 'email_subject' => 'Email Subject', 'email_body' => 'Email Body', 'email_body_help' => 'Use placeholders like {shipment_id}, {user_name}, {offer_price}.', 'whatsapp_body' => 'WhatsApp Body', 'event_name' => 'Event Name', 'subject_en' => 'Subject (EN)', 'subject_ar' => 'Subject (AR)', 'is_company_checkbox' => 'Register as a company?', 'ctr_number' => 'CTR Number', 'ctr_document' => 'CTR Document', 'notes' => 'Notes', 'profile' => 'Profile', 'view_full_size' => 'View Full Size', 'no_trucks_found' => 'No trucks found.', 'create_shipper' => 'Create Shipper', 'create_owner' => 'Create Truck Owner', 'create_success' => 'User created successfully.', 'add_truck' => 'Add Truck', 'new_truck_details' => 'New Truck Details', 'reg_expiry' => 'Reg. Expiry', 'ins_expiry' => 'Ins. Expiry', 'registration_doc' => 'Registration Doc', 'shipment_type' => 'Shipment Type', 'type_frozen' => 'Frozen', 'type_cold' => 'Cold', 'type_dry' => 'Dry', 'target_price' => 'Target Price', 'status_pending_approval' => 'Pending Approval', 'pending_approval_msg' => 'Your shipment is pending admin approval.', 'company_profile' => 'Company Profile', 'company_profile_intro' => 'Update your app name, logo, favicon, contact details, platform charge, and legal policies.', 'company_profile_updated' => 'Company profile updated successfully.', 'invalid_logo_format' => 'Invalid logo format.', 'invalid_favicon_format' => 'Invalid favicon format.', 'tab_company_setting' => 'Company Setting', 'tab_legal_policies' => 'Legal & Policies', 'tab_privacy_policy' => 'Privacy Policy', 'company_app_name' => 'Company / App Name', 'contact_email' => 'Contact Email', 'displayed_in_footer' => 'Displayed in the footer.', 'contact_phone' => 'Contact Phone', 'pricing_model' => 'Pricing Model', 'pricing_percentage_model' => 'Percentage Fee Model', 'default_label' => '(Default)', 'pricing_percentage_desc' => 'Shipper posts details. Truck Owners make offers. Platform adds fee on top of offer.', 'pricing_fixed_model' => 'Shipper Offer & Confirmation', 'pricing_fixed_desc' => 'Shipper sets price. Admin confirms. Truck Owner accepts fixed price.', 'platform_charge_percent' => 'Platform Charge (%)', 'platform_charge_help' => 'Percentage applied as a platform fee (only for Percentage Model).', 'system_timezone' => 'System Timezone', 'company_logo' => 'Company Logo', 'logo_help' => 'Recommended size: 150x40px (PNG, JPG, SVG). Leave empty to keep current.', 'favicon' => 'Favicon', 'favicon_help' => 'Recommended size: 32x32px (ICO, PNG, SVG). Leave empty to keep current.', 'english_label' => 'English', 'arabic_label' => 'Arabic', 'terms_placeholder_en' => 'Enter Terms of Service in English...', 'terms_placeholder_ar' => 'Enter Terms of Service in Arabic...', 'privacy_placeholder_en' => 'Enter Privacy Policy in English...', 'privacy_placeholder_ar' => 'Enter Privacy Policy in Arabic...', ) ]; function t(string $key): string { global $translations, $lang; return $translations[$lang][$key] ?? ucwords(str_replace('_', ' ', $key)); } function e($value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function ensure_schema(): void { try { db()->exec("ALTER TABLE shipments ADD COLUMN pod_path VARCHAR(255) NULL AFTER offer_owner"); } catch (Exception $e) {} 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'), 'pending_approval' => t('status_pending_approval'), ]; 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; } } function format_currency(float $amount): string { return number_format($amount, 3) . ' OMR'; } function require_login(): void { if (!isset($_SESSION['user_id'])) { header('Location: ' . url_with_lang('login.php')); exit; } } function require_role(string $role): void { require_login(); if ($_SESSION['user_role'] !== $role && $_SESSION['user_role'] !== 'admin') { http_response_code(403); die("Access Denied. You must be a " . ucfirst($role) . " to view this page."); } } function generate_csrf_token(): string { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function csrf_field(): string { $token = generate_csrf_token(); return ''; } function validate_csrf_token(): void { if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { http_response_code(403); die("CSRF validation failed. Please refresh the page and try again."); } } } // Set timezone from settings try { $tz = get_setting('timezone', 'UTC'); if ($tz && in_array($tz, DateTimeZone::listIdentifiers())) { date_default_timezone_set($tz); } } catch (Throwable $e) {}