39038-vm/register.php
2026-03-24 07:16:13 +00:00

409 lines
18 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
ensure_schema();
$errors = [];
$saved = false;
$role = $_GET['role'] ?? 'shipper';
if (!in_array($role, ['shipper', 'truck_owner'], true)) {
$role = 'shipper';
}
$values = [
'full_name' => '',
'email' => '',
'phone' => '',
'country_id' => '',
'city_id' => '',
'address_line' => '',
'company_name' => '',
'bank_account' => '',
'bank_name' => '',
'bank_branch' => '',
'is_company' => '0',
'ctr_number' => '',
'notes' => '',
];
$countries = db()->query("SELECT id, name_en, name_ar FROM countries ORDER BY name_en ASC")->fetchAll();
$cities = db()->query("SELECT id, country_id, name_en, name_ar FROM cities ORDER BY name_en ASC")->fetchAll();
if ($_SERVER['REQUEST_METHOD'] === 'POST') { validate_csrf_token();
$fullName = trim($_POST['full_name'] ?? '');
$email = trim($_POST['email'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$countryId = (int)($_POST['country_id'] ?? 0);
$cityId = (int)($_POST['city_id'] ?? 0);
$addressLine = trim($_POST['address_line'] ?? '');
$companyName = trim($_POST['company_name'] ?? '');
$passwordRaw = (string)($_POST['password'] ?? '');
$values = [
'full_name' => $fullName,
'email' => $email,
'phone' => $phone,
'country_id' => $countryId > 0 ? (string)$countryId : '',
'city_id' => $cityId > 0 ? (string)$cityId : '',
'address_line' => $addressLine,
'company_name' => $companyName,
'bank_account' => trim($_POST['bank_account'] ?? ''),
'bank_name' => trim($_POST['bank_name'] ?? ''),
'bank_branch' => trim($_POST['bank_branch'] ?? ''),
'is_company' => isset($_POST['is_company']) ? '1' : '0',
'ctr_number' => trim($_POST['ctr_number'] ?? ''),
'notes' => trim($_POST['notes'] ?? ''),
];
if ($fullName === '') {
$errors[] = t('error_fullname_required');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = t('error_email_invalid');
}
if ($phone === '') {
$errors[] = t('error_phone_required');
}
if ($countryId <= 0 || $cityId <= 0) {
$errors[] = t('error_location_required');
} else {
$cityCheck = db()->prepare("SELECT COUNT(*) FROM cities WHERE id = ? AND country_id = ?");
$cityCheck->execute([$cityId, $countryId]);
if ((int)$cityCheck->fetchColumn() === 0) {
$errors[] = t('error_city_mismatch');
}
}
if ($addressLine === '') {
$errors[] = t('error_address_required');
}
if (strlen($passwordRaw) < 6) {
$errors[] = t('error_password_length');
}
if ($role === 'shipper' && $companyName === '') {
$errors[] = t('error_company_required');
}
if (!$errors) {
$password = password_hash($passwordRaw, PASSWORD_DEFAULT);
$pdo = db();
try {
$pdo->beginTransaction();
$status = ($role === 'truck_owner') ? 'pending' : 'active';
$stmt = $pdo->prepare("INSERT INTO users (email, password, full_name, role, status) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$email, $password, $fullName, $role, $status]);
$userId = (int)$pdo->lastInsertId();
if ($role === 'shipper') {
$shipperStmt = $pdo->prepare(
"INSERT INTO shipper_profiles (user_id, company_name, phone, country_id, city_id, address_line)
VALUES (?, ?, ?, ?, ?, ?)"
);
$shipperStmt->execute([$userId, $companyName, $phone, $countryId, $cityId, $addressLine]);
} else {
$uploadDir = __DIR__ . '/uploads/profiles/' . $userId . '/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0775, true);
}
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$maxSize = 8 * 1024 * 1024;
$saveImage = static function (string $tmpName, int $size, string $prefix) use ($uploadDir, $allowed, $maxSize): ?string {
if ($size <= 0 || $size > $maxSize) {
return null;
}
$mime = mime_content_type($tmpName) ?: '';
if (!isset($allowed[$mime])) {
return null;
}
$filename = uniqid($prefix, true) . '.' . $allowed[$mime];
$target = $uploadDir . $filename;
if (!move_uploaded_file($tmpName, $target)) {
return null;
}
return 'uploads/profiles/' . basename($uploadDir) . '/' . $filename;
};
$idCardPaths = [];
$ctrPath = null;
if ($values['is_company'] === '1') {
if (is_uploaded_file($_FILES['ctr_document']['tmp_name'] ?? '')) {
$ctrPath = $saveImage($_FILES['ctr_document']['tmp_name'], (int)$_FILES['ctr_document']['size'], 'ctr_');
}
if (!$ctrPath) $errors[] = t('error_ctr_required');
} else {
if (is_uploaded_file($_FILES['id_card_front']['tmp_name'] ?? '')) {
$path = $saveImage($_FILES['id_card_front']['tmp_name'], (int)$_FILES['id_card_front']['size'], 'id_front_');
if ($path) $idCardPaths[] = $path;
}
if (is_uploaded_file($_FILES['id_card_back']['tmp_name'] ?? '')) {
$path = $saveImage($_FILES['id_card_back']['tmp_name'], (int)$_FILES['id_card_back']['size'], 'id_back_');
if ($path) $idCardPaths[] = $path;
}
if (count($idCardPaths) < 2) $errors[] = t('error_id_required');
}
if (!$errors) {
$ownerStmt = $pdo->prepare(
"INSERT INTO truck_owner_profiles (user_id, phone, country_id, city_id, address_line, bank_account, bank_name, bank_branch, id_card_path, is_company, ctr_number, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$ownerStmt->execute([
$userId,
$phone,
$countryId,
$cityId,
$addressLine,
$values['bank_account'],
$values['bank_name'],
$values['bank_branch'],
$values['is_company'] === '1' ? $ctrPath : json_encode($idCardPaths, JSON_UNESCAPED_SLASHES),
$values['is_company'],
$values['ctr_number'],
$values['notes']
]);
}
}
if ($errors) {
$pdo->rollBack();
} else {
$pdo->commit();
$user = [
'id' => $userId,
'email' => $email,
'full_name' => $fullName,
'role' => $role,
'phone' => $phone
];
try {
require_once __DIR__ . '/includes/NotificationService.php';
NotificationService::send('welcome_message', $user, [], $lang);
} catch (Throwable $e) {
error_log('Failed to send welcome notification: ' . $e->getMessage());
}
$saved = true;
}
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
if (stripos($e->getMessage(), 'Duplicate entry') !== false) {
$errors[] = t('error_email_exists');
} else {
$errors[] = t('error_registration_failed') . ' (' . $e->getMessage() . ')';
}
}
}
}
render_header('Shipper & Truck Owner Registration');
?>
<div class="page-intro">
<h1 class="section-title mb-1"><?= e(t('reg_title')) ?></h1>
<p class="muted mb-0"><?= e(t('reg_subtitle')) ?></p>
</div>
<div class="panel p-4">
<?php if ($saved): ?>
<?php if ($role === 'truck_owner'): ?>
<div class="alert alert-success"><?= e(t('reg_success_pending')) ?></div>
<?php else: ?>
<div class="alert alert-success"><?= e(t('reg_success')) ?></div>
<?php endif; ?>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode(' ', $errors)) ?></div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data" id="regForm" novalidate> <?= csrf_field() ?>
<input type="hidden" name="role" value="<?= e($role) ?>">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="full_name"><?= e(t('full_name')) ?></label>
<input type="text" name="full_name" id="full_name" class="form-control" value="<?= e($values['full_name']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" for="email"><?= e(t('email')) ?></label>
<input type="email" name="email" id="email" class="form-control" value="<?= e($values['email']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" for="password"><?= e(t('password')) ?></label>
<input type="password" name="password" id="password" class="form-control" minlength="6" required>
</div>
<div class="col-md-3">
<label class="form-label" for="phone"><?= e(t('phone')) ?></label>
<input type="text" name="phone" id="phone" class="form-control" value="<?= e($values['phone']) ?>" required>
</div>
<div class="col-md-3">
<label class="form-label" for="country_id"><?= e(t('country')) ?></label>
<select name="country_id" id="country_id" class="form-select" onchange="syncCities()" required>
<option value=""><?= e(t('select_country')) ?></option>
<?php foreach ($countries as $country): ?>
<option value="<?= e((string)$country['id']) ?>" <?= $values['country_id'] === (string)$country['id'] ? 'selected' : '' ?>>
<?= e($lang === 'ar' && !empty($country['name_ar']) ? $country['name_ar'] : $country['name_en']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="city_id"><?= e(t('city')) ?></label>
<select name="city_id" id="city_id" class="form-select" required data-selected="<?= e($values['city_id']) ?>">
<option value=""><?= e(t('select_city')) ?></option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="address_line"><?= e(t('address')) ?></label>
<input type="text" name="address_line" id="address_line" class="form-control" value="<?= e($values['address_line']) ?>" required>
</div>
</div>
<?php if ($role === 'shipper'): ?>
<div id="shipperFields" class="mt-4">
<h2 class="h5 mb-3"><?= e(t('shipper_details')) ?></h2>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="company_name"><?= e(t('company_name')) ?></label>
<input type="text" name="company_name" id="company_name" class="form-control" value="<?= e($values['company_name']) ?>" required>
</div>
</div>
</div>
<?php else: ?>
<div id="truckFields" class="mt-4">
<h2 class="h5 mb-3"><?= e(t('truck_details')) ?></h2>
<div class="row g-3">
<div class="col-md-12 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="is_company" id="is_company" value="1" <?= $values['is_company'] === '1' ? 'checked' : '' ?> onchange="toggleCompanyFields()">
<label class="form-check-label" for="is_company"><?= e(t('is_company_checkbox')) ?></label>
</div>
</div>
<div class="col-md-4">
<label class="form-label" for="bank_account"><?= e(t('bank_account')) ?></label>
<input type="text" name="bank_account" id="bank_account" class="form-control" value="<?= e($values['bank_account']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="bank_name"><?= e(t('bank_name')) ?></label>
<input type="text" name="bank_name" id="bank_name" class="form-control" value="<?= e($values['bank_name']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="bank_branch"><?= e(t('bank_branch')) ?></label>
<input type="text" name="bank_branch" id="bank_branch" class="form-control" value="<?= e($values['bank_branch']) ?>">
</div>
<div id="individualDocs" class="row g-3 mt-0">
<div class="col-md-6">
<label class="form-label" for="id_card_front"><?= e(t('id_card_front')) ?></label>
<input type="file" name="id_card_front" id="id_card_front" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-6">
<label class="form-label" for="id_card_back"><?= e(t('id_card_back')) ?></label>
<input type="file" name="id_card_back" id="id_card_back" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
</div>
<div id="companyDocs" class="row g-3 mt-0" style="display:none;">
<div class="col-md-6">
<label class="form-label" for="ctr_number"><?= e(t('ctr_number')) ?></label>
<input type="text" name="ctr_number" id="ctr_number" class="form-control" value="<?= e($values['ctr_number']) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="ctr_document"><?= e(t('ctr_document')) ?></label>
<input type="file" name="ctr_document" id="ctr_document" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-12">
<label class="form-label" for="notes"><?= e(t('notes')) ?></label>
<textarea name="notes" id="notes" class="form-control"><?= e($values['notes']) ?></textarea>
</div>
</div>
</div>
</div>
<?php endif; ?>
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-primary"><?= e(t('create_account')) ?></button>
<a class="btn btn-outline-dark" href="<?= e(url_with_lang('index.php')) ?>"><?= e(t('back_to_home')) ?></a>
</div>
</form>
</div>
<style>
<?php if ($lang === 'ar'): ?>
/* Override default browser file input text */
input[type="file"]::file-selector-button {
margin-left: 10px;
margin-right: 0;
}
input[type="file"] {
text-align: right;
}
<?php endif; ?>
</style>
<script>
const allCities = <?= json_encode($cities, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const lang = '<?= e($lang) ?>';
// Force browser-specific file input text override
function updateFileInputPlaceholder() {
if (lang === 'ar') {
const fileInputs = document.querySelectorAll('input[type="file"]');
fileInputs.forEach(input => {
// Standard browser "No file chosen" isn't directly settable via CSS
// but we can wrap it or use custom UI if needed.
// For now, we apply basic directionality for better UI.
});
}
}
function syncCities() {
const countryId = document.getElementById('country_id').value;
const citySelect = document.getElementById('city_id');
const selectedValue = citySelect.dataset.selected || '';
citySelect.innerHTML = '<option value=""><?= e(t('select_city')) ?></option>';
allCities.forEach((city) => {
if (String(city.country_id) !== String(countryId)) {
return;
}
const option = document.createElement('option');
option.value = city.id;
option.textContent = <?= $lang === 'ar' ? '(city.name_ar || city.name_en)' : '(city.name_en || city.name_ar)' ?>;
if (String(city.id) === String(selectedValue)) {
option.selected = true;
}
citySelect.appendChild(option);
});
citySelect.dataset.selected = '';
}
function toggleCompanyFields() {
const isCompany = document.getElementById('is_company').checked;
const front = document.getElementById('id_card_front');
const back = document.getElementById('id_card_back');
const ctr = document.getElementById('ctr_document');
if (front) front.required = !isCompany;
if (back) back.required = !isCompany;
if (ctr) ctr.required = isCompany;
document.getElementById('individualDocs').style.display = isCompany ? 'none' : 'flex';
document.getElementById('companyDocs').style.display = isCompany ? 'flex' : 'none';
}
syncCities();
updateFileInputPlaceholder();
<?php if ($role === 'truck_owner'): ?>
toggleCompanyFields();
<?php endif; ?>
</script>
<?php render_footer();