« Back to History
profile.php
|
20260722_120325.php
Initial Domain Snapshot
Copy Code
<?php /* ========================================================= File: /erp/public/profile.php Purpose: Self-service profile editor (DP, Password, Email) Scope: Page-only (no global/base change) ========================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); /* Auth */ require __DIR__ . '/../modules/auth/auth.php'; require_login(); $auth = auth_user(); if (!$auth) { header("Location: /erp/public/login.php"); exit; } /* PDO (prefer from auth.php; fallback to core/db.php) */ $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) { require __DIR__ . '/../core/db.php'; $pdo = $GLOBALS['pdo'] ?? null; } if (!$pdo instanceof PDO) { http_response_code(500); echo "DB not available"; exit; } $company_id = (int)$auth['company_id']; $user_id = (int)$auth['id']; /* ------------ WEB BASE (critical DP fix) ------------- If doc-root==/erp/public -> dirname('/profile.php') = '/' Else (root->public_html) -> dirname('/erp/public/profile.php') = '/erp/public' In both cases we want a prefix that points to actual files. ------------------------------------------------------- */ $WEB_BASE = rtrim(str_replace('\\','/', dirname($_SERVER['SCRIPT_NAME'] ?? '')), '/'); if ($WEB_BASE === '') $WEB_BASE = '/'; /* ---------------- Helpers ---------------- */ function table_exists(PDO $pdo, string $name): bool { try { $s=$pdo->query("SHOW TABLES LIKE ".$pdo->quote($name)); return (bool)$s->fetch(); } catch(Throwable $e){ return false; } } function col_exists(PDO $pdo, string $table, string $col): bool { try { $s=$pdo->prepare("SHOW COLUMNS FROM `$table` LIKE ?"); $s->execute([$col]); return (bool)$s->fetch(); } catch(Throwable $e){ return false; } } function ensure_dir(string $dir): bool { if (!is_dir($dir)) return @mkdir($dir, 0775, true); return is_writable($dir); } function sanitize_filename(string $name): string { $name = preg_replace('/[^A-Za-z0-9._-]+/', '_', $name); return trim($name, '_') ?: 'file'; } function load_user_row(PDO $pdo, int $user_id){ $st=$pdo->prepare("SELECT id, company_id, name, email, role, username, mobile, password_hash, password, pass, pwd, pin FROM users WHERE id=? LIMIT 1"); $st->execute([$user_id]); return $st->fetch(PDO::FETCH_ASSOC) ?: null; } function verify_password_legacy(array $row, string $input): bool { if (!empty($row['password_hash']) && password_verify($input, $row['password_hash'])) return true; foreach (['password','pass','pwd','pin'] as $c) { if (!empty($row[$c])) { if (hash_equals((string)$row[$c], $input)) return true; if (hash_equals((string)$row[$c], md5($input))) return true; if (hash_equals((string)$row[$c], sha1($input))) return true; } } return false; } /* ---------------- Load company + profile info ---------------- */ $display_name = $auth['name'] ?? 'User'; $company_name = '(Company)'; try { if (table_exists($pdo,'companies')) { $cx=$pdo->prepare("SELECT name FROM companies WHERE id=?"); $cx->execute([$company_id]); $company_name = $cx->fetchColumn() ?: $company_name; } } catch(Throwable $e){} $HAS_PROFILES = table_exists($pdo,'profiles'); $HAS_FILES = table_exists($pdo,'files'); $PROFILES_HAS_DP= $HAS_PROFILES && col_exists($pdo,'profiles','dp_file_id'); $FILES_HAS_PATH = $HAS_FILES && col_exists($pdo,'files','path'); $USERS_HAS_DPURL= col_exists($pdo,'users','dp_url'); $dp_url = null; if ($PROFILES_HAS_DP && $FILES_HAS_PATH) { try { $ps=$pdo->prepare("SELECT dp_file_id FROM profiles WHERE user_id=? LIMIT 1"); $ps->execute([$user_id]); $fid = (int)($ps->fetchColumn() ?: 0); if ($fid) { $fs=$pdo->prepare("SELECT path FROM files WHERE id=? LIMIT 1"); $fs->execute([$fid]); $p = $fs->fetch(PDO::FETCH_ASSOC); if ($p && !empty($p['path'])) { // FIX: prefix with real web base (handles both deployments) $dp_url = rtrim($WEB_BASE,'/').'/'.$p['path']; } } } catch(Throwable $e){} } elseif ($USERS_HAS_DPURL) { try { $us=$pdo->prepare("SELECT dp_url FROM users WHERE id=?"); $us->execute([$user_id]); $dp_url = $us->fetchColumn() ?: null; } catch(Throwable $e){} } /* ---------------- Actions ---------------- */ $notice = ''; $error = ''; if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { $act = $_POST['act'] ?? ''; /* ---- DP upload ---- */ if ($act === 'upload_dp' && isset($_FILES['dp'])) { try { if (!isset($_FILES['dp']) || $_FILES['dp']['error'] !== UPLOAD_ERR_OK) throw new Exception('Upload failed.'); $tmp = $_FILES['dp']['tmp_name']; $orig = $_FILES['dp']['name'] ?? 'photo.jpg'; $size = (int)($_FILES['dp']['size'] ?? 0); if ($size <= 0 || $size > 5*1024*1024) throw new Exception('Max 5MB image allowed'); $fi = new finfo(FILEINFO_MIME_TYPE); $mime = $fi->file($tmp) ?: 'application/octet-stream'; $ok = ['image/jpeg'=>'jpg','image/jpg'=>'jpg','image/pjpeg'=>'jpg','image/png'=>'png','image/x-png'=>'png','image/webp'=>'webp','image/gif'=>'gif']; if (!isset($ok[$mime])) throw new Exception('Invalid image type'); $ext = $ok[$mime]; $fname = date('Ymd_His').'_'.$user_id.'_'.sanitize_filename($orig); if (strtolower(substr($fname, -strlen($ext)-1)) !== '.'.$ext) $fname .= '.'.$ext; $relDir = 'uploads/dp/'.date('Y/m'); // under /erp/public $absBase= realpath(__DIR__); // /erp/public $absDir = $absBase.DIRECTORY_SEPARATOR.$relDir; if (!ensure_dir($absDir)) throw new Exception('Cannot create upload dir'); $absPath= $absDir.DIRECTORY_SEPARATOR.$fname; $relPath= $relDir.'/'.$fname; if (!move_uploaded_file($tmp, $absPath)) throw new Exception('Move failed'); if ($PROFILES_HAS_DP && $FILES_HAS_PATH) { $insF=$pdo->prepare("INSERT INTO files (path, created_at) VALUES (?, NOW())"); $insF->execute([$relPath]); $file_id=(int)$pdo->lastInsertId(); $pdo->prepare("INSERT INTO profiles (user_id, dp_file_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE dp_file_id=VALUES(dp_file_id)") ->execute([$user_id,$file_id]); } elseif ($USERS_HAS_DPURL) { // FIX: store URL with correct web base $pdo->prepare("UPDATE users SET dp_url=? WHERE id=?")->execute([rtrim($WEB_BASE,'/').'/'.$relPath, $user_id]); } else { @unlink($absPath); throw new Exception('No column to store DP.'); } header("Location: profile.php?ok=dp"); exit; } catch(Throwable $e){ $error = $e->getMessage(); } } /* ---- DP remove ---- */ if ($act === 'remove_dp') { try { if ($PROFILES_HAS_DP && $FILES_HAS_PATH) { $ps=$pdo->prepare("SELECT dp_file_id FROM profiles WHERE user_id=? LIMIT 1"); $ps->execute([$user_id]); $fid=(int)($ps->fetchColumn() ?: 0); if ($fid) { $fs=$pdo->prepare("SELECT path FROM files WHERE id=?"); $fs->execute([$fid]); $p=$fs->fetch(PDO::FETCH_ASSOC); if ($p && !empty($p['path'])) { $abs = realpath(__DIR__.DIRECTORY_SEPARATOR.$p['path']); // safe absolute if ($abs && strpos($abs, realpath(__DIR__)) === 0) { @unlink($abs); } } $pdo->prepare("UPDATE profiles SET dp_file_id=NULL WHERE user_id=?")->execute([$user_id]); } } elseif ($USERS_HAS_DPURL) { $pdo->prepare("UPDATE users SET dp_url=NULL WHERE id=?")->execute([$user_id]); } header("Location: profile.php?ok=dp_removed"); exit; } catch(Throwable $e){ $error = $e->getMessage(); } } /* ---- Password change ---- */ if ($act === 'change_password') { try { $cur = (string)($_POST['current_password'] ?? ''); $n1 = (string)($_POST['new_password'] ?? ''); $n2 = (string)($_POST['confirm_password'] ?? ''); if ($n1 === '' || strlen($n1) < 8) throw new Exception('New password must be at least 8 characters.'); if ($n1 !== $n2) throw new Exception('Passwords do not match.'); $row = load_user_row($pdo, $user_id); if (!$row) throw new Exception('User not found.'); if (!verify_password_legacy($row, $cur)) throw new Exception('Current password is incorrect.'); $hash = password_hash($n1, PASSWORD_DEFAULT); $pdo->prepare("UPDATE users SET password_hash=?, password=NULL, pass=NULL, pwd=NULL, pin=NULL WHERE id=?") ->execute([$hash, $user_id]); header("Location: profile.php?ok=pwd"); exit; } catch(Throwable $e){ $error = $e->getMessage(); } } /* ---- Email update ---- */ if ($act === 'update_email') { try { $email = trim((string)($_POST['email'] ?? '')); if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) throw new Exception('Invalid email.'); $pdo->prepare("UPDATE users SET email=? WHERE id=?")->execute([$email !== '' ? $email : null, $user_id]); header("Location: profile.php?ok=email"); exit; } catch(Throwable $e){ $error = $e->getMessage(); } } } /* GET notices */ $notice = ''; if (!$error && isset($_GET['ok'])) { $ok = $_GET['ok']; if ($ok==='dp') $notice='Profile photo updated.'; elseif ($ok==='dp_removed') $notice='Profile photo removed.'; elseif ($ok==='pwd') $notice='Password updated.'; elseif ($ok==='email') $notice='Email updated.'; } /* ---------------- UI ---------------- */ ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <title>Edit Profile • <?= htmlspecialchars($display_name) ?></title> <style> :root{ --primary:#34A853; --bg:#F5FFF7; --text:#202124; } body{ margin:0; background:#f6f7f8; color:#222; font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial; } .wrap{ max-width:920px; margin:24px auto; padding:0 16px; } .card{ background:#fff; border:1px solid #eee; border-radius:16px; padding:18px; box-shadow:0 6px 20px rgba(0,0,0,.05); } .hd{ display:flex; align-items:center; justify-content:space-between; gap:12px; } .brand{ font-weight:800; color:#111; font-size:18px; } .company{ font-size:12px; color:#555; } .note{ margin:10px 0; padding:10px 12px; border-radius:10px; font-size:14px; } .ok{ background:#ecfdf5; color:#065f46; border:1px solid #a7f3d0; } .err{ background:#fef2f2; color:#991b1b; border:1px solid #fecaca; } .tabs{ display:flex; gap:8px; margin:12px 0 16px; flex-wrap:wrap; } .tab{ border:1px solid #ddd; background:#fff; padding:8px 10px; border-radius:10px; text-decoration:none; color:#111; } .tab.active{ border-color:#34A853; box-shadow:0 0 0 2px rgba(52,168,83,.15); } .grid{ display:grid; grid-template-columns:1fr 1fr; gap:16px; } @media (max-width:720px){ .grid{ grid-template-columns:1fr; } } .box{ border:1px solid #eee; border-radius:12px; padding:14px; } .btn{ appearance:none; border:0; background:#34A853; color:#fff; padding:10px 12px; border-radius:10px; font-weight:600; cursor:pointer; } .btn.secondary{ background:#fff; color:#374151; border:1px solid #d1d5db; } input[type=file],input[type=password],input[type=email]{ width:100%; padding:9px 10px; border:1px solid #ddd; border-radius:10px; } .label{ font-size:13px; color:#374151; margin:6px 0 4px; } .dpwrap{ display:flex; align-items:center; gap:16px; } .dp{ width:88px; height:88px; border-radius:999px; overflow:hidden; background:#fff; border:1px solid #eee; display:grid; place-items:center; } .dp img{ width:100%; height:100%; object-fit:cover; display:block; } .small{ font-size:12px; color:#6b7280; } </style> </head> <body> <div class="wrap"> <div class="card"> <div class="hd"> <div> <div class="brand">Edit Profile</div> <div class="company"><?= htmlspecialchars($company_name) ?></div> </div> <a class="tab" href="/erp/public/employee_portal.php">Back to Portal</a> </div> <?php if($notice): ?><div class="note ok"><?= htmlspecialchars($notice) ?></div><?php endif; ?> <?php if($error): ?><div class="note err"><?= htmlspecialchars($error) ?></div><?php endif; ?> <div class="tabs"> <a class="tab active" href="#dp" onclick="sel('dp');return false;">Profile Photo</a> <a class="tab" href="#pwd" onclick="sel('pwd');return false;">Password</a> <a class="tab" href="#email" onclick="sel('email');return false;">Email</a> </div> <div class="grid"> <!-- DP --> <div id="sec-dp" class="box"> <div class="label">Current Photo</div> <div class="dpwrap"> <div class="dp"> <?php if($dp_url): ?> <img src="<?= htmlspecialchars($dp_url) ?>" alt="DP"> <?php else: ?> <!-- initials --> <svg viewBox="0 0 100 100" width="88" height="88" aria-hidden="true"> <circle cx="50" cy="50" r="48" fill="#f3f4f6"/> <text x="50" y="58" text-anchor="middle" font-size="36" fill="#374151" font-family="Arial, sans-serif"> <?= htmlspecialchars(mb_strtoupper(mb_substr($display_name ?? 'U',0,1))) ?> </text> </svg> <?php endif; ?> </div> <div class="small">JPG / PNG / WEBP / GIF up to 5MB.</div> </div> <form method="post" enctype="multipart/form-data" style="margin-top:10px"> <input type="hidden" name="act" value="upload_dp"> <div class="label">Upload New</div> <input type="file" name="dp" accept="image/*" required> <div style="margin-top:10px; display:flex; gap:8px"> <button class="btn">Save Photo</button> <?php if($dp_url): ?> <button class="btn secondary" name="act" value="remove_dp" onclick="return confirm('Remove current photo?')">Remove Photo</button> <?php endif; ?> </div> </form> </div> <!-- Password --> <div id="sec-pwd" class="box" style="display:none"> <form method="post"> <input type="hidden" name="act" value="change_password"> <div class="label">Current Password</div> <input type="password" name="current_password" required> <div class="label">New Password (min 8)</div> <input type="password" name="new_password" minlength="8" required> <div class="label">Confirm New Password</div> <input type="password" name="confirm_password" minlength="8" required> <div style="margin-top:10px"><button class="btn">Update Password</button></div> </form> </div> <!-- Email --> <div id="sec-email" class="box" style="display:none"> <form method="post"> <input type="hidden" name="act" value="update_email"> <div class="label">Email Address</div> <input type="email" name="email" value="<?= htmlspecialchars($auth['email'] ?? '') ?>" placeholder="name@example.com"> <div style="margin-top:10px; display:flex; gap:8px"> <button class="btn">Save Email</button> <button class="btn secondary" name="email" value="" onclick="return confirm('Clear email?')">Clear</button> </div> </form> </div> </div> </div> </div> <script> function sel(sec){ ['dp','pwd','email'].forEach(id=>{ document.getElementById('sec-'+id).style.display = (id===sec)?'block':'none'; }); document.querySelectorAll('.tabs .tab').forEach(a=>a.classList.remove('active')); const act = document.querySelector('.tabs .tab[href="#'+sec+'"]'); if (act) act.classList.add('active'); } if (location.hash==='#pwd') sel('pwd'); else if (location.hash==='#email') sel('email'); else sel('dp'); </script> </body> </html>