Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d0660a262 |
145
admin_dashboard.php
Normal file
145
admin_dashboard.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$leave_requests = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT leave_requests.*, users.full_name AS student_name FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE leave_requests.status = 'approved_by_teacher' ORDER BY leave_requests.created_at DESC");
|
||||
$stmt->execute();
|
||||
$leave_requests = $stmt->fetchAll();
|
||||
} catch (PDOException $e) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// Monthly report
|
||||
$report = [];
|
||||
$month = date('Y-m');
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT status, COUNT(*) as count FROM leave_requests WHERE DATE_FORMAT(created_at, '%Y-%m') = ? GROUP BY status");
|
||||
$stmt->execute([$month]);
|
||||
$result = $stmt->fetchAll();
|
||||
foreach ($result as $row) {
|
||||
$report[$row['status']] = $row['count'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
$import_success_message = '';
|
||||
if (isset($_GET['import_success']) && $_GET['import_success'] == 1) {
|
||||
$import_success_message = 'Students imported successfully!';
|
||||
}
|
||||
|
||||
$import_error_message = '';
|
||||
if (isset($_GET['import_error']) && $_GET['import_error'] == 1) {
|
||||
$import_error_message = 'Error importing students. Please check the file and try again.';
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Admin Dashboard</h1>
|
||||
<div>
|
||||
<span class="me-3">Welcome, <?php echo htmlspecialchars($_SESSION['user_full_name']); ?>!</span>
|
||||
<a href="logout.php" class="btn btn-primary">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($import_success_message): ?>
|
||||
<div class="alert alert-success" role="alert">
|
||||
<?php echo $import_success_message; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($import_error_message): ?>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<?php echo $import_error_message; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<h2>Pending Leave Requests for Final Approval</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student Name</th>
|
||||
<th>Leave Type</th>
|
||||
<th>Start Date</th>
|
||||
<th>End Date</th>
|
||||
<th>Reason</th>
|
||||
<th>Attachment</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($leave_requests as $request): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($request['student_name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['leave_type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['start_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['end_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['reason']); ?></td>
|
||||
<td>
|
||||
<?php if ($request['attachment_path']): ?>
|
||||
<a href="<?php echo htmlspecialchars($request['attachment_path']); ?>" target="_blank">View Attachment</a>
|
||||
<?php else: ?>
|
||||
No Attachment
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="update_leave_status.php?id=<?php echo $request['id']; ?>&status=approved_by_admin" class="btn btn-success btn-sm">Approve</a>
|
||||
<a href="update_leave_status.php?id=<?php echo $request['id']; ?>&status=rejected_by_admin" class="btn btn-danger btn-sm">Reject</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<h2>Monthly Report (<?php echo date('F Y'); ?>)</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($report as $status => $count): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($status); ?></td>
|
||||
<td><?php echo $count; ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="export_report.php" class="btn btn-primary">Export to CSV</a>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h2>Import Students</h2>
|
||||
<form action="import_students.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="student_file" class="form-label">Select CSV file</label>
|
||||
<input type="file" class="form-control" id="student_file" name="student_file" accept=".csv" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Import Students</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
39
assets/css/style.css
Normal file
39
assets/css/style.css
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.form-signin .form-floating:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
150
blog.php
Normal file
150
blog.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>My Awesome App</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<?php include 'includes/header.php'; ?>
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
|
||||
<h1 class="my-4">Page Heading
|
||||
<small>Secondary Text</small>
|
||||
</h1>
|
||||
|
||||
<!-- Blog Post -->
|
||||
<div class="card mb-4">
|
||||
<img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Post Title</h2>
|
||||
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis aliquid atque, nulla? Quos cum ex quis soluta, a laboriosam. Dicta expedita corporis animi vero voluptate voluptatibus possimus, veniam magni quis!</p>
|
||||
<a href="#" class="btn btn-primary">Read More →</a>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
Posted on January 1, 2020 by
|
||||
<a href="#">Start Bootstrap</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Post -->
|
||||
<div class="card mb-4">
|
||||
<img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Post Title</h2>
|
||||
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis aliquid atque, nulla? Quos cum ex quis soluta, a laboriosam. Dicta expedita corporis animi vero voluptate voluptatibus possimus, veniam magni quis!</p>
|
||||
<a href="#" class="btn btn-primary">Read More →</a>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
Posted on January 1, 2020 by
|
||||
<a href="#">Start Bootstrap</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Post -->
|
||||
<div class="card mb-4">
|
||||
<img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Post Title</h2>
|
||||
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis aliquid atque, nulla? Quos cum ex quis soluta, a laboriosam. Dicta expedita corporis animi vero voluptate voluptatibus possimus, veniam magni quis!</p>
|
||||
<a href="#" class="btn btn-primary">Read More →</a>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
Posted on January 1, 2020 by
|
||||
<a href="#">Start Bootstrap</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<ul class="pagination justify-content-center mb-4">
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">← Older</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#">Newer →</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Widgets Column -->
|
||||
<div class="col-md-4">
|
||||
|
||||
<!-- Search Widget -->
|
||||
<div class="card my-4">
|
||||
<h5 class="card-header">Search</h5>
|
||||
<div class="card-body">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search for...">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-secondary" type="button">Go!</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categories Widget -->
|
||||
<div class="card my-4">
|
||||
<h5 class="card-header">Categories</h5>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li>
|
||||
<a href="#">Web Design</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">HTML</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Freebies</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li>
|
||||
<a href="#">JavaScript</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">CSS</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Tutorials</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Widget -->
|
||||
<div class="card my-4">
|
||||
<h5 class="card-header">Side Widget</h5>
|
||||
<div class="card-body">
|
||||
You can put anything you want inside of these side widgets. They are easy to use, and feature the new Bootstrap 4 card containers!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
</div>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
</body>
|
||||
</html>
|
||||
5
composer.json
Normal file
5
composer.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"parsecsv/php-parsecsv": "^1.3"
|
||||
}
|
||||
}
|
||||
83
composer.lock
generated
Normal file
83
composer.lock
generated
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "710967b1d9d7c2e29226ea5c1834b93a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "parsecsv/php-parsecsv",
|
||||
"version": "1.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/parsecsv/parsecsv-for-php.git",
|
||||
"reference": "2d6236cae09133e0533d34ed45ba1e1ecafffebb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/parsecsv/parsecsv-for-php/zipball/2d6236cae09133e0533d34ed45ba1e1ecafffebb",
|
||||
"reference": "2d6236cae09133e0533d34ed45ba1e1ecafffebb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/support": "Fluent array interface for map functions"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParseCsv\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Myhrberg",
|
||||
"email": "contact@jimeh.me"
|
||||
},
|
||||
{
|
||||
"name": "William Knauss",
|
||||
"email": "will.knauss@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Susann Sgorzaly",
|
||||
"homepage": "https://github.com/susgo"
|
||||
},
|
||||
{
|
||||
"name": "Christian Bläul",
|
||||
"homepage": "https://github.com/Fonata"
|
||||
}
|
||||
],
|
||||
"description": "CSV data parser for PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/parsecsv/parsecsv-for-php/issues",
|
||||
"source": "https://github.com/parsecsv/parsecsv-for-php"
|
||||
},
|
||||
"time": "2021-11-07T14:15:46+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
BIN
composer.phar
Executable file
BIN
composer.phar
Executable file
Binary file not shown.
18
cron/send_reminders.php
Normal file
18
cron/send_reminders.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../mail/MailService.php';
|
||||
|
||||
// Get all pending leave requests older than 24 hours
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT leave_requests.*, users.full_name AS student_name FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE status = 'pending' AND created_at < NOW() - INTERVAL 24 HOUR");
|
||||
$stmt->execute();
|
||||
$requests = $stmt->fetchAll();
|
||||
|
||||
foreach ($requests as $request) {
|
||||
// Send reminder email to teacher
|
||||
$teacher_email = 'teacher@example.com'; // Hardcoded for now
|
||||
$subject = 'Reminder: Pending Leave Request from ' . $request['student_name'];
|
||||
$body = "<p>This is a reminder that a leave request from {$request['student_name']} is still pending your approval.</p>\n <p>Please login to the dashboard to review the request.</p>";
|
||||
MailService::sendMail($teacher_email, $subject, $body);
|
||||
}
|
||||
|
||||
51
db/migrations/001_create_initial_tables.sql
Normal file
51
db/migrations/001_create_initial_tables.sql
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
-- Create users table
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`email` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`role` ENUM('student', 'teacher', 'admin') NOT NULL,
|
||||
`full_name` VARCHAR(255) DEFAULT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Create leave_requests table
|
||||
CREATE TABLE IF NOT EXISTS `leave_requests` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`student_id` INT NOT NULL,
|
||||
`start_date` DATE NOT NULL,
|
||||
`end_date` DATE NOT NULL,
|
||||
`reason` TEXT,
|
||||
`status` ENUM('pending_teacher', 'pending_admin', 'approved', 'rejected') NOT NULL DEFAULT 'pending_teacher',
|
||||
`teacher_id` INT DEFAULT NULL,
|
||||
`teacher_approved_at` DATETIME DEFAULT NULL,
|
||||
`teacher_rejection_reason` TEXT DEFAULT NULL,
|
||||
`admin_id` INT DEFAULT NULL,
|
||||
`admin_approved_at` DATETIME DEFAULT NULL,
|
||||
`admin_rejection_reason` TEXT DEFAULT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`student_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`teacher_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`admin_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Create leave_request_attachments table
|
||||
CREATE TABLE IF NOT EXISTS `leave_request_attachments` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`leave_request_id` INT NOT NULL,
|
||||
`file_path` VARCHAR(255) NOT NULL,
|
||||
`original_filename` VARCHAR(255) DEFAULT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`leave_request_id`) REFERENCES `leave_requests`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Create password_resets table
|
||||
CREATE TABLE IF NOT EXISTS `password_resets` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
`token` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
8
db/migrations/002_create_password_resets_table.sql
Normal file
8
db/migrations/002_create_password_resets_table.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS `password_resets` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(255) NOT NULL,
|
||||
`token` varchar(255) NOT NULL,
|
||||
`expires_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `token` (`token`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
13
db/migrations/003_create_leave_requests_table.sql
Normal file
13
db/migrations/003_create_leave_requests_table.sql
Normal file
@ -0,0 +1,13 @@
|
||||
CREATE TABLE IF NOT EXISTS `leave_requests` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`student_id` int(11) NOT NULL,
|
||||
`leave_type` varchar(255) NOT NULL,
|
||||
`start_date` date NOT NULL,
|
||||
`end_date` date NOT NULL,
|
||||
`reason` text NOT NULL,
|
||||
`attachment_path` varchar(255) DEFAULT NULL,
|
||||
`status` varchar(255) NOT NULL DEFAULT 'pending',
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `student_id` (`student_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
41
export_report.php
Normal file
41
export_report.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$month = date('Y-m');
|
||||
$filename = "leave_report_{$month}.csv";
|
||||
|
||||
header('Content-Type: text/csv');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
fputcsv($output, ['Student Name', 'Leave Type', 'Start Date', 'End Date', 'Reason', 'Status']);
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT users.full_name AS student_name, leave_requests.* FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE DATE_FORMAT(leave_requests.created_at, '%Y-%m') = ?");
|
||||
$stmt->execute([$month]);
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
fputcsv($output, [
|
||||
$row['student_name'],
|
||||
$row['leave_type'],
|
||||
$row['start_date'],
|
||||
$row['end_date'],
|
||||
$row['reason'],
|
||||
$row['status']
|
||||
]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
75
forgot_password.php
Normal file
75
forgot_password.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/mail/MailService.php';
|
||||
|
||||
$message = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = $_POST['email'] ?? '';
|
||||
|
||||
if (empty($email)) {
|
||||
$message = 'Please enter your email address.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user) {
|
||||
// Generate a unique token
|
||||
$token = bin2hex(random_bytes(32));
|
||||
|
||||
// Set expiration date to 1 hour from now
|
||||
$expires_at = date('Y-m-d H:i:s', strtotime('+1 hour'));
|
||||
|
||||
// Store the token in the database
|
||||
$stmt = $pdo->prepare("INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$email, $token, $expires_at]);
|
||||
|
||||
// Send the password reset link
|
||||
$reset_link = 'http://' . $_SERVER['HTTP_HOST'] . '/reset_password.php?token=' . $token;
|
||||
|
||||
// For now, we just display the link. Later we will send it by email.
|
||||
$message = 'Password reset link: <a href="' . $reset_link . '">' . $reset_link . '</a>';
|
||||
|
||||
} else {
|
||||
$message = 'If your email address exists in our database, you will receive a password reset link.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
<h2>Forgot Password</h2>
|
||||
<p>Please enter your email address to receive a password reset link.</p>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<?php echo $message; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email address</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Send Reset Link</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
43
import_students.php
Normal file
43
import_students.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'admin') {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
use ParseCsv\Csv;
|
||||
|
||||
if (isset($_FILES['student_file']) && $_FILES['student_file']['error'] === UPLOAD_ERR_OK) {
|
||||
$csv = new Csv();
|
||||
$csv->auto($_FILES['student_file']['tmp_name']);
|
||||
|
||||
$pdo = db();
|
||||
|
||||
foreach ($csv->data as $row) {
|
||||
$full_name = $row['full_name'];
|
||||
$email = $row['email'];
|
||||
|
||||
// Generate a random password
|
||||
$password = bin2hex(random_bytes(8));
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO users (full_name, email, password, role) VALUES (?, ?, ?, 'student')");
|
||||
$stmt->execute([$full_name, $email, $hashed_password]);
|
||||
} catch (PDOException $e) {
|
||||
// Handle duplicate email or other errors
|
||||
// For now, we just skip the row
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: admin_dashboard.php?import_success=1');
|
||||
exit;
|
||||
} else {
|
||||
header('Location: admin_dashboard.php?import_error=1');
|
||||
exit;
|
||||
}
|
||||
10
includes/footer.php
Normal file
10
includes/footer.php
Normal file
@ -0,0 +1,10 @@
|
||||
<footer class="py-3 my-4">
|
||||
<ul class="nav justify-content-center border-bottom pb-3 mb-3">
|
||||
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Home</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Features</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Pricing</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">FAQs</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">About</a></li>
|
||||
</ul>
|
||||
<p class="text-center text-muted">© 2025 Awesome App, Inc</p>
|
||||
</footer>
|
||||
10
includes/header.php
Normal file
10
includes/header.php
Normal file
@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Login - School Leave System</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
225
index.php
225
index.php
@ -4,147 +4,92 @@ declare(strict_types=1);
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
// Redirect if already logged in
|
||||
if (isset($_SESSION['user_role'])) {
|
||||
header('Location: ' . $_SESSION['user_role'] . '_dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$error_message = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$role = $_POST['role'] ?? '';
|
||||
|
||||
if (empty($email) || empty($password) || empty($role)) {
|
||||
$error_message = 'Please provide email, password, and select a role.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND role = ?");
|
||||
$stmt->execute([$email, $role]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// Password is correct, set session variables
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_role'] = $user['role'];
|
||||
$_SESSION['user_email'] = $user['email'];
|
||||
$_SESSION['user_full_name'] = $user['full_name'];
|
||||
|
||||
// Redirect to the respective dashboard
|
||||
header('Location: ' . $user['role'] . '_dashboard.php');
|
||||
exit;
|
||||
} else {
|
||||
$error_message = 'Invalid email, password, or role.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error_message = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</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.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<body class="text-center">
|
||||
<main class="form-signin">
|
||||
<form method="POST">
|
||||
<h1 class="h3 mb-3 fw-normal">School Leave System</h1>
|
||||
<p class="mb-4">Please sign in to continue</p>
|
||||
|
||||
<?php if ($error_message): ?>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<?php echo htmlspecialchars($error_message); ?>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="floatingInput" name="email" placeholder="name@example.com" required>
|
||||
<label for="floatingInput">Email address</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input type="password" class="form-control" id="floatingPassword" name="password" placeholder="Password" required>
|
||||
<label for="floatingPassword">Password</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<select class="form-select" id="floatingRole" name="role" required>
|
||||
<option value="" selected disabled>Select your role</option>
|
||||
<option value="student">Siswa (Student)</option>
|
||||
<option value="teacher">Wali Kelas (Homeroom Teacher)</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
<label for="floatingRole">Role</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox mb-3">
|
||||
<label>
|
||||
<input type="checkbox" value="remember-me"> Remember me
|
||||
</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
|
||||
<p class="mt-3">
|
||||
<a href="forgot_password.php">Forgot password?</a>
|
||||
</p>
|
||||
<p class="mt-5 mb-3 text-muted">© <?php echo date("Y"); ?>. All rights reserved.</p>
|
||||
</form>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
5
logout.php
Normal file
5
logout.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
101
reset_password.php
Normal file
101
reset_password.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$token = $_GET['token'] ?? '';
|
||||
$message = '';
|
||||
$show_form = false;
|
||||
|
||||
if (empty($token)) {
|
||||
$message = 'Invalid password reset token.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM password_resets WHERE token = ? AND expires_at > NOW()");
|
||||
$stmt->execute([$token]);
|
||||
$reset_request = $stmt->fetch();
|
||||
|
||||
if ($reset_request) {
|
||||
$show_form = true;
|
||||
} else {
|
||||
$message = 'Invalid or expired password reset token.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$password = $_POST['password'] ?? '';
|
||||
$password_confirm = $_POST['password_confirm'] ?? '';
|
||||
|
||||
if (empty($password) || empty($password_confirm)) {
|
||||
$message = 'Please enter and confirm your new password.';
|
||||
} elseif ($password !== $password_confirm) {
|
||||
$message = 'Passwords do not match.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM password_resets WHERE token = ? AND expires_at > NOW()");
|
||||
$stmt->execute([$token]);
|
||||
$reset_request = $stmt->fetch();
|
||||
|
||||
if ($reset_request) {
|
||||
$email = $reset_request['email'];
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
// Update user's password
|
||||
$stmt = $pdo->prepare("UPDATE users SET password = ? WHERE email = ?");
|
||||
$stmt->execute([$hashed_password, $email]);
|
||||
|
||||
// Delete the reset token
|
||||
$stmt = $pdo->prepare("DELETE FROM password_resets WHERE token = ?");
|
||||
$stmt->execute([$token]);
|
||||
|
||||
$message = 'Your password has been reset successfully. You can now <a href="index.php">login</a> with your new password.';
|
||||
$show_form = false;
|
||||
} else {
|
||||
$message = 'Invalid or expired password reset token.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
<h2>Reset Password</h2>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<?php echo $message; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_form): ?>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">New Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password_confirm" class="form-label">Confirm New Password</label>
|
||||
<input type="password" class="form-control" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Reset Password</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
103
student_dashboard.php
Normal file
103
student_dashboard.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'student') {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$success_message = '';
|
||||
if (isset($_GET['success']) && $_GET['success'] == 1) {
|
||||
$success_message = 'Leave request submitted successfully!';
|
||||
}
|
||||
|
||||
$leave_requests = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM leave_requests WHERE student_id = ? ORDER BY created_at DESC");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$leave_requests = $stmt->fetchAll();
|
||||
} catch (PDOException $e) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Student Dashboard</h1>
|
||||
<div>
|
||||
<span class="me-3">Welcome, <?php echo htmlspecialchars($_SESSION['user_full_name']); ?>!</span>
|
||||
<a href="logout.php" class="btn btn-primary">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($success_message): ?>
|
||||
<div class="alert alert-success" role="alert">
|
||||
<?php echo $success_message; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<h2>Submit Leave Request</h2>
|
||||
<form action="submit_leave_request.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="leave_type" class="form-label">Leave Type</label>
|
||||
<select class="form-select" id="leave_type" name="leave_type" required>
|
||||
<option value="" selected disabled>Select leave type</option>
|
||||
<option value="sick_leave">Sick Leave</option>
|
||||
<option value="vacation">Vacation</option>
|
||||
<option value="personal">Personal</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="start_date" class="form-label">Start Date</label>
|
||||
<input type="date" class="form-control" id="start_date" name="start_date" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="end_date" class="form-label">End Date</label>
|
||||
<input type="date" class="form-control" id="end_date" name="end_date" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="reason" class="form-label">Reason</label>
|
||||
<textarea class="form-control" id="reason" name="reason" rows="3" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="attachment" class="form-label">Attachment (optional)</label>
|
||||
<input type="file" class="form-control" id="attachment" name="attachment">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit Request</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h2>My Leave Requests</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Leave Type</th>
|
||||
<th>Start Date</th>
|
||||
<th>End Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($leave_requests as $request): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($request['leave_type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['start_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['end_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['status']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
52
submit_leave_request.php
Normal file
52
submit_leave_request.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'student') {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/mail/MailService.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$student_id = $_SESSION['user_id'];
|
||||
$leave_type = $_POST['leave_type'] ?? '';
|
||||
$start_date = $_POST['start_date'] ?? '';
|
||||
$end_date = $_POST['end_date'] ?? '';
|
||||
$reason = $_POST['reason'] ?? '';
|
||||
|
||||
// Basic validation
|
||||
if (empty($leave_type) || empty($start_date) || empty($end_date) || empty($reason)) {
|
||||
die('Please fill all required fields.');
|
||||
}
|
||||
|
||||
$attachment_path = null;
|
||||
if (isset($_FILES['attachment']) && $_FILES['attachment']['error'] === UPLOAD_ERR_OK) {
|
||||
$upload_dir = __DIR__ . '/uploads/';
|
||||
$attachment_name = uniqid() . '-' . basename($_FILES['attachment']['name']);
|
||||
$attachment_path = $upload_dir . $attachment_name;
|
||||
|
||||
if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $attachment_path)) {
|
||||
die('Failed to upload attachment.');
|
||||
}
|
||||
$attachment_path = 'uploads/' . $attachment_name; // Store relative path
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO leave_requests (student_id, leave_type, start_date, end_date, reason, attachment_path) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$student_id, $leave_type, $start_date, $end_date, $reason, $attachment_path]);
|
||||
|
||||
// Send email to teacher
|
||||
$teacher_email = 'teacher@example.com'; // Hardcoded for now
|
||||
$subject = 'New Leave Request from ' . $_SESSION['user_full_name'];
|
||||
$body = "<p>A new leave request has been submitted by {" . $_SESSION['user_full_name'] . "}.</p>\n <p><b>Leave Type:</b> {" . $leave_type . "}</p>\n <p><b>Start Date:</b> {" . $start_date . "}</p>\n <p><b>End Date:</b> {" . $end_date . "}</p>\n <p><b>Reason:</b> {" . $reason . "}</p>\n <p>Please login to the dashboard to approve or reject this request.</p>";
|
||||
MailService::sendMail($teacher_email, $subject, $body);
|
||||
|
||||
header('Location: student_dashboard.php?success=1');
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die('Database error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
75
teacher_dashboard.php
Normal file
75
teacher_dashboard.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'teacher') {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
$leave_requests = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT leave_requests.*, users.full_name AS student_name FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE leave_requests.status = 'pending' ORDER BY leave_requests.created_at DESC");
|
||||
$stmt->execute();
|
||||
$leave_requests = $stmt->fetchAll();
|
||||
} catch (PDOException $e) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Teacher Dashboard</h1>
|
||||
<div>
|
||||
<span class="me-3">Welcome, <?php echo htmlspecialchars($_SESSION['user_full_name']); ?>!</span>
|
||||
<a href="logout.php" class="btn btn-primary">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<h2>Pending Leave Requests</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student Name</th>
|
||||
<th>Leave Type</th>
|
||||
<th>Start Date</th>
|
||||
<th>End Date</th>
|
||||
<th>Reason</th>
|
||||
<th>Attachment</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($leave_requests as $request): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($request['student_name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['leave_type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['start_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['end_date']); ?></td>
|
||||
<td><?php echo htmlspecialchars($request['reason']); ?></td>
|
||||
<td>
|
||||
<?php if ($request['attachment_path']): ?>
|
||||
<a href="<?php echo htmlspecialchars($request['attachment_path']); ?>" target="_blank">View Attachment</a>
|
||||
<?php else: ?>
|
||||
No Attachment
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="update_leave_status.php?id=<?php echo $request['id']; ?>&status=approved_by_teacher" class="btn btn-success btn-sm">Approve</a>
|
||||
<a href="update_leave_status.php?id=<?php echo $request['id']; ?>&status=rejected_by_teacher" class="btn btn-danger btn-sm">Reject</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
85
update_leave_status.php
Normal file
85
update_leave_status.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_role'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/mail/MailService.php';
|
||||
|
||||
if (isset($_GET['id']) && isset($_GET['status'])) {
|
||||
$leave_request_id = $_GET['id'];
|
||||
$new_status = $_GET['status'];
|
||||
|
||||
if ($_SESSION['user_role'] === 'teacher') {
|
||||
$allowed_statuses = ['approved_by_teacher', 'rejected_by_teacher'];
|
||||
if (!in_array($new_status, $allowed_statuses)) {
|
||||
die('Invalid status.');
|
||||
}
|
||||
} elseif ($_SESSION['user_role'] === 'admin') {
|
||||
$allowed_statuses = ['approved_by_admin', 'rejected_by_admin'];
|
||||
if (!in_array($new_status, $allowed_statuses)) {
|
||||
die('Invalid status.');
|
||||
}
|
||||
} else {
|
||||
die('You are not authorized to perform this action.');
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE leave_requests SET status = ? WHERE id = ?");
|
||||
$stmt->execute([$new_status, $leave_request_id]);
|
||||
|
||||
// Get student email
|
||||
$stmt = $pdo->prepare("SELECT users.email, users.full_name FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE leave_requests.id = ?");
|
||||
$stmt->execute([$leave_request_id]);
|
||||
$student = $stmt->fetch();
|
||||
|
||||
if ($student) {
|
||||
$student_email = $student['email'];
|
||||
$student_name = $student['full_name'];
|
||||
|
||||
if ($new_status === 'approved_by_teacher') {
|
||||
// Notify admin
|
||||
$admin_email = 'admin@example.com'; // Hardcoded for now
|
||||
$subject = 'Leave Request Approved by Teacher';
|
||||
$body = "<p>The leave request for {$student_name} has been approved by the teacher and is waiting for your final approval.</p><p>Please login to the dashboard to review the request.</p>";
|
||||
MailService::sendMail($admin_email, $subject, $body);
|
||||
|
||||
// Notify student
|
||||
$subject_student = 'Your Leave Request has been updated';
|
||||
$body_student = "<p>Your leave request has been approved by your teacher and is now pending final approval from the admin.</p>";
|
||||
MailService::sendMail($student_email, $subject_student, $body_student);
|
||||
|
||||
} elseif ($new_status === 'rejected_by_teacher') {
|
||||
// Notify student
|
||||
$subject_student = 'Your Leave Request has been updated';
|
||||
$body_student = "<p>Your leave request has been rejected by your teacher.</p>";
|
||||
MailService::sendMail($student_email, $subject_student, $body_student);
|
||||
|
||||
} elseif ($new_status === 'approved_by_admin') {
|
||||
// Notify student
|
||||
$subject_student = 'Your Leave Request has been approved';
|
||||
$body_student = "<p>Your leave request has been approved by the admin.</p>";
|
||||
MailService::sendMail($student_email, $subject_student, $body_student);
|
||||
|
||||
} elseif ($new_status === 'rejected_by_admin') {
|
||||
// Notify student
|
||||
$subject_student = 'Your Leave Request has been rejected';
|
||||
$body_student = "<p>Your leave request has been rejected by the admin.</p>";
|
||||
MailService::sendMail($student_email, $subject_student, $body_student);
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SESSION['user_role'] === 'teacher') {
|
||||
header('Location: teacher_dashboard.php');
|
||||
} elseif ($_SESSION['user_role'] === 'admin') {
|
||||
header('Location: admin_dashboard.php');
|
||||
}
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die('Database error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
22
vendor/autoload.php
vendored
Normal file
22
vendor/autoload.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitf6815287adc097f2e9e30b4ea978d3c7::getLoader();
|
||||
579
vendor/composer/ClassLoader.php
vendored
Normal file
579
vendor/composer/ClassLoader.php
vendored
Normal file
@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
396
vendor/composer/InstalledVersions.php
vendored
Normal file
396
vendor/composer/InstalledVersions.php
vendored
Normal file
@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
21
vendor/composer/LICENSE
vendored
Normal file
21
vendor/composer/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
10
vendor/composer/autoload_classmap.php
vendored
Normal file
10
vendor/composer/autoload_classmap.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
10
vendor/composer/autoload_psr4.php
vendored
Normal file
10
vendor/composer/autoload_psr4.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'ParseCsv\\' => array($vendorDir . '/parsecsv/php-parsecsv/src'),
|
||||
);
|
||||
38
vendor/composer/autoload_real.php
vendored
Normal file
38
vendor/composer/autoload_real.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitf6815287adc097f2e9e30b4ea978d3c7
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitf6815287adc097f2e9e30b4ea978d3c7', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitf6815287adc097f2e9e30b4ea978d3c7', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
36
vendor/composer/autoload_static.php
vendored
Normal file
36
vendor/composer/autoload_static.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'P' =>
|
||||
array (
|
||||
'ParseCsv\\' => 9,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'ParseCsv\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/parsecsv/php-parsecsv/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
73
vendor/composer/installed.json
vendored
Normal file
73
vendor/composer/installed.json
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "parsecsv/php-parsecsv",
|
||||
"version": "1.3.2",
|
||||
"version_normalized": "1.3.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/parsecsv/parsecsv-for-php.git",
|
||||
"reference": "2d6236cae09133e0533d34ed45ba1e1ecafffebb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/parsecsv/parsecsv-for-php/zipball/2d6236cae09133e0533d34ed45ba1e1ecafffebb",
|
||||
"reference": "2d6236cae09133e0533d34ed45ba1e1ecafffebb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/support": "Fluent array interface for map functions"
|
||||
},
|
||||
"time": "2021-11-07T14:15:46+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParseCsv\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Myhrberg",
|
||||
"email": "contact@jimeh.me"
|
||||
},
|
||||
{
|
||||
"name": "William Knauss",
|
||||
"email": "will.knauss@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Susann Sgorzaly",
|
||||
"homepage": "https://github.com/susgo"
|
||||
},
|
||||
{
|
||||
"name": "Christian Bläul",
|
||||
"homepage": "https://github.com/Fonata"
|
||||
}
|
||||
],
|
||||
"description": "CSV data parser for PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/parsecsv/parsecsv-for-php/issues",
|
||||
"source": "https://github.com/parsecsv/parsecsv-for-php"
|
||||
},
|
||||
"install-path": "../parsecsv/php-parsecsv"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": []
|
||||
}
|
||||
32
vendor/composer/installed.php
vendored
Normal file
32
vendor/composer/installed.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '2a30fbdcdbc8f6f2a59d2d0d87a5d6b9e8549fd6',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '2a30fbdcdbc8f6f2a59d2d0d87a5d6b9e8549fd6',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'parsecsv/php-parsecsv' => array(
|
||||
'pretty_version' => '1.3.2',
|
||||
'version' => '1.3.2.0',
|
||||
'reference' => '2d6236cae09133e0533d34ed45ba1e1ecafffebb',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../parsecsv/php-parsecsv',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
25
vendor/composer/platform_check.php
vendored
Normal file
25
vendor/composer/platform_check.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 50500)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.5.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
38
vendor/parsecsv/php-parsecsv/.github/workflows/ci.yml
vendored
Normal file
38
vendor/parsecsv/php-parsecsv/.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php_version:
|
||||
- "7.4"
|
||||
- "7.3"
|
||||
- "7.2"
|
||||
- "7.1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php_version }}
|
||||
env:
|
||||
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
run: composer update
|
||||
- name: Validate dependencies
|
||||
run: composer validate
|
||||
- name: Run tests
|
||||
run: vendor/bin/phpunit --configuration tests/phpunit.xml
|
||||
21
vendor/parsecsv/php-parsecsv/License.txt
vendored
Normal file
21
vendor/parsecsv/php-parsecsv/License.txt
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
(The MIT license)
|
||||
|
||||
Copyright (c) 2014 Jim Myhrberg.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
246
vendor/parsecsv/php-parsecsv/README.md
vendored
Normal file
246
vendor/parsecsv/php-parsecsv/README.md
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
# ParseCsv
|
||||
[](https://opencollective.com/parsecsv)
|
||||
|
||||
ParseCsv is an easy-to-use PHP class that reads and writes CSV data properly. It
|
||||
fully conforms to the specifications outlined on the on the
|
||||
[Wikipedia article][CSV] (and thus RFC 4180). It has many advanced features which help make your
|
||||
life easier when dealing with CSV data.
|
||||
|
||||
You may not need a library at all: before using ParseCsv, please make sure if PHP's own `str_getcsv()`, ``fgetcsv()`` or `fputcsv()` meets your needs.
|
||||
|
||||
This library was originally created in early 2007 by [jimeh](https://github.com/jimeh) due to the lack of built-in
|
||||
and third-party support for handling CSV data in PHP.
|
||||
|
||||
[csv]: http://en.wikipedia.org/wiki/Comma-separated_values
|
||||
|
||||
## Features
|
||||
|
||||
* ParseCsv is a complete and fully featured CSV solution for PHP
|
||||
* Supports enclosed values, enclosed commas, double quotes and new lines.
|
||||
* Automatic delimiter character detection.
|
||||
* Sort data by specific fields/columns.
|
||||
* Easy data manipulation.
|
||||
* Basic SQL-like _conditions_, _offset_ and _limit_ options for filtering
|
||||
data.
|
||||
* Error detection for incorrectly formatted input. It attempts to be
|
||||
intelligent, but can not be trusted 100% due to the structure of CSV, and
|
||||
how different programs like Excel for example outputs CSV data.
|
||||
* Support for character encoding conversion using PHP's
|
||||
`iconv()` and `mb_convert_encoding()` functions.
|
||||
* Supports PHP 5.5 and higher.
|
||||
It certainly works with PHP 7.2 and all versions in between.
|
||||
|
||||
## Installation
|
||||
|
||||
Installation is easy using Composer. Just run the following on the
|
||||
command line:
|
||||
```
|
||||
composer require parsecsv/php-parsecsv
|
||||
```
|
||||
|
||||
If you don't use a framework such as Drupal, Laravel, Symfony, Yii etc.,
|
||||
you may have to manually include Composer's autoloader file in your PHP
|
||||
script:
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
```
|
||||
|
||||
#### Without composer
|
||||
Not recommended, but technically possible: you can also clone the
|
||||
repository or extract the
|
||||
[ZIP](https://github.com/parsecsv/parsecsv-for-php/archive/master.zip).
|
||||
To use ParseCSV, you then have to add a `require 'parsecsv.lib.php';` line.
|
||||
|
||||
## Example Usage
|
||||
|
||||
**Parse a tab-delimited CSV file with encoding conversion**
|
||||
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->encoding('UTF-16', 'UTF-8');
|
||||
$csv->delimiter = "\t";
|
||||
$csv->parseFile('data.tsv');
|
||||
print_r($csv->data);
|
||||
```
|
||||
|
||||
**Auto-detect field delimiter character**
|
||||
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->auto('data.csv');
|
||||
print_r($csv->data);
|
||||
```
|
||||
|
||||
**Parse data with offset**
|
||||
* ignoring the first X (e.g. two) rows
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->offset = 2;
|
||||
$csv->parseFile('data.csv');
|
||||
print_r($csv->data);
|
||||
```
|
||||
|
||||
**Limit the number of returned data rows**
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->limit = 5;
|
||||
$csv->parseFile('data.csv');
|
||||
print_r($csv->data);
|
||||
```
|
||||
|
||||
**Get total number of data rows without parsing whole data**
|
||||
* Excluding heading line if present (see $csv->header property)
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->loadFile('data.csv');
|
||||
$count = $csv->getTotalDataRowCount();
|
||||
print_r($count);
|
||||
```
|
||||
|
||||
**Get most common data type for each column**
|
||||
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv('data.csv');
|
||||
$csv->getDatatypes();
|
||||
print_r($csv->data_types);
|
||||
```
|
||||
|
||||
**Modify data in a CSV file**
|
||||
|
||||
Change data values:
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->sort_by = 'id';
|
||||
$csv->parseFile('data.csv');
|
||||
# "4" is the value of the "id" column of the CSV row
|
||||
$csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@doe.com');
|
||||
$csv->save();
|
||||
```
|
||||
|
||||
Enclose each data value by quotes:
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->parseFile('data.csv');
|
||||
$csv->enclose_all = true;
|
||||
$csv->save();
|
||||
```
|
||||
|
||||
**Replace field names or set ones if missing**
|
||||
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->fields = ['id', 'name', 'category'];
|
||||
$csv->parseFile('data.csv');
|
||||
```
|
||||
|
||||
**Add row/entry to end of CSV file**
|
||||
|
||||
_Only recommended when you know the exact structure of the file._
|
||||
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->save('data.csv', array(array('1986', 'Home', 'Nowhere', '')), /* append */ true);
|
||||
```
|
||||
|
||||
**Convert 2D array to CSV data and send headers to browser to treat output as
|
||||
a file and download it**
|
||||
|
||||
Your web app users would call this an export.
|
||||
|
||||
```php
|
||||
$csv = new \ParseCsv\Csv();
|
||||
$csv->linefeed = "\n";
|
||||
$header = array('field 1', 'field 2');
|
||||
$csv->output('movies.csv', $data_array, $header, ',');
|
||||
```
|
||||
|
||||
For more complex examples, see the ``tests`` and `examples` directories.
|
||||
|
||||
## Test coverage
|
||||
|
||||
All tests are located in the `tests` directory. To execute tests, run the following commands:
|
||||
|
||||
````bash
|
||||
composer install
|
||||
composer run test
|
||||
````
|
||||
|
||||
When pushing code to GitHub, tests will be executed using GitHub Actions. The relevant configuration is in the
|
||||
file `.github/workflows/ci.yml`. To run the `test` action locally, you can execute the following command:
|
||||
|
||||
````bash
|
||||
make local-ci
|
||||
````
|
||||
|
||||
## Security
|
||||
|
||||
If you discover any security related issues, please email ParseCsv@blaeul.de instead of using GitHub issues.
|
||||
|
||||
## Credits
|
||||
|
||||
* ParseCsv is based on the concept of [Ming Hong Ng][ming]'s [CsvFileParser][]
|
||||
class.
|
||||
|
||||
[ming]: http://minghong.blogspot.com/
|
||||
[CsvFileParser]: http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
Please find a complete list on the project's [contributors][] page.
|
||||
|
||||
[contributors]: https://github.com/parsecsv/parsecsv-for-php/graphs/contributors
|
||||
<a href="https://github.com/parsecsv/parsecsv-for-php/graphs/contributors"><img src="https://opencollective.com/parsecsv/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/parsecsv/contribute)]
|
||||
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/parsecsv"><img src="https://opencollective.com/parsecsv/individuals.svg?width=890"></a>
|
||||
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/parsecsv/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/parsecsv/organization/0/website"><img src="https://opencollective.com/parsecsv/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/1/website"><img src="https://opencollective.com/parsecsv/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/2/website"><img src="https://opencollective.com/parsecsv/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/3/website"><img src="https://opencollective.com/parsecsv/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/4/website"><img src="https://opencollective.com/parsecsv/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/5/website"><img src="https://opencollective.com/parsecsv/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/6/website"><img src="https://opencollective.com/parsecsv/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/7/website"><img src="https://opencollective.com/parsecsv/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/8/website"><img src="https://opencollective.com/parsecsv/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/parsecsv/organization/9/website"><img src="https://opencollective.com/parsecsv/organization/9/avatar.svg"></a>
|
||||
|
||||
## License
|
||||
|
||||
(The MIT license)
|
||||
|
||||
Copyright (c) 2014 Jim Myhrberg.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
[](https://travis-ci.org/parsecsv/parsecsv-for-php)
|
||||
57
vendor/parsecsv/php-parsecsv/composer.json
vendored
Normal file
57
vendor/parsecsv/php-parsecsv/composer.json
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "parsecsv/php-parsecsv",
|
||||
"description": "CSV data parser for PHP",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Myhrberg",
|
||||
"email": "contact@jimeh.me"
|
||||
},
|
||||
{
|
||||
"name": "William Knauss",
|
||||
"email": "will.knauss@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Susann Sgorzaly",
|
||||
"homepage": "https://github.com/susgo"
|
||||
},
|
||||
{
|
||||
"name": "Christian Bläul",
|
||||
"homepage": "https://github.com/Fonata"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParseCsv\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ParseCsv\\tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/support": "Fluent array interface for map functions"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"vendor/bin/phpunit -c tests tests --disallow-test-output --coverage-clover coverage_clover.xml --whitelist src"
|
||||
]
|
||||
},
|
||||
"support": {
|
||||
"issues": "https://github.com/parsecsv/parsecsv-for-php/issues",
|
||||
"source": "https://github.com/parsecsv/parsecsv-for-php"
|
||||
}
|
||||
}
|
||||
24
vendor/parsecsv/php-parsecsv/parsecsv.lib.php
vendored
Normal file
24
vendor/parsecsv/php-parsecsv/parsecsv.lib.php
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
// This file should not be used at all! Instead, please use Composer's autoload.
|
||||
// It purely exists to reduce the maintenance burden for existing code using
|
||||
// this repository.
|
||||
|
||||
// Check if people used Composer to include this project in theirs
|
||||
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||
require __DIR__ . '/src/enums/AbstractEnum.php';
|
||||
require __DIR__ . '/src/enums/DatatypeEnum.php';
|
||||
require __DIR__ . '/src/enums/FileProcessingModeEnum.php';
|
||||
require __DIR__ . '/src/enums/SortEnum.php';
|
||||
require __DIR__ . '/src/extensions/DatatypeTrait.php';
|
||||
require __DIR__ . '/src/Csv.php';
|
||||
} else {
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
|
||||
// This wrapper class should not be used by new projects. Please look at the
|
||||
// examples to find the up-to-date way of using this repo.
|
||||
class parseCSV extends ParseCsv\Csv {
|
||||
|
||||
}
|
||||
1472
vendor/parsecsv/php-parsecsv/src/Csv.php
vendored
Normal file
1472
vendor/parsecsv/php-parsecsv/src/Csv.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
38
vendor/parsecsv/php-parsecsv/src/enums/AbstractEnum.php
vendored
Normal file
38
vendor/parsecsv/php-parsecsv/src/enums/AbstractEnum.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace ParseCsv\enums;
|
||||
|
||||
abstract class AbstractEnum {
|
||||
|
||||
/**
|
||||
* Creates a new value of some type
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws \UnexpectedValueException if incompatible type is given.
|
||||
*/
|
||||
public function __construct($value) {
|
||||
if (!$this->isValid($value)) {
|
||||
throw new \UnexpectedValueException("Value '$value' is not part of the enum " . get_called_class());
|
||||
}
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public static function getConstants() {
|
||||
$class = get_called_class();
|
||||
$reflection = new \ReflectionClass($class);
|
||||
|
||||
return $reflection->getConstants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if enum value is valid
|
||||
*
|
||||
* @param $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValid($value) {
|
||||
return in_array($value, static::getConstants(), true);
|
||||
}
|
||||
}
|
||||
120
vendor/parsecsv/php-parsecsv/src/enums/DatatypeEnum.php
vendored
Normal file
120
vendor/parsecsv/php-parsecsv/src/enums/DatatypeEnum.php
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace ParseCsv\enums;
|
||||
|
||||
/**
|
||||
* Class DatatypeEnum
|
||||
*
|
||||
* @package ParseCsv\enums
|
||||
*
|
||||
* todo: needs a basic parent enum class for error handling.
|
||||
*/
|
||||
class DatatypeEnum extends AbstractEnum {
|
||||
|
||||
const __DEFAULT = self::TYPE_STRING;
|
||||
|
||||
const TYPE_STRING = 'string';
|
||||
|
||||
const TYPE_FLOAT = 'float';
|
||||
|
||||
const TYPE_INT = 'integer';
|
||||
|
||||
const TYPE_BOOL = 'boolean';
|
||||
|
||||
const TYPE_DATE = 'date';
|
||||
|
||||
const REGEX_FLOAT = '/(^[+-]?$)|(^[+-]?[0-9]+([,.][0-9])?[0-9]*(e[+-]?[0-9]+)?$)/';
|
||||
|
||||
const REGEX_INT = '/^[-+]?[0-9]\d*$/';
|
||||
|
||||
const REGEX_BOOL = '/^(?i:true|false)$/';
|
||||
|
||||
/**
|
||||
* Define validator functions here.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @uses isValidFloat
|
||||
* @uses isValidInteger
|
||||
* @uses isValidBoolean
|
||||
* @uses isValidDate
|
||||
*/
|
||||
private static $validators = array(
|
||||
self::TYPE_STRING => null,
|
||||
self::TYPE_INT => 'isValidInteger',
|
||||
self::TYPE_BOOL => 'isValidBoolean',
|
||||
self::TYPE_FLOAT => 'isValidFloat',
|
||||
self::TYPE_DATE => 'isValidDate',
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks data type for given string.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function getValidTypeFromSample($value) {
|
||||
$value = trim((string) $value);
|
||||
|
||||
if (empty($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (self::$validators as $type => $validator) {
|
||||
if ($validator === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method_exists(__CLASS__, $validator) && self::$validator($value)) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
return self::__DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is float value.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isValidFloat($value) {
|
||||
return (bool) preg_match(self::REGEX_FLOAT, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is integer value.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isValidInteger($value) {
|
||||
return (bool) preg_match(self::REGEX_INT, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is boolean.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isValidBoolean($value) {
|
||||
return (bool) preg_match(self::REGEX_BOOL, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is date.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isValidDate($value) {
|
||||
return (bool) strtotime($value);
|
||||
}
|
||||
}
|
||||
28
vendor/parsecsv/php-parsecsv/src/enums/FileProcessingModeEnum.php
vendored
Normal file
28
vendor/parsecsv/php-parsecsv/src/enums/FileProcessingModeEnum.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace ParseCsv\enums;
|
||||
|
||||
|
||||
/**
|
||||
* Class FileProcessingEnum
|
||||
*
|
||||
* @package ParseCsv\enums
|
||||
*
|
||||
* todo extends a basic enum class after merging #121
|
||||
*/
|
||||
class FileProcessingModeEnum {
|
||||
|
||||
const __default = self::MODE_FILE_OVERWRITE;
|
||||
|
||||
const MODE_FILE_APPEND = true;
|
||||
|
||||
const MODE_FILE_OVERWRITE = false;
|
||||
|
||||
public static function getAppendMode($mode) {
|
||||
if ($mode == self::MODE_FILE_APPEND) {
|
||||
return 'ab';
|
||||
}
|
||||
|
||||
return 'wb';
|
||||
}
|
||||
}
|
||||
29
vendor/parsecsv/php-parsecsv/src/enums/SortEnum.php
vendored
Normal file
29
vendor/parsecsv/php-parsecsv/src/enums/SortEnum.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace ParseCsv\enums;
|
||||
|
||||
|
||||
class SortEnum extends AbstractEnum {
|
||||
|
||||
const __DEFAULT = self::SORT_TYPE_REGULAR;
|
||||
|
||||
const SORT_TYPE_REGULAR = 'regular';
|
||||
|
||||
const SORT_TYPE_NUMERIC = 'numeric';
|
||||
|
||||
const SORT_TYPE_STRING = 'string';
|
||||
|
||||
private static $sorting = array(
|
||||
self::SORT_TYPE_REGULAR => SORT_REGULAR,
|
||||
self::SORT_TYPE_STRING => SORT_STRING,
|
||||
self::SORT_TYPE_NUMERIC => SORT_NUMERIC,
|
||||
);
|
||||
|
||||
public static function getSorting($type) {
|
||||
if (array_key_exists($type, self::$sorting)) {
|
||||
return self::$sorting[$type];
|
||||
}
|
||||
|
||||
return self::$sorting[self::__DEFAULT];
|
||||
}
|
||||
}
|
||||
102
vendor/parsecsv/php-parsecsv/src/extensions/DatatypeTrait.php
vendored
Normal file
102
vendor/parsecsv/php-parsecsv/src/extensions/DatatypeTrait.php
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace ParseCsv\extensions;
|
||||
|
||||
use ParseCsv\enums\DatatypeEnum;
|
||||
|
||||
trait DatatypeTrait {
|
||||
|
||||
/**
|
||||
* Data Types
|
||||
* Data types of CSV data-columns, keyed by the column name. Possible values
|
||||
* are string, float, integer, boolean, date. See DatatypeEnum.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data_types = [];
|
||||
|
||||
/**
|
||||
* Check data type for one column.
|
||||
* Check for most commonly data type for one column.
|
||||
*
|
||||
* @param array $datatypes
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private function getMostFrequentDatatypeForColumn($datatypes) {
|
||||
// remove 'false' from array (can happen if CSV cell is empty)
|
||||
$typesFiltered = array_filter($datatypes);
|
||||
|
||||
if (empty($typesFiltered)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$typesFreq = array_count_values($typesFiltered);
|
||||
arsort($typesFreq);
|
||||
reset($typesFreq);
|
||||
|
||||
return key($typesFreq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check data type foreach Column
|
||||
* Check data type for each column and returns the most commonly.
|
||||
*
|
||||
* Requires PHP >= 5.5
|
||||
*
|
||||
* @uses DatatypeEnum::getValidTypeFromSample
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function getDatatypes() {
|
||||
if (empty($this->data)) {
|
||||
$this->data = $this->_parse_string();
|
||||
}
|
||||
if (!is_array($this->data)) {
|
||||
throw new \UnexpectedValueException('No data set yet.');
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($this->titles as $cName) {
|
||||
$column = array_column($this->data, $cName);
|
||||
$cDatatypes = array_map(DatatypeEnum::class . '::getValidTypeFromSample', $column);
|
||||
|
||||
$result[$cName] = $this->getMostFrequentDatatypeForColumn($cDatatypes);
|
||||
}
|
||||
|
||||
$this->data_types = $result;
|
||||
|
||||
return !empty($this->data_types) ? $this->data_types : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check data type of titles / first row for auto detecting if this could be
|
||||
* a heading line.
|
||||
*
|
||||
* Requires PHP >= 5.5
|
||||
*
|
||||
* @uses DatatypeEnum::getValidTypeFromSample
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function autoDetectFileHasHeading() {
|
||||
if (empty($this->data)) {
|
||||
throw new \UnexpectedValueException('No data set yet.');
|
||||
}
|
||||
|
||||
if ($this->heading) {
|
||||
$firstRow = $this->titles;
|
||||
} else {
|
||||
$firstRow = $this->data[0];
|
||||
}
|
||||
|
||||
$firstRow = array_filter($firstRow);
|
||||
if (empty($firstRow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstRowDatatype = array_map(DatatypeEnum::class . '::getValidTypeFromSample', $firstRow);
|
||||
|
||||
return $this->getMostFrequentDatatypeForColumn($firstRowDatatype) === DatatypeEnum::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user