Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6908213cbc |
26
add_log.php
Normal file
26
add_log.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/cls/class.pef.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$pef = new Pef();
|
||||||
|
$projectId = (int)($_POST['project_id'] ?? 0);
|
||||||
|
$version = $_POST['version'] ?? '';
|
||||||
|
$logEntry = $_POST['log_entry'] ?? '';
|
||||||
|
$author = $_SESSION['user']['full_name'] ?? 'System';
|
||||||
|
|
||||||
|
if ($projectId && $version && $logEntry) {
|
||||||
|
$pef->addLog($projectId, $version, $logEntry, $author);
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: index.php?view=projects&project_id=$projectId");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
137
cls/class.pef.php
Normal file
137
cls/class.pef.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
class Pef {
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = db();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all projects, optionally filtered by industry or status.
|
||||||
|
*/
|
||||||
|
public function getProjects(?string $industry = null, ?string $status = null): array {
|
||||||
|
$sql = "SELECT * FROM projects WHERE 1=1";
|
||||||
|
$params = [];
|
||||||
|
if ($industry) {
|
||||||
|
$sql .= " AND industry = :industry";
|
||||||
|
$params['industry'] = $industry;
|
||||||
|
}
|
||||||
|
if ($status) {
|
||||||
|
$sql .= " AND status = :status";
|
||||||
|
$params['status'] = $status;
|
||||||
|
}
|
||||||
|
$sql .= " ORDER BY updated_at DESC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User authentication.
|
||||||
|
*/
|
||||||
|
public function login(string $username, string $password): ?array {
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = :username");
|
||||||
|
$stmt->execute(['username' => $username]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
unset($user['password']);
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update project status or details.
|
||||||
|
*/
|
||||||
|
public function updateProject(int $id, array $data): bool {
|
||||||
|
$fields = [];
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$fields[] = "$key = :$key";
|
||||||
|
}
|
||||||
|
$sql = "UPDATE projects SET " . implode(', ', $fields) . " WHERE id = :id";
|
||||||
|
$data['id'] = $id;
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
return $stmt->execute($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete project.
|
||||||
|
*/
|
||||||
|
public function deleteProject(int $id): bool {
|
||||||
|
$stmt = $this->db->prepare("DELETE FROM projects WHERE id = :id");
|
||||||
|
return $stmt->execute(['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard stats.
|
||||||
|
*/
|
||||||
|
public function getStats(): array {
|
||||||
|
$stats = [];
|
||||||
|
$stats['total_projects'] = $this->db->query("SELECT COUNT(*) FROM projects")->fetchColumn();
|
||||||
|
$stats['active_projects'] = $this->db->query("SELECT COUNT(*) FROM projects WHERE status = 'active'")->fetchColumn();
|
||||||
|
$stats['latest_logs'] = $this->db->query("SELECT pl.*, p.name as project_name FROM project_logs pl JOIN projects p ON pl.project_id = p.id ORDER BY pl.created_at DESC LIMIT 5")->fetchAll();
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single project by ID.
|
||||||
|
*/
|
||||||
|
public function getProject(int $id): ?array {
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM projects WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
return $stmt->fetch() ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all logs for a specific project.
|
||||||
|
*/
|
||||||
|
public function getProjectLogs(int $projectId): array {
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM project_logs WHERE project_id = :project_id ORDER BY created_at DESC");
|
||||||
|
$stmt->execute(['project_id' => $projectId]);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new project.
|
||||||
|
*/
|
||||||
|
public function createProject(string $name, string $description, string $industry, string $version = '1.0.0'): int {
|
||||||
|
$stmt = $this->db->prepare("INSERT INTO projects (name, description, industry, current_version) VALUES (:name, :description, :industry, :version)");
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $description,
|
||||||
|
'industry' => $industry,
|
||||||
|
'version' => $version
|
||||||
|
]);
|
||||||
|
return (int)$this->db->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a log entry for a project and update its current version.
|
||||||
|
*/
|
||||||
|
public function addLog(int $projectId, string $version, string $logEntry, string $author = 'System'): bool {
|
||||||
|
try {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare("INSERT INTO project_logs (project_id, version, log_entry, author) VALUES (:project_id, :version, :log_entry, :author)");
|
||||||
|
$stmt->execute([
|
||||||
|
'project_id' => $projectId,
|
||||||
|
'version' => $version,
|
||||||
|
'log_entry' => $logEntry,
|
||||||
|
'author' => $author
|
||||||
|
]);
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare("UPDATE projects SET current_version = :version WHERE id = :id");
|
||||||
|
$stmt->execute(['version' => $version, 'id' => $projectId]);
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
db/migrations/20260215_init.sql
Normal file
20
db/migrations/20260215_init.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-- Initial schema for Construction and Education CMS
|
||||||
|
CREATE TABLE IF NOT EXISTS projects (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
industry ENUM('Construction', 'Education', 'Other') DEFAULT 'Construction',
|
||||||
|
current_version VARCHAR(50) DEFAULT '1.0.0',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS project_logs (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
project_id INT NOT NULL,
|
||||||
|
version VARCHAR(50) NOT NULL,
|
||||||
|
log_entry TEXT NOT NULL,
|
||||||
|
author VARCHAR(255),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
30
db/migrations/20260216_auth_and_status.sql
Normal file
30
db/migrations/20260216_auth_and_status.sql
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
-- Add users table
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
role ENUM('admin', 'editor', 'viewer') DEFAULT 'viewer',
|
||||||
|
full_name VARCHAR(100),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Safely add status column
|
||||||
|
SET @dbname = DATABASE();
|
||||||
|
SET @tablename = "projects";
|
||||||
|
SET @columnname = "status";
|
||||||
|
SET @preparedStatement = (SELECT IF(
|
||||||
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @dbname
|
||||||
|
AND TABLE_NAME = @tablename
|
||||||
|
AND COLUMN_NAME = @columnname) > 0,
|
||||||
|
"SELECT 1",
|
||||||
|
"ALTER TABLE projects ADD COLUMN status ENUM('active', 'completed', 'on_hold', 'archived') DEFAULT 'active' AFTER industry"
|
||||||
|
));
|
||||||
|
PREPARE stmt FROM @preparedStatement;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- Create default admin user (password: admin123)
|
||||||
|
INSERT INTO users (username, password, role, full_name)
|
||||||
|
VALUES ('admin', '$2y$10$Tsw26nakbueEO40q9tx8Pe4g1yQuWfQFhKXjyVYvbNakTgBi/7NkG', 'admin', 'System Administrator')
|
||||||
|
ON DUPLICATE KEY UPDATE password = VALUES(password);
|
||||||
447
index.php
447
index.php
@ -1,150 +1,341 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@ini_set('display_errors', '1');
|
session_start();
|
||||||
@error_reporting(E_ALL);
|
require_once __DIR__ . '/cls/class.pef.php';
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
if (!isset($_SESSION['user'])) {
|
||||||
$now = date('Y-m-d H:i:s');
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $_SESSION['user'];
|
||||||
|
$pef = new Pef();
|
||||||
|
$stats = $pef->getStats();
|
||||||
|
$projects = $pef->getProjects();
|
||||||
|
|
||||||
|
$selectedProjectId = isset($_GET['project_id']) ? (int)$_GET['project_id'] : null;
|
||||||
|
$selectedProject = $selectedProjectId ? $pef->getProject($selectedProjectId) : null;
|
||||||
|
$logs = $selectedProjectId ? $pef->getProjectLogs($selectedProjectId) : [];
|
||||||
|
|
||||||
|
$view = $_GET['view'] ?? 'dashboard';
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>New Style</title>
|
<title>CMS Dashboard - Construction & Education</title>
|
||||||
<?php
|
|
||||||
// Read project preview data from environment
|
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
|
||||||
<?php if ($projectDescription): ?>
|
|
||||||
<!-- Meta description -->
|
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
|
||||||
<!-- Open Graph meta tags -->
|
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<!-- Open Graph image -->
|
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<!-- Twitter image -->
|
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-color-start: #6a11cb;
|
--sidebar-bg: #1e293b;
|
||||||
--bg-color-end: #2575fc;
|
--sidebar-hover: #334155;
|
||||||
--text-color: #ffffff;
|
--primary: #3b82f6;
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
--bg: #f8fafc;
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
--card: #ffffff;
|
||||||
}
|
--text: #1e293b;
|
||||||
body {
|
--text-muted: #64748b;
|
||||||
margin: 0;
|
--border: #e2e8f0;
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
body { margin: 0; font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); display: flex; min-height: 100vh; }
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
nav { width: 260px; background: var(--sidebar-bg); color: white; display: flex; flex-direction: column; }
|
||||||
|
.nav-header { padding: 2rem 1.5rem; font-weight: 700; font-size: 1.25rem; color: var(--primary); }
|
||||||
|
.nav-links { flex: 1; padding: 0 1rem; }
|
||||||
|
.nav-link { display: flex; align-items: center; padding: 0.75rem 1rem; color: #cbd5e1; text-decoration: none; border-radius: 8px; margin-bottom: 0.5rem; transition: all 0.2s; }
|
||||||
|
.nav-link:hover, .nav-link.active { background: var(--sidebar-hover); color: white; }
|
||||||
|
.nav-footer { padding: 1.5rem; border-top: 1px solid #334155; }
|
||||||
|
.user-info { font-size: 0.875rem; color: #94a3b8; margin-bottom: 1rem; }
|
||||||
|
.logout-btn { color: #f87171; text-decoration: none; font-size: 0.875rem; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
main { flex: 1; display: flex; flex-direction: column; }
|
||||||
|
.top-bar { height: 64px; background: white; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 2rem; }
|
||||||
|
.content { padding: 2rem; overflow-y: auto; }
|
||||||
|
|
||||||
|
/* Cards & Stats */
|
||||||
|
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
|
||||||
|
.stat-card { background: white; padding: 1.5rem; border-radius: 12px; border: 1px solid var(--border); }
|
||||||
|
.stat-label { font-size: 0.875rem; color: var(--text-muted); font-weight: 500; }
|
||||||
|
.stat-value { font-size: 1.5rem; font-weight: 700; margin-top: 0.5rem; }
|
||||||
|
|
||||||
|
.card { background: white; border-radius: 12px; border: 1px solid var(--border); overflow: hidden; margin-bottom: 1.5rem; }
|
||||||
|
.card-header { padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.card-title { margin: 0; font-size: 1rem; font-weight: 600; }
|
||||||
|
.card-body { padding: 1.5rem; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
th { text-align: left; padding: 1rem; font-size: 0.875rem; color: var(--text-muted); border-bottom: 1px solid var(--border); }
|
||||||
|
td { padding: 1rem; border-bottom: 1px solid var(--border); font-size: 0.9375rem; }
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge { padding: 0.25rem 0.625rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
|
||||||
|
.badge-active { background: #dcfce7; color: #166534; }
|
||||||
|
.badge-completed { background: #e0f2fe; color: #0369a1; }
|
||||||
|
.badge-on_hold { background: #fef3c7; color: #92400e; }
|
||||||
|
|
||||||
|
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-weight: 600; font-size: 0.875rem; cursor: pointer; border: none; text-decoration: none; display: inline-block; }
|
||||||
|
.btn-primary { background: var(--primary); color: white; }
|
||||||
|
.btn-outline { border: 1px solid var(--border); background: white; color: var(--text); }
|
||||||
|
|
||||||
|
/* Log Items */
|
||||||
|
.log-entry { padding: 1rem; border-left: 3px solid var(--primary); background: #f8fafc; margin-bottom: 1rem; border-radius: 0 8px 8px 0; }
|
||||||
|
.log-meta { display: flex; justify-content: space-between; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<nav>
|
||||||
<div class="card">
|
<div class="nav-header">PEF CMS</div>
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<div class="nav-links">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<a href="?view=dashboard" class="nav-link <?= $view === 'dashboard' ? 'active' : '' ?>">Overview</a>
|
||||||
<span class="sr-only">Loading…</span>
|
<a href="?view=projects" class="nav-link <?= $view === 'projects' ? 'active' : '' ?>">Projects</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
<div class="nav-footer">
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
<div class="user-info">
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
Logged in as: <strong><?= htmlspecialchars($user['full_name']) ?></strong><br>
|
||||||
|
<small><?= ucfirst($user['role']) ?></small>
|
||||||
|
</div>
|
||||||
|
<a href="logout.php" class="logout-btn">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="top-bar">
|
||||||
|
<h2 style="font-size: 1.125rem; margin: 0;"><?= ucfirst($view) ?></h2>
|
||||||
|
<div style="font-size: 0.875rem; color: var(--text-muted);">
|
||||||
|
<?= date('l, F j, Y') ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<?php if ($view === 'dashboard'): ?>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Total Projects</div>
|
||||||
|
<div class="stat-value"><?= $stats['total_projects'] ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Active Projects</div>
|
||||||
|
<div class="stat-value"><?= $stats['active_projects'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h3 class="card-title">Recent Activity</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($stats['latest_logs'])): ?>
|
||||||
|
<p style="text-align:center; color:var(--text-muted); padding: 2rem;">No recent activity. Start by creating a project or adding a log.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($stats['latest_logs'] as $log): ?>
|
||||||
|
<div class="log-entry">
|
||||||
|
<div class="log-meta">
|
||||||
|
<strong><?= htmlspecialchars($log['project_name']) ?> (v<?= htmlspecialchars($log['version']) ?>)</strong>
|
||||||
|
<span><?= $log['created_at'] ?></span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 0.9rem;"><?= htmlspecialchars($log['log_entry']) ?></div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.25rem;">By: <?= htmlspecialchars($log['author']) ?></div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($view === 'projects'): ?>
|
||||||
|
<?php if ($selectedProjectId): ?>
|
||||||
|
<!-- Project Detail View -->
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<a href="?view=projects" class="btn btn-outline">← Back to Projects</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><?= htmlspecialchars($selectedProject['name']) ?></h3>
|
||||||
|
<div style="display:flex; gap:0.5rem; align-items:center;">
|
||||||
|
<span class="badge badge-<?= $selectedProject['status'] ?>"><?= ucfirst($selectedProject['status']) ?></span>
|
||||||
|
<?php if ($user['role'] !== 'viewer'): ?>
|
||||||
|
<button onclick="openEditModal()" class="btn btn-outline" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;">Edit</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p><?= nl2br(htmlspecialchars($selectedProject['description'])) ?></p>
|
||||||
|
<div style="font-size: 0.875rem; color: var(--text-muted);">
|
||||||
|
Industry: <?= $selectedProject['industry'] ?> | Current Version: v<?= $selectedProject['current_version'] ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($user['role'] !== 'viewer'): ?>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h3 class="card-title">Add New Log & Version</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="add_log.php" method="POST" style="display: grid; gap: 1rem;">
|
||||||
|
<input type="hidden" name="project_id" value="<?= $selectedProjectId ?>">
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Version</label>
|
||||||
|
<input type="text" name="version" value="<?= htmlspecialchars($selectedProject['current_version']) ?>" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">What changed?</label>
|
||||||
|
<textarea name="log_entry" rows="3" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;" required placeholder="Detailed log entry..."></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" style="width:fit-content;">Post Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h3 class="card-title">Version History</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($logs)): ?>
|
||||||
|
<p style="text-align:center; color:var(--text-muted);">No logs available.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($logs as $log): ?>
|
||||||
|
<div class="log-entry">
|
||||||
|
<div class="log-meta">
|
||||||
|
<strong>Version <?= htmlspecialchars($log['version']) ?></strong>
|
||||||
|
<span><?= $log['created_at'] ?></span>
|
||||||
|
</div>
|
||||||
|
<div><?= nl2br(htmlspecialchars($log['log_entry'])) ?></div>
|
||||||
|
<div style="font-size:0.75rem; color:var(--text-muted); margin-top:0.5rem;">Author: <?= htmlspecialchars($log['author']) ?></div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Projects List -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Managed Projects</h3>
|
||||||
|
<?php if ($user['role'] !== 'viewer'): ?>
|
||||||
|
<button onclick="document.getElementById('addProjectModal').style.display='block'" class="btn btn-primary">+ New Project</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" style="padding:0;">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Project Name</th>
|
||||||
|
<th>Industry</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($projects as $p): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?= htmlspecialchars($p['name']) ?></strong></td>
|
||||||
|
<td><?= $p['industry'] ?></td>
|
||||||
|
<td><span class="badge badge-<?= $p['status'] ?>"><?= ucfirst($p['status']) ?></span></td>
|
||||||
|
<td>v<?= htmlspecialchars($p['current_version']) ?></td>
|
||||||
|
<td><?= date('Y-m-d', strtotime($p['updated_at'])) ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="?view=projects&project_id=<?= $p['id'] ?>" class="btn btn-outline" style="padding: 0.25rem 0.75rem;">View</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<!-- Modals -->
|
||||||
</footer>
|
<div id="addProjectModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:100; align-items:center; justify-content:center;">
|
||||||
|
<div style="background:white; padding:2rem; border-radius:12px; width:450px; position:relative; margin: 10% auto;">
|
||||||
|
<h3 style="margin-top:0;">Create New Project</h3>
|
||||||
|
<form action="manage_project.php?action=create" method="POST" style="display:grid; gap:1rem;">
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Name</label>
|
||||||
|
<input type="text" name="name" required style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Industry</label>
|
||||||
|
<select name="industry" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
|
||||||
|
<option>Construction</option>
|
||||||
|
<option>Education</option>
|
||||||
|
<option>Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Description</label>
|
||||||
|
<textarea name="description" rows="3" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;"></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Initial Version</label>
|
||||||
|
<input type="text" name="version" value="1.0.0" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:0.5rem; justify-content:flex-end; margin-top:1rem;">
|
||||||
|
<button type="button" onclick="this.closest('#addProjectModal').style.display='none'" class="btn btn-outline">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Create Project</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Project Modal -->
|
||||||
|
<?php if ($selectedProject): ?>
|
||||||
|
<div id="editProjectModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:100; align-items:center; justify-content:center;">
|
||||||
|
<div style="background:white; padding:2rem; border-radius:12px; width:450px; position:relative; margin: 10% auto;">
|
||||||
|
<h3 style="margin-top:0;">Edit Project</h3>
|
||||||
|
<form action="manage_project.php?action=update" method="POST" style="display:grid; gap:1rem;">
|
||||||
|
<input type="hidden" name="id" value="<?= $selectedProject['id'] ?>">
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Name</label>
|
||||||
|
<input type="text" name="name" value="<?= htmlspecialchars($selectedProject['name']) ?>" required style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Status</label>
|
||||||
|
<select name="status" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
|
||||||
|
<option value="active" <?= $selectedProject['status'] === 'active' ? 'selected' : '' ?>>Active</option>
|
||||||
|
<option value="completed" <?= $selectedProject['status'] === 'completed' ? 'selected' : '' ?>>Completed</option>
|
||||||
|
<option value="on_hold" <?= $selectedProject['status'] === 'on_hold' ? 'selected' : '' ?>>On Hold</option>
|
||||||
|
<option value="archived" <?= $selectedProject['status'] === 'archived' ? 'selected' : '' ?>>Archived</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Industry</label>
|
||||||
|
<select name="industry" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
|
||||||
|
<option <?= $selectedProject['industry'] === 'Construction' ? 'selected' : '' ?>>Construction</option>
|
||||||
|
<option <?= $selectedProject['industry'] === 'Education' ? 'selected' : '' ?>>Education</option>
|
||||||
|
<option <?= $selectedProject['industry'] === 'Other' ? 'selected' : '' ?>>Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Description</label>
|
||||||
|
<textarea name="description" rows="3" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;"><?= htmlspecialchars($selectedProject['description']) ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:0.5rem; justify-content:space-between; margin-top:1rem;">
|
||||||
|
<button type="button" onclick="if(confirm('Delete project?')) window.location.href='manage_project.php?action=delete&id=<?= $selectedProject['id'] ?>'" class="btn btn-outline" style="color:red; border-color:red;">Delete</button>
|
||||||
|
<div style="display:flex; gap:0.5rem;">
|
||||||
|
<button type="button" onclick="closeEditModal()" class="btn btn-outline">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<script>
|
||||||
|
function openEditModal() {
|
||||||
|
document.getElementById('editProjectModal').style.display = 'block';
|
||||||
|
}
|
||||||
|
function closeEditModal() {
|
||||||
|
document.getElementById('editProjectModal').style.display = 'none';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
63
login.php
Normal file
63
login.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/cls/class.pef.php';
|
||||||
|
|
||||||
|
if (isset($_SESSION['user'])) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$pef = new Pef();
|
||||||
|
$user = $pef->login($_POST['username'] ?? '', $_POST['password'] ?? '');
|
||||||
|
if ($user) {
|
||||||
|
$_SESSION['user'] = $user;
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Invalid username or password.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login - Construction & Education CMS</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Inter', sans-serif; background: #f8fafc; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
||||||
|
.login-card { background: white; padding: 2.5rem; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); width: 100%; max-width: 400px; }
|
||||||
|
h1 { margin-top: 0; font-size: 1.5rem; color: #1e293b; text-align: center; }
|
||||||
|
.form-group { margin-bottom: 1rem; }
|
||||||
|
label { display: block; font-size: 0.875rem; font-weight: 500; color: #64748b; margin-bottom: 0.5rem; }
|
||||||
|
input { width: 100%; padding: 0.75rem; border: 1px solid #e2e8f0; border-radius: 6px; box-sizing: border-box; }
|
||||||
|
button { width: 100%; padding: 0.75rem; background: #3b82f6; color: white; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; margin-top: 1rem; }
|
||||||
|
button:hover { background: #2563eb; }
|
||||||
|
.error { color: #ef4444; font-size: 0.875rem; text-align: center; margin-bottom: 1rem; }
|
||||||
|
.footer-note { text-align: center; margin-top: 1.5rem; font-size: 0.75rem; color: #94a3b8; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-card">
|
||||||
|
<h1>CMS Login</h1>
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="error"><?= htmlspecialchars($error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" name="username" required autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Password</label>
|
||||||
|
<input type="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Sign In</button>
|
||||||
|
</form>
|
||||||
|
<div class="footer-note">Default: admin / admin123</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
logout.php
Normal file
5
logout.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_destroy();
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
32
manage_project.php
Normal file
32
manage_project.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/cls/class.pef.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user']) || $_SESSION['user']['role'] === 'viewer') {
|
||||||
|
die('Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pef = new Pef();
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'create') {
|
||||||
|
$pef->createProject(
|
||||||
|
$_POST['name'],
|
||||||
|
$_POST['description'],
|
||||||
|
$_POST['industry'],
|
||||||
|
$_POST['version'] ?: '1.0.0'
|
||||||
|
);
|
||||||
|
} elseif ($action === 'update') {
|
||||||
|
$pef->updateProject((int)$_POST['id'], [
|
||||||
|
'name' => $_POST['name'],
|
||||||
|
'description' => $_POST['description'],
|
||||||
|
'industry' => $_POST['industry'],
|
||||||
|
'status' => $_POST['status']
|
||||||
|
]);
|
||||||
|
} elseif ($action === 'delete') {
|
||||||
|
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
$pef->deleteProject($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
23
migrate.php
Normal file
23
migrate.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
function runMigrations() {
|
||||||
|
$pdo = db();
|
||||||
|
$migrationsDir = __DIR__ . '/db/migrations';
|
||||||
|
$files = glob($migrationsDir . '/*.sql');
|
||||||
|
sort($files);
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$sql = file_get_contents($file);
|
||||||
|
try {
|
||||||
|
$pdo->exec($sql);
|
||||||
|
echo "Successfully ran migration: " . basename($file) . "\n";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Error running migration " . basename($file) . ": " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (php_sapi_name() === 'cli' || isset($_GET['run'])) {
|
||||||
|
runMigrations();
|
||||||
|
}
|
||||||
18
seed.php
Normal file
18
seed.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/cls/class.pef.php';
|
||||||
|
|
||||||
|
$pef = new Pef();
|
||||||
|
|
||||||
|
// Check if we already have projects
|
||||||
|
if (count($pef->getProjects()) === 0) {
|
||||||
|
$p1 = $pef->createProject('Bridge Construction A1', 'Main bridge construction for the highway project.', 'Construction', '1.0.0');
|
||||||
|
$pef->addLog($p1, '1.0.0', 'Project initiated.', 'Admin');
|
||||||
|
$pef->addLog($p1, '1.1.0', 'Foundation completed.', 'Site Manager');
|
||||||
|
|
||||||
|
$p2 = $pef->createProject('Online Learning Platform', 'LMS for local schools.', 'Education', '0.9.0');
|
||||||
|
$pef->addLog($p2, '0.9.0', 'Beta version launched for testing.', 'Dev Team');
|
||||||
|
|
||||||
|
echo "Sample data seeded.\n";
|
||||||
|
} else {
|
||||||
|
echo "Database already contains data.\n";
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user