www
/
wwwroot
/
codedabster.com
/
workspace
/
admin
➕ New
📤 Upload
✎ Editing:
projects.php
← Back
<?php include('../includes/config.php'); include('../includes/config.inc.php'); $errors = []; $msg = $_GET['msg'] ?? ''; /** ---------- Helpers FS ---------- */ function fs_normalize($p) { $p = str_replace(["\0"], '', (string)$p); // evităm .. dubioase & normalizăm $real = realpath($p); return $real ?: rtrim($p, '/'); } // setează aici root-ul „safe” (poți lua din config dacă ai deja) $FS_ROOT = rtrim(($config['root_path'] ?? '/'), '/'); // fallback "/" if ($FS_ROOT === '') { $FS_ROOT = '/'; } function fs_is_allowed($fullPath, $root) { $full = fs_normalize($fullPath); $root = rtrim(fs_normalize($root), '/'); if ($root === '') $root = '/'; return $full === $root || (strpos($full, $root . '/') === 0); } function fs_list_dirs($dir) { $out = []; if (!is_dir($dir) || !is_readable($dir)) return $out; $items = @scandir($dir); if (!$items) return $out; foreach ($items as $it) { if ($it === '.' || $it === '..') continue; $p = $dir . '/' . $it; if (is_dir($p)) { $out[] = ['name' => $it, 'path' => rtrim($p, '/')]; } } return $out; } /** Helpers FS */ function path_is_inside($base, $path) { $base = rtrim(realpath($base), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; $rp = realpath($path); if ($rp === false) return false; // dacă nu există încă, folosim dirname ca fallback return strncmp($rp, $base, strlen($base)) === 0; } /** mkdir cu permisiuni + recursiv; returnează true/false */ function mkpath($p) { if (is_dir($p)) return true; return @mkdir($p, 0775, true); } /** ștergere recursivă sigură */ function rrmdir($dir) { if (!is_dir($dir)) return; $items = scandir($dir); foreach ($items as $it) { if ($it === '.' || $it === '..') continue; $full = $dir . DIRECTORY_SEPARATOR . $it; if (is_dir($full) && !is_link($full)) rrmdir($full); else @unlink($full); } @rmdir($dir); } /** prima versiune de patch: 1.2 -> 1.2.1 (dacă nu are .x) */ function first_patch_of($v) { if ($v === '') return 'v1.0.1'; // acceptăm și format cu / fără 'v' $v = ltrim($v, 'vV'); if (preg_match('~^\d+\.\d+\.\d+$~', $v)) return 'v' . $v; // deja are 3 segmente return 'v' . $v . '.1'; } /** adaugă 'v' dacă lipsește */ function norm_ver($v) { $v = trim($v); if ($v === '') return 'v1.0'; return (stripos($v, 'v') === 0) ? $v : ('v' . $v); } // === DELETE (hard) === // === DELETE (hard) === if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'delete') { $id = (int)$_GET['id']; // detectăm dacă vine prin AJAX (POST cu del_fs) $isAjax = (strtoupper($_SERVER['REQUEST_METHOD']) === 'POST') || (isset($_SERVER['HTTP_X_REQUESTED_WITH'])); if ($id > 0) { // aflăm working_dir înainte de delete $info = $database->execute("SELECT working_dir FROM projects WHERE id = ? LIMIT 1", [$id]); $wd = ($info['status'] && $info['rows']) ? ($info['data'][0]['working_dir'] ?? '') : ''; $delFs = isset($_POST['del_fs']) ? (int)$_POST['del_fs'] : 0; // ștergem din DB $database->execute("DELETE FROM projects WHERE id = ? LIMIT 1", [$id]); $fsDeleted = false; $fsErr = null; if ($delFs && $wd && fs_ensure_is_under_docroot($wd)) { if (is_dir($wd)) { $fsDeleted = fs_rrmdir($wd); if (!$fsDeleted) $fsErr = 'could not delete folder'; } else { // dacă nu mai există, tot îl considerăm „ok” $fsDeleted = true; } } if ($isAjax) { header('Content-Type: application/json; charset=utf-8'); echo json_encode(['status'=>1, 'fs'=>['attempted'=> (bool)$delFs, 'ok'=>$fsDeleted, 'error'=>$fsErr]]); exit; } header("Location: projects.php?msg=deleted"); exit; } if ($isAjax) { header('Content-Type: application/json; charset=utf-8'); echo json_encode(['status'=>0, 'error'=>'invalid id']); exit; } } // === FS: list only directories, rooted at $_SERVER['DOCUMENT_ROOT'] === if (isset($_GET['action']) && $_GET['action'] === 'fs_list') { header('Content-Type: application/json; charset=utf-8'); // Baza permisă: DOAR DOCUMENT_ROOT $base = rtrim((string)($_SERVER['DOCUMENT_ROOT'] ?? '/'), '/'); $baseReal = realpath($base); if ($baseReal === false) { echo json_encode(['ok'=>0,'error'=>'invalid DOCUMENT_ROOT']); exit; } // Path cerut (poate veni gol, "/" sau relativ) $req = trim((string)($_GET['dir'] ?? '')); if ($req === '' || $req === '/') { $req = $base; // start from root } elseif ($req[0] !== '/') { // tratează ca RELATIV la baza permisă $req = $base . '/' . $req; } $dirReal = realpath($req); if ($dirReal === false || !is_dir($dirReal)) { echo json_encode(['ok'=>0,'error'=>'not found']); exit; } // securitate: trebuie să rămână sub DOCUMENT_ROOT if (strpos($dirReal, $baseReal) !== 0) { echo json_encode(['ok'=>0,'error'=>'path not allowed']); exit; } if (!is_readable($dirReal)) { echo json_encode(['ok'=>0,'error'=>'unreadable']); exit; } $scan = @scandir($dirReal); if ($scan === false) { echo json_encode(['ok'=>0,'error'=>'scan failed']); exit; } $items = []; foreach ($scan as $name) { if ($name === '.' || $name === '..') continue; $p = $dirReal . DIRECTORY_SEPARATOR . $name; if (is_dir($p) && is_readable($p)) { $items[] = [ 'name' => $name, 'path' => str_replace('\\','/', $p), // returnăm path absolut sub DOCUMENT_ROOT ]; } } usort($items, fn($a,$b) => strcasecmp($a['name'], $b['name'])); echo json_encode([ 'ok' => 1, 'base' => str_replace('\\','/',$baseReal), 'dir' => str_replace('\\','/',$dirReal), 'items'=> $items, ]); exit; } // === FS: rmdir (doar directoare goale) sub DOCUMENT_ROOT === if (isset($_GET['action']) && $_GET['action'] === 'fs_rmdir') { header('Content-Type: application/json; charset=utf-8'); $base = rtrim((string)($_SERVER['DOCUMENT_ROOT'] ?? '/'), '/'); $baseReal = realpath($base); if ($baseReal === false) { echo json_encode(['ok'=>0,'error'=>'invalid DOCUMENT_ROOT']); exit; } $path = (string)($_POST['path'] ?? ''); if ($path === '' || $path === '/') { echo json_encode(['ok'=>0,'error'=>'invalid path']); exit; } // normalizăm calea în DOC_ROOT if ($path[0] !== '/') $path = $base . '/' . $path; $real = realpath($path); if ($real === false || !is_dir($real)) { echo json_encode(['ok'=>0,'error'=>'directory not found']); exit; } if (strpos($real, $baseReal) !== 0) { echo json_encode(['ok'=>0,'error'=>'path not allowed']); exit; } // trebuie să fie gol $files = @scandir($real); if ($files === false) { echo json_encode(['ok'=>0,'error'=>'cannot read directory']); exit; } if (count(array_diff($files, ['.','..'])) > 0) { echo json_encode(['ok'=>0,'error'=>'directory not empty']); exit; } $ok = @rmdir($real); if (!$ok) { echo json_encode(['ok'=>0,'error'=>'rmdir failed']); exit; } echo json_encode(['ok'=>1, 'deleted'=>str_replace('\\','/',$real)]); exit; } // === FS: mkdir sub DOCUMENT_ROOT === if (isset($_GET['action']) && $_GET['action'] === 'fs_mkdir') { header('Content-Type: application/json; charset=utf-8'); $base = rtrim((string)($_SERVER['DOCUMENT_ROOT'] ?? '/'), '/'); $baseReal = realpath($base); if ($baseReal === false) { echo json_encode(['ok'=>0,'error'=>'invalid DOCUMENT_ROOT']); exit; } $dir = (string)($_POST['dir'] ?? ''); $name = (string)($_POST['name'] ?? ''); // normalizăm dir (poate fi "/" sau absolut sub DOC_ROOT) if ($dir === '' || $dir === '/') $dir = $base; elseif ($dir[0] !== '/') $dir = $base . '/' . $dir; $dirReal = realpath($dir); if ($dirReal === false || !is_dir($dirReal)) { echo json_encode(['ok'=>0,'error'=>'dir not found']); exit; } if (strpos($dirReal, $baseReal) !== 0) { echo json_encode(['ok'=>0,'error'=>'path not allowed']); exit; } // validare nume (doar folder name, nu cale) $name = trim($name); // înlocuim spațiile cu underscore pentru UX mai bun $name = preg_replace('/\s+/', '_', $name); if ($name === '' || strlen($name) > 128) { echo json_encode(['ok'=>0,'error'=>'invalid name']); exit; } if (preg_match('#[/\\\\]#', $name)) { echo json_encode(['ok'=>0,'error'=>'name must not contain slashes']); exit; } if (!preg_match('/^[A-Za-z0-9._-]+$/', $name)) { echo json_encode(['ok'=>0,'error'=>'allowed: letters, digits, . _ -']); exit; } $newPath = $dirReal . DIRECTORY_SEPARATOR . $name; if (file_exists($newPath)) { echo json_encode(['ok'=>0,'error'=>'already exists']); exit; } // creează $ok = @mkdir($newPath, 0775, true); if (!$ok) { echo json_encode(['ok'=>0,'error'=>'mkdir failed']); exit; } // final echo json_encode([ 'ok' => 1, 'path' => str_replace('\\','/',$newPath), 'name' => $name, 'dir' => str_replace('\\','/',$dirReal) ]); exit; } // === Helpers FS: limităm totul la DOCUMENT_ROOT function fs_base_real() { $base = rtrim((string)($_SERVER['DOCUMENT_ROOT'] ?? '/'), '/'); $real = realpath($base); return $real ?: '/'; } // normalizează o cale ABSOLUTĂ presupusă sub DOC_ROOT function fs_normalize_under_docroot(string $abs): string { $base = fs_base_real(); // eliminăm multiple slashes + rezolvăm segmente .. / . $parts = []; foreach (explode('/', preg_replace('#/+#','/', $abs)) as $seg) { if ($seg === '' || $seg === '.') continue; if ($seg === '..') { array_pop($parts); continue; } $parts[] = $seg; } $norm = '/'.implode('/', $parts); // dacă nu începe cu base, încercăm să îl facem relativ la base if (strpos($norm, $base.'/') === 0 || $norm === $base) return $norm; // dacă e „/var/www/html/site/…”, iar base e „/var/www/html”, tot ok dacă începe cu el if (strpos($norm, $base) === 0) return $norm; // dacă a venit fără base, prefixăm base return rtrim($base,'/').$norm; } function fs_ensure_is_under_docroot(string $abs): bool { $base = fs_base_real(); $norm = fs_normalize_under_docroot($abs); return (strpos($norm, $base) === 0); } function fs_mkdirs(array $paths): array { $out = []; foreach ($paths as $p) { $p = fs_normalize_under_docroot($p); if (!fs_ensure_is_under_docroot($p)) { $out[$p] = false; continue; } if (!is_dir($p)) { @mkdir($p, 0775, true); } $out[$p] = is_dir($p); } return $out; } function fs_rrmdir(string $abs): bool { $abs = fs_normalize_under_docroot($abs); if (!fs_ensure_is_under_docroot($abs)) return false; if (!is_dir($abs)) return false; $items = scandir($abs); if ($items === false) return false; foreach ($items as $it) { if ($it === '.' || $it === '..') continue; $path = $abs.'/'.$it; if (is_dir($path) && !is_link($path)) { if (!fs_rrmdir($path)) return false; } else { if (!@unlink($path)) return false; } } return @rmdir($abs); } // prima versiune „patch” din start_version: 1.0 -> 1.0.1 function first_patch(string $ver): string { $ver = trim($ver); if ($ver === '') return '1.0.1'; // extrage cifrele principale if (preg_match('/^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/', $ver, $m)) { $maj = (int)$m[1]; $min = isset($m[2]) ? (int)$m[2] : 0; // patch = 1 return $maj.'.'.$min.'.1'; } // fallback return $ver.'.1'; } /** ========== API JSON: GET/ADD/EDIT ========== */ if (isset($_GET['action']) && in_array($_GET['action'], ['get','add','edit'], true)) { header('Content-Type: application/json; charset=utf-8'); if ($_GET['action'] === 'get') { $id = (int)($_GET['id'] ?? 0); if ($id <= 0) { echo json_encode(['status'=>0,'error'=>'invalid id']); exit; } $res = $database->execute("SELECT * FROM projects WHERE id = ? LIMIT 1", [$id]); if ($res['status'] && $res['rows']) { echo json_encode(['status'=>1,'data'=>$res['data'][0]]); exit; } echo json_encode(['status'=>0,'error'=>'not found']); exit; } // colectăm POST pentru add/edit $id = (int)($_POST['id'] ?? 0); $title = trim($_POST['title'] ?? ''); $slug = trim($_POST['slug'] ?? ''); $client_name = trim($_POST['client_name'] ?? ''); $working_dir = trim($_POST['working_dir'] ?? ''); $repo_url = trim($_POST['repo_url'] ?? ''); $type = trim($_POST['type'] ?? 'local'); $statusV = trim($_POST['status'] ?? 'open'); $deadline_date = trim($_POST['deadline_date'] ?? ''); $start_version = trim($_POST['start_version'] ?? ''); $description = $_POST['description_html'] ?? ''; $deadline_param= $deadline_date; // alias consistent if ($title === '' || $slug === '') { echo json_encode(['status'=>0,'error'=>'Title and Slug are required']); exit; } if ($_GET['action'] === 'add') { // INSERT $res = $database->execute(" INSERT INTO projects (title, creator_user_id, slug, description_html, client_name, working_dir, repo_url, type, status, deadline_date, start_version, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?, ''),?, NOW(), NOW()) ", [ $title, SES_ADMIN, $slug, $description, $client_name, $working_dir, $repo_url, $type, $statusV, $deadline_param, $start_version ]); if (!$res['status']) { echo json_encode(['status'=>0, 'error'=>($res['message'] ?? 'insert failed')]); exit; } $newId = (int)($res['last_id'] ?? 0); // === creeăm structura de foldere, doar dacă working_dir e setat și e sub DOC_ROOT $doc = fs_base_real(); $projRoot = fs_normalize_under_docroot($working_dir ?: ''); $fs = ['ok'=>false, 'paths'=>[]]; if ($working_dir && fs_ensure_is_under_docroot($projRoot)) { $v = trim($start_version) !== '' ? trim($start_version) : '1.0'; $vTag = $v; $pTag = first_patch($v); $paths = [ $projRoot, $projRoot.'/.trash', $projRoot.'/releases/'.$vTag, $projRoot.'/releases/'.$vTag.'/patch/'.$pTag, $projRoot.'/original_diff', ]; $mk = fs_mkdirs($paths); $fs['paths'] = [ 'root' => $projRoot, 'trash'=> $projRoot.'/.trash', 'release'=> $projRoot.'/releases/'.$vTag, 'patch'=> $projRoot.'/releases/'.$vTag.'/patch/'.$pTag, 'original_diff'=> $projRoot.'/original_diff' ]; $fs['ok'] = array_reduce($mk, fn($c,$v)=> $c && $v, true); } echo json_encode(['status'=>1, 'last_id'=>$newId, 'fs'=>$fs]); exit; } if ($_GET['action'] === 'edit') { if ($id <= 0) { echo json_encode(['status'=>0,'error'=>'invalid id']); exit; } $res = $database->execute(" UPDATE projects SET title = ?, slug = ?, description_html = ?, client_name = ?, working_dir = ?, repo_url = ?, type = ?, status = ?, deadline_date = NULLIF(?, ''), start_version = ?, updated_at = NOW() WHERE id = ? LIMIT 1 ", [ $title, $slug, $description, $client_name, $working_dir, $repo_url, $type, $statusV, $deadline_param, $start_version, $id ]); echo json_encode(['status' => $res['status'] ? 1 : 0, 'error' => ($res['message'] ?? null)]); exit; } } /** ========= LISTARE (pagina HTML) ========= */ $status = isset($_GET['status']) ? strtolower(trim($_GET['status'])) : 'all'; $allowed = ['all','open','finalized','deleted','draft']; if (!in_array($status, $allowed, true)) $status = 'all'; $q = isset($_GET['q']) ? trim($_GET['q']) : ''; $page = max(1, (int)($_GET['page'] ?? 1)); $perPage = 20; $offset = ($page - 1) * $perPage; $where = " WHERE 1 "; $params = []; if ($status !== 'all') { $where .= " AND status = ? "; $params[] = $status; } if ($q !== '') { $where .= " AND (title LIKE CONCAT('%',?,'%') OR client_name LIKE CONCAT('%',?,'%') OR working_dir LIKE CONCAT('%',?,'%')) "; $params[] = $q; $params[] = $q; $params[] = $q; } $count = $database->execute("SELECT COUNT(*) AS c FROM projects {$where}", $params); $totalRows = $count['status'] && $count['rows'] ? (int)$count['data'][0]['c'] : 0; $data = $database->execute(" SELECT id, title, client_name, working_dir, type, status, created_at, deadline_date, last_patch_version, start_version FROM projects {$where} ORDER BY id DESC LIMIT ? OFFSET ? ", array_merge($params, [$perPage, $offset])); $projects = $data['status'] ? $data['data'] : []; $totalPages = (int)ceil(max(1, $totalRows) / $perPage); $startRow = $totalRows > 0 ? (($page - 1) * $perPage + 1) : 0; $endRow = $totalRows > 0 ? (min($page * $perPage, $totalRows)) : 0; $smarty->assign('projects', $projects); $smarty->assign('totalRows', $totalRows); $smarty->assign('totalPages', $totalPages); $smarty->assign('page', $page); $smarty->assign('perPage', $perPage); $smarty->assign('q', $q); $smarty->assign('status', $status); $smarty->assign('msg', $msg); $smarty->assign('startRow', $startRow); $smarty->assign('endRow', $endRow); $smarty->display(ADMIN_TEMPLATE_PATH . 'projects.html');
💾 Save Changes
Cancel
📤 Upload File
×
Select File
Upload
Cancel
➕ Create New
×
Type
📄 File
📁 Folder
Name
Create
Cancel
✎ Rename Item
×
Current Name
New Name
Rename
Cancel
🔐 Change Permissions
×
Target File
Permission (e.g., 0755, 0644)
0755
0644
0777
Apply
Cancel