Updated the proostekrant to work properly again.
This commit is contained in:
Executable
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
// Application configuration
|
||||
return [
|
||||
'app_name' => 'ProostenKrant',
|
||||
'app_description' => 'Een krant van en voor Proosten',
|
||||
'app_slogan' => 'Een krant van en voor Proosten',
|
||||
|
||||
'universal_password' => getenv('UNIVERSAL_PASSWORD') ?: 'wachtwoord',
|
||||
|
||||
'upload_paths' => [
|
||||
'articles' => __DIR__ . '/../public/images/articles/',
|
||||
'profiles' => __DIR__ . '/../public/images/profiles/',
|
||||
],
|
||||
|
||||
'latex_paths' => [
|
||||
'templates' => __DIR__ . '/../templates/latex/',
|
||||
'output' => __DIR__ . '/../storage/latex/',
|
||||
],
|
||||
|
||||
'seasons' => [
|
||||
1 => ['name' => 'Lente', 'color' => '#90EE90'],
|
||||
2 => ['name' => 'Zomer', 'color' => '#FFD700'],
|
||||
3 => ['name' => 'Herfst', 'color' => '#FF8C00'],
|
||||
4 => ['name' => 'Winter', 'color' => '#ADD8E6'],
|
||||
],
|
||||
];
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
// Database configuration
|
||||
return [
|
||||
'host' => getenv('DB_HOST') ?: 'mysql',
|
||||
'database' => getenv('DB_NAME') ?: 'proostenkrant',
|
||||
'username' => getenv('DB_USER') ?: 'proostenkrant',
|
||||
'password' => getenv('DB_PASSWORD') ?: 'proostenkrant',
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
];
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
// Route configuration
|
||||
return [
|
||||
'GET' => [
|
||||
'/' => 'PublicController@index',
|
||||
'/index.php' => 'PublicController@index',
|
||||
'/archive' => 'PublicController@archive',
|
||||
|
||||
// Submission routes
|
||||
'/submit' => 'SubmissionController@showPassphrase',
|
||||
'/submit/passphrase' => 'SubmissionController@showPassphrase',
|
||||
'/submit/form' => 'SubmissionController@showForm',
|
||||
'/submit/confirmation' => 'SubmissionController@showConfirmation',
|
||||
'/submit/edit' => 'SubmissionController@showEditPassphrase',
|
||||
'/submit/edit/list' => 'SubmissionController@showEditList',
|
||||
'/submit/edit/(\d+)' => 'SubmissionController@showWriterVerification',
|
||||
|
||||
// Legacy routes (for logged in users)
|
||||
'/login' => 'AuthController@showLogin',
|
||||
'/logout' => 'AuthController@logout',
|
||||
'/dashboard' => 'DashboardController@index',
|
||||
'/articles' => 'ArticleController@index',
|
||||
'/articles/new' => 'ArticleController@new',
|
||||
'/articles/create' => 'ArticleController@create',
|
||||
'/articles/(\d+)' => 'ArticleController@show',
|
||||
'/articles/(\d+)/verify-writer' => 'ArticleController@showVerifyWriter',
|
||||
'/articles/(\d+)/verify-passphrase' => 'ArticleController@showVerifyPassphrase',
|
||||
'/seasons' => 'SeasonController@index',
|
||||
'/generate-pdf' => 'PdfController@generate',
|
||||
],
|
||||
'POST' => [
|
||||
// Submission routes
|
||||
'/submit/verify' => 'SubmissionController@verifyPassphrase',
|
||||
'/submit/store' => 'SubmissionController@store',
|
||||
'/submit/edit/verify' => 'SubmissionController@verifyEditPassphrase',
|
||||
'/submit/edit/verify-writer' => 'SubmissionController@verifyWriter',
|
||||
|
||||
// Legacy routes
|
||||
'/login' => 'AuthController@login',
|
||||
'/articles' => 'ArticleController@store',
|
||||
'/upload-image' => 'UploadController@upload',
|
||||
'/articles/verify-writer' => 'ArticleController@verifyWriter',
|
||||
'/articles/verify-passphrase' => 'ArticleController@verifyPassphrase',
|
||||
],
|
||||
];
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
# Redirect all requests to index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
</IfModule>
|
||||
|
||||
# Allow .htaccess overrides
|
||||
AllowOverride All
|
||||
|
||||
# PHP settings
|
||||
<IfModule mod_php.c>
|
||||
php_value upload_max_filesize 50M
|
||||
php_value post_max_size 52M
|
||||
php_value max_execution_time 300
|
||||
</IfModule>
|
||||
Executable
+1291
File diff suppressed because it is too large
Load Diff
Executable
+582
@@ -0,0 +1,582 @@
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #8B4513, #A0522D);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.logo a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.slogan {
|
||||
font-style: italic;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.location {
|
||||
font-size: 1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.nav a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.nav a.btn {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
.nav a.btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.nav a.btn-logout {
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
.nav a.btn-logout:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main {
|
||||
padding: 20px 0;
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: #8B4513;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert .close {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background-color: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.login-card h2 {
|
||||
text-align: center;
|
||||
color: #8B4513;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-card p {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #8B4513;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-control.error {
|
||||
border-color: #f44336;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f44336;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #8B4513;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #A0522D;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #607d8b;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #455a64;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.9rem;
|
||||
background-color: #607d8b;
|
||||
}
|
||||
|
||||
.btn-small:hover {
|
||||
background-color: #455a64;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.articles-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.articles-table th,
|
||||
.articles-table td {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.articles-table th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.articles-table tr:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.status-badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.published {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-badge.draft {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-badge.archived {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.article-card,
|
||||
.season-card,
|
||||
.dashboard-card {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.article-card h4,
|
||||
.season-card h4,
|
||||
.dashboard-card h3 {
|
||||
color: #8B4513;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.article-card .article-meta {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.article-card p {
|
||||
color: #444;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.article-image {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 5px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* Grids */
|
||||
.articles-grid,
|
||||
.seasons-grid,
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
/* Season Banner */
|
||||
.season-banner,
|
||||
.season-info {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin: 20px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.season-banner h3,
|
||||
.season-info h3 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Form Layout */
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-row .full-width {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.checkbox-label:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* Article Detail */
|
||||
.article-detail {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.article-detail h2 {
|
||||
color: #8B4513;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta-item strong {
|
||||
color: #8B4513;
|
||||
}
|
||||
|
||||
.article-image-container {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.article-image {
|
||||
max-width: 100%;
|
||||
max-height: 500px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.article-content {
|
||||
line-height: 1.8;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.article-actions {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* Quick Links */
|
||||
.quick-links {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.quick-links a {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: white;
|
||||
color: #8B4513;
|
||||
text-decoration: none;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.quick-links a:hover {
|
||||
background-color: #8B4513;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Season Detail */
|
||||
.season-detail {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.season-detail h2 {
|
||||
color: #8B4513;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.season-info {
|
||||
background-color: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.season-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
color: #8B4513;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.articles-grid,
|
||||
.seasons-grid,
|
||||
.dashboard-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print Styles */
|
||||
@media print {
|
||||
.nav,
|
||||
.header-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.article-card,
|
||||
.season-card,
|
||||
.dashboard-card {
|
||||
box-shadow: none;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
Executable
Executable
Executable
+114
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
// Error reporting
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Start session
|
||||
session_start();
|
||||
|
||||
// Set timezone
|
||||
ini_set('date.timezone', 'Europe/Brussels');
|
||||
|
||||
// Autoload classes
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'App\\';
|
||||
$baseDir = __DIR__ . '/../src/';
|
||||
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relativeClass = substr($class, $len);
|
||||
// Convert namespace separators to directory separators
|
||||
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||
|
||||
// Convert to lowercase for directory names
|
||||
$parts = explode('/', $file);
|
||||
$newParts = [];
|
||||
foreach ($parts as $part) {
|
||||
if (strpos($part, '.php') === false) {
|
||||
$newParts[] = strtolower($part);
|
||||
} else {
|
||||
$newParts[] = $part;
|
||||
}
|
||||
}
|
||||
$file = implode('/', $newParts);
|
||||
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
});
|
||||
|
||||
// Load configuration
|
||||
$config = include __DIR__ . '/../config/app.php';
|
||||
|
||||
// Database configuration
|
||||
$databaseConfig = include __DIR__ . '/../config/database.php';
|
||||
|
||||
// Set database environment variables for Docker
|
||||
if (!getenv('DB_HOST')) {
|
||||
putenv('DB_HOST=mysql');
|
||||
}
|
||||
if (!getenv('DB_NAME')) {
|
||||
putenv('DB_NAME=proostenkrant');
|
||||
}
|
||||
if (!getenv('DB_USER')) {
|
||||
putenv('DB_USER=proostenkrant');
|
||||
}
|
||||
if (!getenv('DB_PASSWORD')) {
|
||||
putenv('DB_PASSWORD=proostenkrant');
|
||||
}
|
||||
|
||||
// Load routes
|
||||
$routes = include __DIR__ . '/../config/routes.php';
|
||||
|
||||
// Get current request
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
|
||||
// Route matching
|
||||
$matched = false;
|
||||
foreach ($routes[$method] ?? [] as $pattern => $handler) {
|
||||
// Convert route pattern to regex
|
||||
// First, replace route parameter patterns before quoting
|
||||
$patternForRegex = preg_replace('/\(\d+\)/', '(\d+)', $pattern);
|
||||
$patternForRegex = preg_replace('/\(\w+\)/', '([^\/]+)', $patternForRegex);
|
||||
$regex = '#^' . $patternForRegex . '$#';
|
||||
|
||||
if (preg_match($regex, $uri, $matches)) {
|
||||
// Remove first match (full match)
|
||||
array_shift($matches);
|
||||
|
||||
// Parse handler
|
||||
list($controllerName, $actionName) = explode('@', $handler);
|
||||
|
||||
// Instantiate controller
|
||||
$controllerClass = 'App\\Controllers\\' . $controllerName;
|
||||
if (class_exists($controllerClass)) {
|
||||
$controller = new $controllerClass();
|
||||
|
||||
// Call action with parameters
|
||||
if (method_exists($controller, $actionName)) {
|
||||
call_user_func_array([$controller, $actionName], $matches);
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 404
|
||||
if (!$matched) {
|
||||
http_response_code(404);
|
||||
|
||||
// Show a simple 404 page
|
||||
echo '<!DOCTYPE html>';
|
||||
echo '<html lang="nl">';
|
||||
echo '<head><meta charset="UTF-8"><title>404 - Pagina niet gevonden</title></head>';
|
||||
echo '<body style="text-align: center; padding: 50px;">';
|
||||
echo '<h1>404 - Pagina niet gevonden</h1>';
|
||||
echo '<p><a href="/">Ga terug naar de homepagina</a></p>';
|
||||
echo '</body></html>';
|
||||
}
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
// ProostenKrant JavaScript
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Auto-hide alerts after 5 seconds
|
||||
const alerts = document.querySelectorAll('.alert');
|
||||
alerts.forEach(alert => {
|
||||
setTimeout(() => {
|
||||
alert.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
alert.remove();
|
||||
}, 500);
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// Form validation feedback
|
||||
const formGroups = document.querySelectorAll('.form-group');
|
||||
formGroups.forEach(group => {
|
||||
const input = group.querySelector('.form-control');
|
||||
const errorMessage = group.querySelector('.error-message');
|
||||
|
||||
if (input && errorMessage && errorMessage.textContent.trim() !== '') {
|
||||
input.addEventListener('focus', function() {
|
||||
input.classList.remove('error');
|
||||
errorMessage.style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-focus first input on login page
|
||||
if (document.querySelector('.login-form')) {
|
||||
const firstInput = document.querySelector('.login-form .form-control');
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth scrolling for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Image preview for article form
|
||||
const imageInputs = [document.getElementById('article_image'), document.getElementById('image')];
|
||||
imageInputs.forEach(input => {
|
||||
if (input) {
|
||||
const form = input.closest('form');
|
||||
const previewContainer = document.createElement('div');
|
||||
previewContainer.className = 'image-preview';
|
||||
previewContainer.style.display = 'none';
|
||||
previewContainer.style.marginTop = '15px';
|
||||
|
||||
input.parentNode.parentNode.appendChild(previewContainer);
|
||||
|
||||
input.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
previewContainer.innerHTML = '<img src="' + e.target.result + '" style="max-width: 200px; max-height: 200px; border-radius: 5px;" />';
|
||||
previewContainer.style.display = 'block';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
previewContainer.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Confirm before logout
|
||||
const logoutLinks = document.querySelectorAll('.btn-logout, a[href="/logout"]');
|
||||
logoutLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!confirm('Weet u zeker dat u wilt uitloggen?')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Season color indicator
|
||||
const seasonCards = document.querySelectorAll('.season-card');
|
||||
seasonCards.forEach(card => {
|
||||
const bgColor = card.style.backgroundColor;
|
||||
if (bgColor) {
|
||||
card.style.borderLeft = '5px solid ' + bgColor;
|
||||
}
|
||||
});
|
||||
});
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/var/www/html/storage/latex
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Season;
|
||||
use App\Models\User;
|
||||
|
||||
class ArticleController extends Controller {
|
||||
|
||||
public function index() {
|
||||
$this->requireAuth();
|
||||
|
||||
$articles = Article::all();
|
||||
$this->view('articles/index', ['articles' => $articles]);
|
||||
}
|
||||
|
||||
public function new() {
|
||||
$this->requireAuth();
|
||||
|
||||
$seasons = Season::all();
|
||||
$users = User::all();
|
||||
|
||||
$this->view('articles/new', [
|
||||
'seasons' => $seasons,
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function create() {
|
||||
// This will handle the form display for creating a new article
|
||||
$this->new();
|
||||
}
|
||||
|
||||
public function store() {
|
||||
$this->requireAuth();
|
||||
|
||||
$validationRules = [
|
||||
'title' => 'required|min:5|max:255',
|
||||
'content' => 'required|min:10',
|
||||
'author_id' => 'required',
|
||||
'event_date' => 'required|date',
|
||||
'season_id' => 'required',
|
||||
];
|
||||
|
||||
$errors = $this->validate($_POST, $validationRules);
|
||||
|
||||
if (!empty($errors)) {
|
||||
$_SESSION['errors'] = $errors;
|
||||
$_SESSION['old_input'] = $_POST;
|
||||
$this->redirect('/articles/new');
|
||||
}
|
||||
|
||||
// Process participants
|
||||
$participants = $_POST['participants'] ?? [];
|
||||
if (empty($participants)) {
|
||||
$participants = [$_POST['author_id']];
|
||||
}
|
||||
|
||||
$articleImage = null;
|
||||
if (isset($_FILES['article_image']) && $_FILES['article_image']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = $this->config['upload_paths']['articles'];
|
||||
$filename = uniqid() . '_' . basename($_FILES['article_image']['name']);
|
||||
$targetPath = $uploadDir . $filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['article_image']['tmp_name'], $targetPath)) {
|
||||
$articleImage = 'images/articles/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
$articleData = [
|
||||
'title' => $_POST['title'],
|
||||
'content' => $_POST['content'],
|
||||
'author_id' => $_POST['author_id'],
|
||||
'participants' => implode(',', $participants),
|
||||
'event_date' => $_POST['event_date'],
|
||||
'season_id' => $_POST['season_id'],
|
||||
'article_image' => $articleImage,
|
||||
'status' => 'published',
|
||||
];
|
||||
|
||||
$articleId = Article::create($articleData);
|
||||
|
||||
$_SESSION['success'] = 'Artikel succesvol toegevoegd!';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
public function show($id) {
|
||||
$article = Article::find($id);
|
||||
|
||||
if (!$article) {
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
$participantIds = explode(',', $article['participants']);
|
||||
$participants = User::getByIds($participantIds);
|
||||
|
||||
$this->view('articles/show', [
|
||||
'article' => $article,
|
||||
'participants' => $participants
|
||||
]);
|
||||
}
|
||||
|
||||
public function showVerifyWriter($id) {
|
||||
$this->requireAuth();
|
||||
|
||||
$article = Article::find($id);
|
||||
|
||||
if (!$article) {
|
||||
$_SESSION['error'] = 'Artikel niet gevonden.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
$users = User::all();
|
||||
|
||||
$this->view('articles/verify_writer', [
|
||||
'article' => $article,
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function showVerifyPassphrase($id) {
|
||||
$this->requireAuth();
|
||||
|
||||
$article = Article::find($id);
|
||||
|
||||
if (!$article) {
|
||||
$_SESSION['error'] = 'Artikel niet gevonden.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
// Ensure edit_article_id is set in session for the passphrase verification
|
||||
if (!isset($_SESSION['edit_article_id']) || $_SESSION['edit_article_id'] != $id) {
|
||||
$_SESSION['edit_article_id'] = $id;
|
||||
}
|
||||
|
||||
$this->view('articles/verify_passphrase', [
|
||||
'article' => $article
|
||||
]);
|
||||
}
|
||||
|
||||
public function verifyWriter() {
|
||||
$this->requireAuth();
|
||||
|
||||
$articleId = $_POST['article_id'] ?? null;
|
||||
$writerId = $_POST['writer_id'] ?? null;
|
||||
|
||||
if (!$articleId || !$writerId) {
|
||||
$_SESSION['error'] = 'Ongeldige verificatie.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
$article = Article::find($articleId);
|
||||
if (!$article) {
|
||||
$_SESSION['error'] = 'Artikel niet gevonden.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
if ($article['author_id'] != $writerId) {
|
||||
$_SESSION['error'] = 'Enkel de schrijver van het artikel kan deze bewerken.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
$this->redirect('/articles/' . $articleId . '/verify-passphrase');
|
||||
}
|
||||
|
||||
public function verifyPassphrase() {
|
||||
$this->requireAuth();
|
||||
|
||||
$articleId = $_SESSION['edit_article_id'] ?? null;
|
||||
$passphrase = $_POST['passphrase'] ?? '';
|
||||
$universalPassword = $this->config['universal_password'] ?? 'ProostenKrant2024!';
|
||||
|
||||
if (!$articleId) {
|
||||
$_SESSION['error'] = 'Geen artikel geselecteerd voor bewerken.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
$article = Article::find($articleId);
|
||||
if (!$article) {
|
||||
$_SESSION['error'] = 'Artikel niet gevonden.';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
if ($passphrase !== $universalPassword) {
|
||||
$_SESSION['error'] = 'Onjuiste paszin. Probeer het opnieuw.';
|
||||
$this->redirect('/articles/' . $articleId . '/verify-passphrase');
|
||||
}
|
||||
|
||||
// Redirect to submission form with article data
|
||||
$this->redirect('/submit/form');
|
||||
}
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Setting;
|
||||
|
||||
class AuthController extends Controller {
|
||||
|
||||
public function showLogin() {
|
||||
if ($this->isLoggedIn()) {
|
||||
$this->redirect('/dashboard');
|
||||
}
|
||||
|
||||
$this->view('login', ['page_title' => 'Inloggen']);
|
||||
}
|
||||
|
||||
public function login() {
|
||||
$password = $_POST['password'] ?? '';
|
||||
$username = $_POST['username'] ?? '';
|
||||
|
||||
$universalPassword = Setting::get('universal_password', $this->config['universal_password']);
|
||||
|
||||
$user = User::findByUsername($username);
|
||||
|
||||
if ($user && $password === $universalPassword) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['display_name'] = $user['display_name'];
|
||||
$_SESSION['is_logged_in'] = true;
|
||||
|
||||
$redirect = $_SESSION['redirect_after_login'] ?? '/dashboard';
|
||||
unset($_SESSION['redirect_after_login']);
|
||||
|
||||
$this->redirect($redirect);
|
||||
} else {
|
||||
$_SESSION['error'] = 'Ongeldige gebruikersnaam of wachtwoord';
|
||||
$this->redirect('/login');
|
||||
}
|
||||
}
|
||||
|
||||
public function logout() {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
$this->redirect('/login');
|
||||
}
|
||||
}
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
class Controller {
|
||||
protected $config;
|
||||
protected $data = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->config = include __DIR__ . '/../../config/app.php';
|
||||
}
|
||||
|
||||
protected function view($viewName, $data = []) {
|
||||
$this->data = array_merge($this->data, $data);
|
||||
|
||||
// Try different view directories
|
||||
$viewPaths = [
|
||||
__DIR__ . '/../views/' . $viewName . '.php',
|
||||
__DIR__ . '/../views/pages/' . $viewName . '.php',
|
||||
__DIR__ . '/../views/public/' . $viewName . '.php',
|
||||
__DIR__ . '/../views/submission/' . $viewName . '.php',
|
||||
__DIR__ . '/../views/articles/' . $viewName . '.php',
|
||||
__DIR__ . '/../views/seasons/' . $viewName . '.php',
|
||||
];
|
||||
|
||||
$viewPath = null;
|
||||
foreach ($viewPaths as $path) {
|
||||
if (file_exists($path)) {
|
||||
$viewPath = $path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$viewPath) {
|
||||
die("View not found: " . $viewName);
|
||||
}
|
||||
|
||||
// Make config available to views
|
||||
$this->data['config'] = $this->config;
|
||||
|
||||
extract($this->data);
|
||||
require $viewPath;
|
||||
}
|
||||
|
||||
protected function redirect($url) {
|
||||
header("Location: {$url}");
|
||||
exit();
|
||||
}
|
||||
|
||||
protected function validate($data, $rules) {
|
||||
$errors = [];
|
||||
|
||||
foreach ($rules as $field => $ruleString) {
|
||||
$rulesList = explode('|', $ruleString);
|
||||
|
||||
foreach ($rulesList as $rule) {
|
||||
$ruleParts = explode(':', $rule);
|
||||
$ruleName = $ruleParts[0];
|
||||
$ruleParam = $ruleParts[1] ?? null;
|
||||
|
||||
if ($ruleName === 'required' && empty($data[$field])) {
|
||||
$errors[$field] = "Het veld {$field} is verplicht";
|
||||
}
|
||||
|
||||
if ($ruleName === 'min' && isset($data[$field]) && strlen($data[$field]) < $ruleParam) {
|
||||
$errors[$field] = "Het veld {$field} moet minstens {$ruleParam} karakters lang zijn";
|
||||
}
|
||||
|
||||
if ($ruleName === 'max' && isset($data[$field]) && strlen($data[$field]) > $ruleParam) {
|
||||
$errors[$field] = "Het veld {$field} mag maximaal {$ruleParam} karakters lang zijn";
|
||||
}
|
||||
|
||||
if ($ruleName === 'date' && isset($data[$field]) && !$this->isValidDate($data[$field])) {
|
||||
$errors[$field] = "Het veld {$field} moet een geldige datum zijn";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
protected function isValidDate($date) {
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date);
|
||||
}
|
||||
|
||||
protected function isLoggedIn() {
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
protected function requireAuth() {
|
||||
if (!$this->isLoggedIn()) {
|
||||
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
|
||||
$this->redirect('/login');
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Season;
|
||||
use App\Models\User;
|
||||
|
||||
class DashboardController extends Controller {
|
||||
|
||||
public function index() {
|
||||
$this->requireAuth();
|
||||
|
||||
$currentSeason = Season::getCurrent();
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
// Get articles by current user
|
||||
$userArticles = Article::all();
|
||||
|
||||
// Get all users
|
||||
$users = User::all();
|
||||
|
||||
$this->view('dashboard', [
|
||||
'currentSeason' => $currentSeason,
|
||||
'userArticles' => $userArticles,
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Season;
|
||||
|
||||
class HomeController extends Controller {
|
||||
|
||||
public function index() {
|
||||
// Always show the public home page now
|
||||
$this->redirect('/');
|
||||
}
|
||||
|
||||
public function test() {
|
||||
$this->view('test', ['page_title' => 'Test']);
|
||||
}
|
||||
}
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Season;
|
||||
use App\Models\User;
|
||||
|
||||
class PdfController extends Controller {
|
||||
|
||||
public function generate() {
|
||||
$this->requireAuth();
|
||||
|
||||
$seasonId = $_GET['season_id'] ?? null;
|
||||
|
||||
if ($seasonId) {
|
||||
$articles = Article::getBySeason($seasonId);
|
||||
$season = Season::find($seasonId);
|
||||
} else {
|
||||
$articles = Article::all('published');
|
||||
$season = Season::getCurrent();
|
||||
}
|
||||
|
||||
if (empty($articles)) {
|
||||
$_SESSION['error'] = 'Geen artikelen gevonden voor deze editie';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
// Generate LaTeX
|
||||
$latexContent = $this->generateLatex($articles, $season);
|
||||
|
||||
// Save LaTeX file
|
||||
$latexDir = $this->config['latex_paths']['output'];
|
||||
if (!is_dir($latexDir)) {
|
||||
mkdir($latexDir, 0755, true);
|
||||
}
|
||||
|
||||
$latexFilename = $latexDir . 'ProostenKrant_' . ($season ? $season['name'] : 'Algemeen') . '_' . date('Y-m-d') . '.tex';
|
||||
file_put_contents($latexFilename, $latexContent);
|
||||
|
||||
// Generate PDF using LaTeX compiler
|
||||
$this->compileLatex($latexFilename);
|
||||
|
||||
$_SESSION['success'] = 'PDF succesvol gegenereerd!';
|
||||
$this->redirect('/articles');
|
||||
}
|
||||
|
||||
private function generateLatex($articles, $season = null) {
|
||||
$config = $this->config;
|
||||
|
||||
$latex = "\\documentclass{article}\\
|
||||
";
|
||||
$latex .= "\\usepackage[utf8]{inputenc}\\
|
||||
";
|
||||
$latex .= "\\usepackage[T1]{fontenc}\\
|
||||
";
|
||||
$latex .= "\\usepackage{microtype}\\
|
||||
";
|
||||
$latex .= "\\usepackage[dutch]{babel}\\
|
||||
";
|
||||
$latex .= "\\usepackage{newspaper}\\
|
||||
";
|
||||
$latex .= "\\usepackage{graphicx}\\
|
||||
";
|
||||
$latex .= "\\usepackage{tikz}\\
|
||||
";
|
||||
$latex .= "\\usepackage{multicol}\\
|
||||
";
|
||||
$latex .= "\\usepackage{geometry}\\
|
||||
";
|
||||
$latex .= "\\usepackage{array}\\
|
||||
";
|
||||
$latex .= "\\usepackage{wrapfig}\\
|
||||
";
|
||||
$latex .= "\\usepackage{lipsum}\\
|
||||
";
|
||||
$latex .= "\\usepackage{newtxtext,newtxmath}\\
|
||||
";
|
||||
$latex .= "\\usepackage{etoolbox}\\
|
||||
";
|
||||
|
||||
// Custom styling from original project
|
||||
$latex .= "\\newcommand{\\headlinestyle}{\\itshape\\huge}\\
|
||||
";
|
||||
$latex .= "\\newcommand{\\bylinestyle}{\\scshape\\Large}\\
|
||||
";
|
||||
$latex .= "\\patchcmd{\\headline}{#1}{\\headlinestyle #1}{}{}\\
|
||||
";
|
||||
$latex .= "\\patchcmd{\\byline}{#1}{\\bylinestyle #1}{}{}\\
|
||||
";
|
||||
|
||||
// Improved image handling commands
|
||||
$latex .= $this->getImageCommands();
|
||||
|
||||
// Document metadata
|
||||
$latex .= "\\\\date{\\today}\\
|
||||
";
|
||||
$latex .= "\\currentvolume{1}\\
|
||||
";
|
||||
$latex .= "\\currentissue{1}\\
|
||||
";
|
||||
$latex .= "\\SetPaperName{Proo{s:}tenKrant}\\
|
||||
";
|
||||
$latex .= "\\SetHeaderName{ProostenKrant}\\
|
||||
";
|
||||
$latex .= "\\SetPaperLocation{$config['app_location']}\\
|
||||
";
|
||||
$latex .= "\\SetPaperSlogan{{$config['app_slogan']}}\\
|
||||
";
|
||||
$latex .= "\\SetPaperPrice{$config['app_price']}\\
|
||||
";
|
||||
|
||||
$latex .= "\\\\begin{document}\\
|
||||
";
|
||||
$latex .= "\\maketitle\\\\\\
|
||||
";
|
||||
|
||||
// Determine season name
|
||||
$seasonName = $season ? $season['name'] : 'Algemeen';
|
||||
$latex .= "\\\\noindent\\textbf{\\LARGE Editie: {$seasonName}}\\
|
||||
";
|
||||
$latex .= "\\\\\\
|
||||
";
|
||||
|
||||
$latex .= "\\\\begin{multicols}{3}\\
|
||||
";
|
||||
|
||||
// Add articles
|
||||
foreach ($articles as $article) {
|
||||
$latex .= $this->articleToLatex($article);
|
||||
}
|
||||
|
||||
$latex .= "\\\\end{multicols}\\
|
||||
";
|
||||
$latex .= "\\end{document}";
|
||||
|
||||
return $latex;
|
||||
}
|
||||
|
||||
private function getImageCommands() {
|
||||
return
|
||||
"\\newcommand{\\showInlineImage}[2]\n" .
|
||||
"{\n" .
|
||||
" \\begin{center}\n" .
|
||||
" \\begin{minipage}{0.9\\columnwidth}\n" .
|
||||
" \\centering\n" .
|
||||
" \\framebox{\n" .
|
||||
" \\resizebox{\\linewidth}{!}{\\\\includegraphics{#1}}\n" .
|
||||
" }\\\\\n" .
|
||||
" \\small #2\\\n" .
|
||||
" \\end{minipage}\n" .
|
||||
" \\end{center}\n" .
|
||||
"}\n\n" .
|
||||
|
||||
"\\newcommand{\\showPortraitImage}[2]\n" .
|
||||
"{\n" .
|
||||
" \\begin{minipage}[t]{0.45\\columnwidth}\n" .
|
||||
" \\centering\n" .
|
||||
" \\begin{tikzpicture}\n" .
|
||||
" \\clip (0,0) circle (1.5cm);\n" .
|
||||
" \\node[anchor=center] at (0,0) {\\\\includegraphics[width=3cm, height=3cm, keepaspectratio]{#1}};\n" .
|
||||
" \\end{tikzpicture}\n" .
|
||||
" \\smallskip\\\n" .
|
||||
" #2\\\n" .
|
||||
" \\end{minipage}\n" .
|
||||
"}\n\n" .
|
||||
|
||||
"\\newcommand{\\showLandscapeImage}[2]\n" .
|
||||
"{\n" .
|
||||
" \\begin{minipage}[t]{\\columnwidth}\n" .
|
||||
" \\centering\n" .
|
||||
" \\includegraphics[width=\\linewidth, keepaspectratio]{#1}\\par\n" .
|
||||
" \\small #2\\\n" .
|
||||
" \\end{minipage}\n" .
|
||||
"}\n\n" .
|
||||
|
||||
"\\newcommand{\\showOneWriter}[2]\n" .
|
||||
"{\n" .
|
||||
" \\hfill\n" .
|
||||
" \\begin{minipage}[t]{0.4\\columnwidth}\n" .
|
||||
" \\centering\n" .
|
||||
" \\begin{tikzpicture}\n" .
|
||||
" \\clip (0,0) circle (1.2cm);\n" .
|
||||
" \\node[anchor=center] at (0,0) {\\\\includegraphics[width=2.5cm, height=2.5cm, keepaspectratio]{#1}};\n" .
|
||||
" \\end{tikzpicture}\n" .
|
||||
" \\\n" .
|
||||
" \\small #2\\\n" .
|
||||
" \\end{minipage}\n" .
|
||||
" \\hfill\n" .
|
||||
"}\n\n" .
|
||||
|
||||
"\\newcommand{\\showTwoWriters}[4]\n" .
|
||||
"{\n" .
|
||||
" \\begin{center}\n" .
|
||||
" \\begin{minipage}[t]{0.8\\columnwidth}\n" .
|
||||
" \\centering\n" .
|
||||
" \\begin{tikzpicture}\n" .
|
||||
" \\clip (0,0) circle (1cm);\n" .
|
||||
" \\node[anchor=west] at (0,0) {\\\\includegraphics[width=2cm, height=2cm, keepaspectratio]{#1}};\n" .
|
||||
" \\end{tikzpicture}\n" .
|
||||
" \\hspace{0.5cm}\n" .
|
||||
" \\begin{tikzpicture}\n" .
|
||||
" \\clip (0,0) circle (1cm);\n" .
|
||||
" \\node[anchor=east] at (0,0) {\\\\includegraphics[width=2cm, height=2cm, keepaspectratio]{#3}};\n" .
|
||||
" \\end{tikzpicture}\n" .
|
||||
" \\\n" .
|
||||
" \\small #2 \\quad #4\\\n" .
|
||||
" \\end{minipage}\n" .
|
||||
" \\end{center}\n" .
|
||||
"}\n\n";
|
||||
}
|
||||
|
||||
private function articleToLatex($article) {
|
||||
$latex = "\\headline{{$article['title']}}\\
|
||||
";
|
||||
$latex .= "\\byline{{$article['event_date']}}{{$article['author_name']}}\\
|
||||
";
|
||||
|
||||
// Get base public path from config
|
||||
$uploadPaths = $this->config['upload_paths'] ?? [];
|
||||
$profilesPath = $uploadPaths['profiles'] ?? __DIR__ . '/../../public/images/profiles/';
|
||||
$articlesPath = $uploadPaths['articles'] ?? __DIR__ . '/../../public/images/articles/';
|
||||
$publicPath = dirname(dirname($profilesPath)) . '/';
|
||||
|
||||
// Get participants
|
||||
$participantIds = explode(',', $article['participants']);
|
||||
$participants = User::getByIds($participantIds);
|
||||
|
||||
if (count($participants) === 1) {
|
||||
$profileImage = $participants[0]['profile_image'] ? $publicPath . $participants[0]['profile_image'] : $profilesPath . 'Unknown.jpg';
|
||||
$latex .= "\\showOneWriter{{$profileImage}}{{$participants[0]['display_name']}}\\
|
||||
";
|
||||
} elseif (count($participants) === 2) {
|
||||
$profileImage1 = $participants[0]['profile_image'] ? $publicPath . $participants[0]['profile_image'] : $profilesPath . 'Unknown.jpg';
|
||||
$profileImage2 = $participants[1]['profile_image'] ? $publicPath . $participants[1]['profile_image'] : $profilesPath . 'Unknown.jpg';
|
||||
$latex .= "\\showTwoWriters{{$profileImage1}}{{$participants[0]['display_name']}}{{$profileImage2}}{{$participants[1]['display_name']}}\\
|
||||
";
|
||||
}
|
||||
|
||||
// Content
|
||||
$content = $article['content'];
|
||||
// Escape special LaTeX characters
|
||||
$content = str_replace(['#', '$', '%', '&', '_', '{', '}', '\\', '~', '^'],
|
||||
['\\#', '\\$', '\\%', '\\&', '\\_', '\\{', '\\}', '\\\\', '\\~', '\\^'],
|
||||
$content);
|
||||
$latex .= "\\par {$content}\\par\n\\\n";
|
||||
|
||||
// Article image
|
||||
if ($article['article_image']) {
|
||||
$imagePath = $publicPath . $article['article_image'];
|
||||
// Check if image exists
|
||||
if (file_exists($imagePath)) {
|
||||
$latex .= "\\showInlineImage{{$imagePath}}{}\\
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
$latex .= "\\\n\\\n";
|
||||
|
||||
return $latex;
|
||||
}
|
||||
|
||||
private function compileLatex($latexFilename) {
|
||||
$outputDir = pathinfo($latexFilename, PATHINFO_DIRNAME);
|
||||
$baseName = pathinfo($latexFilename, PATHINFO_FILENAME);
|
||||
|
||||
// Run pdflatex twice to resolve references
|
||||
$commands = [
|
||||
"cd {$outputDir} && pdflatex -interaction=nonstopmode {$latexFilename}",
|
||||
"cd {$outputDir} && pdflatex -interaction=nonstopmode {$latexFilename}",
|
||||
];
|
||||
|
||||
foreach ($commands as $command) {
|
||||
shell_exec($command);
|
||||
}
|
||||
|
||||
// Clean up temporary files
|
||||
$tempFiles = [$baseName . '.aux', $baseName . '.log', $baseName . '.out'];
|
||||
foreach ($tempFiles as $tempFile) {
|
||||
$tempPath = $outputDir . '/' . $tempFile;
|
||||
if (file_exists($tempPath)) {
|
||||
unlink($tempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Season;
|
||||
use App\Models\User;
|
||||
|
||||
class PublicController extends Controller {
|
||||
|
||||
public function index() {
|
||||
$config = $this->config;
|
||||
|
||||
// Get current season
|
||||
$currentSeason = Season::getCurrent();
|
||||
|
||||
// Get recent articles (draft status for WIP)
|
||||
$draftArticles = Article::all('draft');
|
||||
|
||||
// Get published articles for archive preview
|
||||
$publishedArticles = Article::all('published');
|
||||
|
||||
// Scan for PDF files in multiple directories
|
||||
$pdfFiles = [];
|
||||
|
||||
// Check storage/latex directory
|
||||
$pdfDirs = [
|
||||
__DIR__ . '/../../storage/latex/',
|
||||
__DIR__ . '/../../../Latex/',
|
||||
__DIR__ . '/../../../Build/',
|
||||
__DIR__ . '/../../../Proostenkrant.NET/',
|
||||
];
|
||||
|
||||
foreach ($pdfDirs as $pdfDir) {
|
||||
if (is_dir($pdfDir)) {
|
||||
$files = scandir($pdfDir);
|
||||
foreach ($files as $file) {
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) === 'pdf') {
|
||||
$filepath = $pdfDir . $file;
|
||||
$pdfFiles[] = [
|
||||
'filename' => $file,
|
||||
'path' => $this->getPdfPath($pdfDir, $file),
|
||||
'date' => date('Y-m-d', filemtime($filepath)),
|
||||
'fullpath' => $filepath
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by date descending
|
||||
usort($pdfFiles, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
$users = User::all();
|
||||
$usersById = [];
|
||||
foreach ($users as $user) {
|
||||
$usersById[$user['id']] = $user['display_name'];
|
||||
}
|
||||
|
||||
$this->view('public/home', [
|
||||
'page_title' => 'Home',
|
||||
'show_nav' => true,
|
||||
'config' => $config,
|
||||
'currentSeason' => $currentSeason,
|
||||
'draftArticles' => $draftArticles,
|
||||
'publishedArticles' => $publishedArticles,
|
||||
'pdfFiles' => $pdfFiles,
|
||||
'usersById' => $usersById
|
||||
]);
|
||||
}
|
||||
|
||||
private function getPdfPath($directory, $filename) {
|
||||
// Map directory to public URL
|
||||
$baseDir = __DIR__ . '/../../';
|
||||
|
||||
if (strpos($directory, '/storage/latex') !== false) {
|
||||
return '/storage/latex/' . $filename;
|
||||
} elseif (strpos($directory, '/Latex/') !== false) {
|
||||
return '/Latex/' . $filename;
|
||||
} elseif (strpos($directory, '/Build/') !== false) {
|
||||
return '/Build/' . $filename;
|
||||
} elseif (strpos($directory, '/Proostenkrant.NET/') !== false) {
|
||||
return '/Proostenkrant.NET/' . $filename;
|
||||
}
|
||||
|
||||
return '/' . basename($directory) . '/' . $filename;
|
||||
}
|
||||
|
||||
public function archive() {
|
||||
$pdfFiles = [];
|
||||
|
||||
// Check multiple directories for PDFs
|
||||
$pdfDirs = [
|
||||
__DIR__ . '/../../storage/latex/',
|
||||
__DIR__ . '/../../../Latex/',
|
||||
__DIR__ . '/../../../Build/',
|
||||
__DIR__ . '/../../../Proostenkrant.NET/',
|
||||
];
|
||||
|
||||
foreach ($pdfDirs as $pdfDir) {
|
||||
if (is_dir($pdfDir)) {
|
||||
$files = scandir($pdfDir);
|
||||
foreach ($files as $file) {
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) === 'pdf') {
|
||||
$filepath = $pdfDir . $file;
|
||||
// Try to extract date and edition from filename
|
||||
$basename = pathinfo($file, PATHINFO_FILENAME);
|
||||
$date = date('Y-m-d', filemtime($filepath));
|
||||
|
||||
// Check if filename has a date pattern
|
||||
if (preg_match('/(\d{4}-\d{2}-\d{2})/', $basename, $matches)) {
|
||||
$date = $matches[1];
|
||||
}
|
||||
|
||||
$editionName = str_replace(['_', '-'], ' ', $basename);
|
||||
|
||||
$pdfFiles[] = [
|
||||
'filename' => $file,
|
||||
'path' => $this->getPdfPath($pdfDir, $file),
|
||||
'date' => $date,
|
||||
'edition_name' => $editionName,
|
||||
'fullpath' => $filepath
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by date descending
|
||||
usort($pdfFiles, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
$this->view('public/archive', [
|
||||
'page_title' => 'Archief',
|
||||
'show_nav' => true,
|
||||
'pdfFiles' => $pdfFiles
|
||||
]);
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Season;
|
||||
use App\Models\Article;
|
||||
|
||||
class SeasonController extends Controller {
|
||||
|
||||
public function index() {
|
||||
$this->requireAuth();
|
||||
|
||||
$seasons = Season::all();
|
||||
$currentSeason = Season::getCurrent();
|
||||
|
||||
foreach ($seasons as &$season) {
|
||||
$season['article_count'] = count(Article::getBySeason($season['id']));
|
||||
}
|
||||
|
||||
$this->view('seasons/index', [
|
||||
'seasons' => $seasons,
|
||||
'currentSeason' => $currentSeason
|
||||
]);
|
||||
}
|
||||
|
||||
public function show($id) {
|
||||
$season = Season::find($id);
|
||||
|
||||
if (!$season) {
|
||||
$this->redirect('/seasons');
|
||||
}
|
||||
|
||||
$articles = Article::getBySeason($id);
|
||||
|
||||
$this->view('seasons/show', [
|
||||
'season' => $season,
|
||||
'articles' => $articles
|
||||
]);
|
||||
}
|
||||
}
|
||||
Executable
+287
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\User;
|
||||
|
||||
class SubmissionController extends Controller {
|
||||
|
||||
public function showPassphrase() {
|
||||
$this->view('submission/passphrase', [
|
||||
'page_title' => 'Artikel Indienen - Beveiliging',
|
||||
'show_nav' => true,
|
||||
'next_page' => '/submit/form'
|
||||
]);
|
||||
}
|
||||
|
||||
public function verifyPassphrase() {
|
||||
$passphrase = $_POST['passphrase'] ?? '';
|
||||
$universalPassword = $this->config['universal_password'] ?? 'ProostenKrant2024!';
|
||||
|
||||
if ($passphrase === $universalPassword) {
|
||||
// Store in session that user has passed the passphrase check
|
||||
$_SESSION['passphrase_verified'] = true;
|
||||
$_SESSION['passphrase_timestamp'] = time();
|
||||
|
||||
// Redirect to submission form
|
||||
$this->redirect('/submit/form');
|
||||
} else {
|
||||
$_SESSION['error'] = 'Onjuiste paszin. Probeer het opnieuw.';
|
||||
$this->redirect('/submit');
|
||||
}
|
||||
}
|
||||
|
||||
public function showForm() {
|
||||
// Check if passphrase is verified OR if we're coming from article edit flow
|
||||
$isEditFlow = isset($_SESSION['edit_article_id']) && $_SESSION['edit_article_id'];
|
||||
|
||||
if (!$isEditFlow && (!isset($_SESSION['passphrase_verified']) || !$_SESSION['passphrase_verified'])) {
|
||||
$this->redirect('/submit');
|
||||
}
|
||||
|
||||
$users = User::all();
|
||||
|
||||
// Get old input if validation failed
|
||||
$oldInput = $_SESSION['old_input'] ?? [];
|
||||
unset($_SESSION['old_input']);
|
||||
$errors = $_SESSION['errors'] ?? [];
|
||||
unset($_SESSION['errors']);
|
||||
|
||||
// Check if we're editing an existing article
|
||||
$articleId = $_SESSION['edit_article_id'] ?? null;
|
||||
$article = null;
|
||||
if ($articleId) {
|
||||
$article = Article::find($articleId);
|
||||
unset($_SESSION['edit_article_id']);
|
||||
}
|
||||
|
||||
$this->view('submission/form', [
|
||||
'page_title' => $article ? 'Artikel Bewerken' : 'Artikel Indienen',
|
||||
'show_nav' => true,
|
||||
'users' => $users,
|
||||
'oldInput' => $oldInput,
|
||||
'errors' => $errors,
|
||||
'article' => $article
|
||||
]);
|
||||
}
|
||||
|
||||
public function store() {
|
||||
// Check if passphrase is verified OR if we're coming from article edit flow (which already verified)
|
||||
$isEditFlow = isset($_POST['article_id']) && $_POST['article_id'];
|
||||
|
||||
if (!$isEditFlow && (!isset($_SESSION['passphrase_verified']) || !$_SESSION['passphrase_verified'])) {
|
||||
$_SESSION['error'] = 'Je moet eerst de beveiligingspaszin invoeren.';
|
||||
$this->redirect('/submit');
|
||||
}
|
||||
|
||||
$users = User::all();
|
||||
$userMap = [];
|
||||
foreach ($users as $user) {
|
||||
$userMap[$user['id']] = $user['display_name'];
|
||||
}
|
||||
|
||||
$validationRules = [
|
||||
'title' => 'required|min:5|max:255',
|
||||
'content' => 'required|min:10',
|
||||
'writer' => 'required',
|
||||
'participants' => 'required',
|
||||
];
|
||||
|
||||
$errors = $this->validate($_POST, $validationRules);
|
||||
|
||||
// Check if image was uploaded
|
||||
$articleImage = null;
|
||||
if (isset($_FILES['image'])) {
|
||||
if ($_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||
// Use configured upload path for articles
|
||||
$uploadDir = $this->config['upload_paths']['articles'] ?? __DIR__ . '/../../public/images/articles/';
|
||||
$filename = uniqid() . '_' . basename($_FILES['image']['name']);
|
||||
$targetPath = $uploadDir . '/' . $filename;
|
||||
|
||||
// Make sure the directory exists and is writable
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
|
||||
// Ensure directory has proper permissions
|
||||
if (!is_writable($uploadDir)) {
|
||||
chmod($uploadDir, 0777);
|
||||
}
|
||||
|
||||
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetPath)) {
|
||||
$articleImage = 'images/articles/' . $filename;
|
||||
// Store in session to show preview on redirect
|
||||
$_SESSION['uploaded_image'] = $articleImage;
|
||||
} else {
|
||||
$error = error_get_last();
|
||||
$errors['image'] = 'Fout bij het uploaden van de afbeelding: ' . ($error ? $error['message'] : 'Onbekende fout');
|
||||
}
|
||||
} else {
|
||||
// Image upload error - no file selected
|
||||
$errors['image'] = 'Selecteer een afbeelding om te uploaden.';
|
||||
}
|
||||
} else {
|
||||
// No image file was sent
|
||||
$errors['image'] = 'Een afbeelding is verplicht.';
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
$_SESSION['errors'] = $errors;
|
||||
$_SESSION['old_input'] = $_POST;
|
||||
$this->redirect('/submit/form');
|
||||
}
|
||||
|
||||
// Get the writer's user ID
|
||||
$writerId = $_POST['writer'];
|
||||
$writerName = $userMap[$writerId] ?? 'Onbekend';
|
||||
|
||||
// Process participants
|
||||
$participantIds = $_POST['participants'] ?? [];
|
||||
if (!is_array($participantIds)) {
|
||||
$participantIds = [$participantIds];
|
||||
}
|
||||
|
||||
// Ensure writer is in participants
|
||||
if (!in_array($writerId, $participantIds)) {
|
||||
array_unshift($participantIds, $writerId);
|
||||
}
|
||||
|
||||
$articleData = [
|
||||
'title' => $_POST['title'],
|
||||
'content' => $_POST['content'],
|
||||
'author_id' => $writerId,
|
||||
'participants' => implode(',', $participantIds),
|
||||
'event_date' => $_POST['event_date'] ?? date('Y-m-d'),
|
||||
'article_image' => $articleImage,
|
||||
'status' => 'draft',
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
// Check if we're editing or creating new
|
||||
$articleId = $_POST['article_id'] ?? null;
|
||||
if ($articleId) {
|
||||
// For existing articles, preserve the status
|
||||
$existingArticle = Article::find($articleId);
|
||||
if ($existingArticle) {
|
||||
$articleData['status'] = $existingArticle['status'];
|
||||
}
|
||||
Article::update($articleId, $articleData);
|
||||
$_SESSION['success'] = 'Artikel succesvol bijgewerkt!';
|
||||
} else {
|
||||
$articleId = Article::create($articleData);
|
||||
$_SESSION['success'] = 'Artikel succesvol ingediend!';
|
||||
}
|
||||
|
||||
// Clear passphrase verification for security
|
||||
unset($_SESSION['passphrase_verified']);
|
||||
unset($_SESSION['passphrase_timestamp']);
|
||||
|
||||
// Store article ID for confirmation
|
||||
$_SESSION['submitted_article_id'] = $articleId;
|
||||
|
||||
$this->redirect('/submit/confirmation');
|
||||
}
|
||||
|
||||
public function showConfirmation() {
|
||||
// Check if there's a submitted article
|
||||
$articleId = $_SESSION['submitted_article_id'] ?? null;
|
||||
unset($_SESSION['submitted_article_id']);
|
||||
|
||||
$article = null;
|
||||
if ($articleId) {
|
||||
$article = Article::find($articleId);
|
||||
}
|
||||
|
||||
$this->view('submission/confirmation', [
|
||||
'page_title' => 'Bevestiging',
|
||||
'show_nav' => true,
|
||||
'article' => $article
|
||||
]);
|
||||
}
|
||||
|
||||
public function showEditList() {
|
||||
// Check if passphrase is verified
|
||||
if (!isset($_SESSION['passphrase_verified']) || !$_SESSION['passphrase_verified']) {
|
||||
$this->redirect('/submit/edit');
|
||||
}
|
||||
|
||||
$articles = Article::all('draft');
|
||||
$users = User::all();
|
||||
$usersById = [];
|
||||
foreach ($users as $user) {
|
||||
$usersById[$user['id']] = $user['display_name'];
|
||||
}
|
||||
|
||||
$this->view('submission/edit_list', [
|
||||
'page_title' => 'Artikelen Bewerken',
|
||||
'show_nav' => true,
|
||||
'articles' => $articles,
|
||||
'usersById' => $usersById
|
||||
]);
|
||||
}
|
||||
|
||||
public function verifyEditPassphrase() {
|
||||
$passphrase = $_POST['passphrase'] ?? '';
|
||||
$universalPassword = $this->config['universal_password'] ?? 'ProostenKrant2024!';
|
||||
|
||||
if ($passphrase === $universalPassword) {
|
||||
$_SESSION['passphrase_verified'] = true;
|
||||
$_SESSION['passphrase_timestamp'] = time();
|
||||
$this->redirect('/submit/edit/list');
|
||||
} else {
|
||||
$_SESSION['error'] = 'Onjuiste paszin. Probeer het opnieuw.';
|
||||
$this->redirect('/submit/edit');
|
||||
}
|
||||
}
|
||||
|
||||
public function showEditPassphrase() {
|
||||
$this->view('submission/edit_passphrase', [
|
||||
'page_title' => 'Artikelen Bewerken - Beveiliging',
|
||||
'show_nav' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function showWriterVerification($articleId) {
|
||||
$article = Article::find($articleId);
|
||||
|
||||
if (!$article) {
|
||||
$_SESSION['error'] = 'Artikel niet gevonden.';
|
||||
$this->redirect('/submit/edit/list');
|
||||
}
|
||||
|
||||
$users = User::all();
|
||||
|
||||
$this->view('submission/verify_writer', [
|
||||
'page_title' => 'Verifieer Schrijver',
|
||||
'show_nav' => true,
|
||||
'article' => $article,
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function verifyWriter() {
|
||||
$articleId = $_POST['article_id'] ?? null;
|
||||
$writerId = $_POST['writer_id'] ?? null;
|
||||
|
||||
if (!$articleId || !$writerId) {
|
||||
$_SESSION['error'] = 'Ongeldige verificatie.';
|
||||
$this->redirect('/submit/edit/list');
|
||||
}
|
||||
|
||||
$article = Article::find($articleId);
|
||||
if (!$article) {
|
||||
$_SESSION['error'] = 'Artikel niet gevonden.';
|
||||
$this->redirect('/submit/edit/list');
|
||||
}
|
||||
|
||||
if ($article['author_id'] != $writerId) {
|
||||
$_SESSION['error'] = 'Alleen de schrijver van het artikel kan deze bewerken.';
|
||||
$this->redirect('/submit/edit/list');
|
||||
}
|
||||
|
||||
// Store article ID in session for editing
|
||||
$_SESSION['edit_article_id'] = $articleId;
|
||||
|
||||
$this->redirect('/submit/form');
|
||||
}
|
||||
}
|
||||
Executable
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UploadController extends Controller {
|
||||
|
||||
public function upload() {
|
||||
$this->requireAuth();
|
||||
|
||||
if (!isset($_FILES['file'])) {
|
||||
echo json_encode(['error' => 'No file uploaded']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$file = $_FILES['file'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
echo json_encode(['error' => 'Upload error: ' . $file['error']]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$type = $_POST['type'] ?? 'articles';
|
||||
$validTypes = ['articles', 'profiles'];
|
||||
|
||||
if (!in_array($type, $validTypes)) {
|
||||
echo json_encode(['error' => 'Invalid upload type']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$uploadDir = $this->config['upload_paths'][$type];
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$filename = uniqid() . '.' . $ext;
|
||||
$targetPath = $uploadDir . $filename;
|
||||
|
||||
// Validate image
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
if (!in_array(strtolower($ext), $allowedExtensions)) {
|
||||
echo json_encode(['error' => 'Only images are allowed']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($file['tmp_name']);
|
||||
if ($imageInfo === false) {
|
||||
echo json_encode(['error' => 'Invalid image file']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Resize large images to prevent issues with LaTeX
|
||||
if ($imageInfo[0] > 4000 || $imageInfo[1] > 4000) {
|
||||
$this->resizeImage($file['tmp_name'], $targetPath, 2000, 2000);
|
||||
} else {
|
||||
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
|
||||
echo json_encode(['error' => 'Failed to move uploaded file']);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'filename' => $filename,
|
||||
'path' => 'images/' . $type . '/' . $filename,
|
||||
'url' => '/images/' . $type . '/' . $filename
|
||||
]);
|
||||
}
|
||||
|
||||
private function resizeImage($source, $target, $maxWidth, $maxHeight) {
|
||||
$imageInfo = getimagesize($source);
|
||||
$width = $imageInfo[0];
|
||||
$height = $imageInfo[1];
|
||||
$mime = $imageInfo['mime'];
|
||||
|
||||
$ratio = min($maxWidth / $width, $maxHeight / $height);
|
||||
$newWidth = (int)($width * $ratio);
|
||||
$newHeight = (int)($height * $ratio);
|
||||
|
||||
$sourceImage = null;
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
$sourceImage = imagecreatefromjpeg($source);
|
||||
break;
|
||||
case 'image/png':
|
||||
$sourceImage = imagecreatefrompng($source);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$sourceImage = imagecreatefromgif($source);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$sourceImage = imagecreatefromwebp($source);
|
||||
break;
|
||||
default:
|
||||
move_uploaded_file($source, $target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$sourceImage) {
|
||||
move_uploaded_file($source, $target);
|
||||
return;
|
||||
}
|
||||
|
||||
$destinationImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
// Preserve transparency for PNG and GIF
|
||||
if ($mime === 'image/png' || $mime === 'image/gif') {
|
||||
imagealphablending($destinationImage, false);
|
||||
imagesavealpha($destinationImage, true);
|
||||
$transparent = imagecolorallocatealpha($destinationImage, 255, 255, 255, 127);
|
||||
imagefilledrectangle($destinationImage, 0, 0, $newWidth, $newHeight, $transparent);
|
||||
}
|
||||
|
||||
imagecopyresampled($destinationImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
imagejpeg($destinationImage, $target, 90);
|
||||
break;
|
||||
case 'image/png':
|
||||
imagepng($destinationImage, $target, 9);
|
||||
break;
|
||||
case 'image/gif':
|
||||
imagegif($destinationImage, $target);
|
||||
break;
|
||||
case 'image/webp':
|
||||
imagewebp($destinationImage, $target, 90);
|
||||
break;
|
||||
}
|
||||
|
||||
imagedestroy($sourceImage);
|
||||
imagedestroy($destinationImage);
|
||||
}
|
||||
}
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
class AuthHelper {
|
||||
|
||||
public static function check() {
|
||||
return isset($_SESSION['is_logged_in']) && $_SESSION['is_logged_in'] === true;
|
||||
}
|
||||
|
||||
public static function user() {
|
||||
if (!self::check()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $_SESSION['user_id'] ?? null,
|
||||
'username' => $_SESSION['username'] ?? null,
|
||||
'display_name' => $_SESSION['display_name'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
public static function requireAuth() {
|
||||
if (!self::check()) {
|
||||
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
public static function redirectIfAuthenticated() {
|
||||
if (self::check()) {
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
class ImageHelper {
|
||||
|
||||
public static function resizeImage($source, $target, $maxWidth, $maxHeight, $quality = 90) {
|
||||
if (!file_exists($source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($source);
|
||||
if ($imageInfo === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($width, $height, $type) = $imageInfo;
|
||||
|
||||
// If image is smaller than max dimensions, just copy it
|
||||
if ($width <= $maxWidth && $height <= $maxHeight) {
|
||||
return copy($source, $target);
|
||||
}
|
||||
|
||||
// Calculate new dimensions maintaining aspect ratio
|
||||
$ratio = min($maxWidth / $width, $maxHeight / $height);
|
||||
$newWidth = (int)($width * $ratio);
|
||||
$newHeight = (int)($height * $ratio);
|
||||
|
||||
// Create source image resource
|
||||
$sourceImage = null;
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
$sourceImage = imagecreatefromjpeg($source);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$sourceImage = imagecreatefrompng($source);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
$sourceImage = imagecreatefromgif($source);
|
||||
break;
|
||||
case IMAGETYPE_WEBP:
|
||||
$sourceImage = imagecreatefromwebp($source);
|
||||
break;
|
||||
default:
|
||||
return copy($source, $target);
|
||||
}
|
||||
|
||||
if (!$sourceImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create destination image
|
||||
$destinationImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
// Preserve transparency for PNG and GIF
|
||||
if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF) {
|
||||
imagealphablending($destinationImage, false);
|
||||
imagesavealpha($destinationImage, true);
|
||||
$transparent = imagecolorallocatealpha($destinationImage, 255, 255, 255, 127);
|
||||
imagefilledrectangle($destinationImage, 0, 0, $newWidth, $newHeight, $transparent);
|
||||
}
|
||||
|
||||
// Resample image
|
||||
imagecopyresampled($destinationImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||
|
||||
// Save image
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
return imagejpeg($destinationImage, $target, $quality);
|
||||
case IMAGETYPE_PNG:
|
||||
return imagepng($destinationImage, $target, 9);
|
||||
case IMAGETYPE_GIF:
|
||||
return imagegif($destinationImage, $target);
|
||||
case IMAGETYPE_WEBP:
|
||||
return imagewebp($destinationImage, $target, $quality);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function isImagePortrait($imagePath) {
|
||||
if (!file_exists($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($imagePath);
|
||||
if ($imageInfo === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($width, $height) = $imageInfo;
|
||||
return $height > $width;
|
||||
}
|
||||
|
||||
public static function isImageLandscape($imagePath) {
|
||||
if (!file_exists($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($imagePath);
|
||||
if ($imageInfo === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($width, $height) = $imageInfo;
|
||||
return $width > $height;
|
||||
}
|
||||
|
||||
public static function getImageOrientation($imagePath) {
|
||||
if (!file_exists($imagePath)) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($imagePath);
|
||||
if ($imageInfo === false) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
list($width, $height) = $imageInfo;
|
||||
|
||||
if ($width > $height) {
|
||||
return 'landscape';
|
||||
} elseif ($height > $width) {
|
||||
return 'portrait';
|
||||
} else {
|
||||
return 'square';
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
class Article {
|
||||
private static $table = 'articles';
|
||||
|
||||
public static function all($status = 'published') {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll(
|
||||
"SELECT a.*, u.display_name as author_name, s.name as season_name " .
|
||||
"FROM " . self::$table . " a " .
|
||||
"LEFT JOIN users u ON a.author_id = u.id " .
|
||||
"LEFT JOIN seasons s ON a.season_id = s.id " .
|
||||
"WHERE a.status = ? ORDER BY a.event_date DESC, a.created_at DESC",
|
||||
[$status]
|
||||
);
|
||||
}
|
||||
|
||||
public static function find($id) {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetch(
|
||||
"SELECT a.*, u.display_name as author_name, s.name as season_name " .
|
||||
"FROM " . self::$table . " a " .
|
||||
"LEFT JOIN users u ON a.author_id = u.id " .
|
||||
"LEFT JOIN seasons s ON a.season_id = s.id " .
|
||||
"WHERE a.id = ?",
|
||||
[$id]
|
||||
);
|
||||
}
|
||||
|
||||
public static function create($data) {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Process participants
|
||||
if (isset($data['participants']) && is_array($data['participants'])) {
|
||||
$data['participants'] = implode(',', $data['participants']);
|
||||
}
|
||||
|
||||
return $db->insert(self::$table, $data);
|
||||
}
|
||||
|
||||
public static function update($id, $data) {
|
||||
$db = Database::getInstance();
|
||||
|
||||
if (isset($data['participants']) && is_array($data['participants'])) {
|
||||
$data['participants'] = implode(',', $data['participants']);
|
||||
}
|
||||
|
||||
return $db->update(self::$table, $data, ['id' => $id]);
|
||||
}
|
||||
|
||||
public static function delete($id) {
|
||||
$db = Database::getInstance();
|
||||
return $db->delete(self::$table, ['id' => $id]);
|
||||
}
|
||||
|
||||
public static function getBySeason($seasonId) {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll(
|
||||
"SELECT a.*, u.display_name as author_name " .
|
||||
"FROM " . self::$table . " a " .
|
||||
"LEFT JOIN users u ON a.author_id = u.id " .
|
||||
"WHERE a.season_id = ? AND a.status = 'published' " .
|
||||
"ORDER BY a.event_date DESC",
|
||||
[$seasonId]
|
||||
);
|
||||
}
|
||||
|
||||
public static function getByUser($userId) {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll(
|
||||
"SELECT a.*, u.display_name as author_name " .
|
||||
"FROM " . self::$table . " a " .
|
||||
"LEFT JOIN users u ON a.author_id = u.id " .
|
||||
"WHERE a.author_id = ? OR a.participants LIKE ? " .
|
||||
"ORDER BY a.event_date DESC",
|
||||
[$userId, "%,{$userId},%"]
|
||||
);
|
||||
}
|
||||
|
||||
public static function getCurrentSeasonId() {
|
||||
$db = Database::getInstance();
|
||||
$result = $db->fetch(
|
||||
"SELECT id FROM seasons " .
|
||||
"WHERE start_date <= CURDATE() AND end_date >= CURDATE() " .
|
||||
"ORDER BY start_date DESC LIMIT 1"
|
||||
);
|
||||
return $result ? $result['id'] : null;
|
||||
}
|
||||
}
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
class Database {
|
||||
private static $instance = null;
|
||||
private $connection;
|
||||
|
||||
private function __construct() {
|
||||
$config = include __DIR__ . '/../../config/database.php';
|
||||
|
||||
$dsn = "mysql:host={$config['host']};dbname={$config['database']};charset={$config['charset']}";
|
||||
|
||||
try {
|
||||
$this->connection = new PDO(
|
||||
$dsn,
|
||||
$config['username'],
|
||||
$config['password'],
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
die("Database connection failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function getConnection() {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function query($sql, $params = []) {
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
public function fetchAll($sql, $params = []) {
|
||||
return $this->query($sql, $params)->fetchAll();
|
||||
}
|
||||
|
||||
public function fetch($sql, $params = []) {
|
||||
return $this->query($sql, $params)->fetch();
|
||||
}
|
||||
|
||||
public function insert($table, $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
$stmt->execute($data);
|
||||
|
||||
return $this->connection->lastInsertId();
|
||||
}
|
||||
|
||||
public function update($table, $data, $where) {
|
||||
$setParts = [];
|
||||
foreach ($data as $key => $value) {
|
||||
$setParts[] = "{$key} = :{$key}";
|
||||
}
|
||||
|
||||
$whereParts = [];
|
||||
foreach ($where as $key => $value) {
|
||||
$whereParts[] = "{$key} = :where_{$key}";
|
||||
$data["where_{$key}"] = $value;
|
||||
}
|
||||
|
||||
$sql = "UPDATE {$table} SET " . implode(', ', $setParts) .
|
||||
" WHERE " . implode(' AND ', $whereParts);
|
||||
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
return $stmt->execute($data);
|
||||
}
|
||||
|
||||
public function delete($table, $where) {
|
||||
$whereParts = [];
|
||||
$params = [];
|
||||
foreach ($where as $key => $value) {
|
||||
$whereParts[] = "{$key} = :{$key}";
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM {$table} WHERE " . implode(' AND ', $whereParts);
|
||||
return $this->query($sql, $params)->rowCount();
|
||||
}
|
||||
}
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
class Season {
|
||||
private static $table = 'seasons';
|
||||
|
||||
public static function all() {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll("SELECT * FROM " . self::$table . " ORDER BY start_date");
|
||||
}
|
||||
|
||||
public static function find($id) {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetch("SELECT * FROM " . self::$table . " WHERE id = ?", [$id]);
|
||||
}
|
||||
|
||||
public static function getActive() {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll(
|
||||
"SELECT * FROM " . self::$table . " WHERE is_active = 1 ORDER BY start_date"
|
||||
);
|
||||
}
|
||||
|
||||
public static function getCurrent() {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetch(
|
||||
"SELECT * FROM " . self::$table . " " .
|
||||
"WHERE start_date <= CURDATE() AND end_date >= CURDATE() " .
|
||||
"ORDER BY start_date DESC LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
public static function getNext() {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetch(
|
||||
"SELECT * FROM " . self::$table . " " .
|
||||
"WHERE start_date > CURDATE() " .
|
||||
"ORDER BY start_date ASC LIMIT 1"
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
class Setting {
|
||||
private static $table = 'settings';
|
||||
|
||||
public static function get($key, $default = null) {
|
||||
$db = Database::getInstance();
|
||||
$result = $db->fetch("SELECT value FROM " . self::$table . " WHERE key_name = ?", [$key]);
|
||||
return $result ? $result['value'] : $default;
|
||||
}
|
||||
|
||||
public static function set($key, $value) {
|
||||
$db = Database::getInstance();
|
||||
|
||||
$existing = $db->fetch("SELECT id FROM " . self::$table . " WHERE key_name = ?", [$key]);
|
||||
|
||||
if ($existing) {
|
||||
return $db->update(self::$table, ['value' => $value], ['key_name' => $key]);
|
||||
} else {
|
||||
return $db->insert(self::$table, ['key_name' => $key, 'value' => $value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
class User {
|
||||
private static $table = 'users';
|
||||
|
||||
public static function all() {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll("SELECT * FROM " . self::$table . " WHERE is_active = 1 ORDER BY display_name");
|
||||
}
|
||||
|
||||
public static function find($id) {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetch("SELECT * FROM " . self::$table . " WHERE id = ?", [$id]);
|
||||
}
|
||||
|
||||
public static function findByUsername($username) {
|
||||
$db = Database::getInstance();
|
||||
return $db->fetch("SELECT * FROM " . self::$table . " WHERE username = ? AND is_active = 1", [$username]);
|
||||
}
|
||||
|
||||
public static function getByIds($ids) {
|
||||
if (empty($ids)) return [];
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($ids), '?'));
|
||||
$db = Database::getInstance();
|
||||
return $db->fetchAll("SELECT * FROM " . self::$table . " WHERE id IN ({$placeholders}) AND is_active = 1", $ids);
|
||||
}
|
||||
|
||||
public static function updateProfileImage($userId, $imagePath) {
|
||||
$db = Database::getInstance();
|
||||
return $db->update(self::$table, ['profile_image' => $imagePath], ['id' => $userId]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
require 'controllers/Controller.php';
|
||||
require 'controllers/TestController.php';
|
||||
$controller = new \App\Controllers\TestController();
|
||||
$controller->index();
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
$page_title = 'Artikelen';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="articles-list">
|
||||
<div class="header-actions">
|
||||
<h2>Alle Artikelen</h2>
|
||||
<a href="/articles/new" class="btn">Nieuw Artikel</a>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($articles)): ?>
|
||||
<table class="articles-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<th>Auteur</th>
|
||||
<th>Datum</th>
|
||||
<th>Seizoen</th>
|
||||
<th>Status</th>
|
||||
<th>Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($articles as $article): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($article['title']) ?></td>
|
||||
<td><?= htmlspecialchars($article['author_name'] ?? 'Onbekend') ?></td>
|
||||
<td><?= date('d-m-Y', strtotime($article['event_date'])) ?></td>
|
||||
<td><?= htmlspecialchars($article['season_name'] ?? 'N/A') ?></td>
|
||||
<td>
|
||||
<span class="status-badge <?= strtolower($article['status']) ?>">
|
||||
<?= htmlspecialchars($article['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/articles/<?= $article['id'] ?>" class="btn-small">Bekijken</a>
|
||||
<a href="/articles/<?= $article['id'] ?>/verify-writer" class="edit-icon" title="Bewerken">✏️</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p>Er zijn nog geen artikelen.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
$page_title = 'Nieuw Artikel';
|
||||
ob_start();
|
||||
|
||||
// Get old input if validation failed
|
||||
$oldInput = $_SESSION['old_input'] ?? [];
|
||||
unset($_SESSION['old_input']);
|
||||
$errors = $_SESSION['errors'] ?? [];
|
||||
unset($_SESSION['errors']);
|
||||
|
||||
$users = \App\Models\User::all();
|
||||
$seasons = \App\Models\Season::all();
|
||||
$currentSeason = \App\Models\Season::getCurrent();
|
||||
?>
|
||||
|
||||
<div class="article-form">
|
||||
<h2>Nieuw Artikel Toevoegen</h2>
|
||||
|
||||
<form action="/articles" method="post" enctype="multipart/form-data">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="title">Titel *</label>
|
||||
<input type="text" name="title" id="title"
|
||||
value="<?= htmlspecialchars($oldInput['title'] ?? '') ?>"
|
||||
required class="form-control <?= isset($errors['title']) ? 'error' : '' ?>">
|
||||
<?php if (isset($errors['title'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['title']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="content">Inhoud *</label>
|
||||
<textarea name="content" id="content" rows="10"
|
||||
required class="form-control <?= isset($errors['content']) ? 'error' : '' ?>"><?= htmlspecialchars($oldInput['content'] ?? '') ?></textarea>
|
||||
<?php if (isset($errors['content'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['content']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="event_date">Datum van het evenement *</label>
|
||||
<input type="date" name="event_date" id="event_date"
|
||||
value="<?= htmlspecialchars($oldInput['event_date'] ?? date('Y-m-d')) ?>"
|
||||
required class="form-control <?= isset($errors['event_date']) ? 'error' : '' ?>">
|
||||
<?php if (isset($errors['event_date'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['event_date']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="author_id">Auteur *</label>
|
||||
<select name="author_id" id="author_id" required
|
||||
class="form-control <?= isset($errors['author_id']) ? 'error' : '' ?>">
|
||||
<option value="">-- Selecteer auteur --</option>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"
|
||||
<?= (($oldInput['author_id'] ?? '') == $user['id'] || (isset($_SESSION['user_id']) && $_SESSION['user_id'] == $user['id'])) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($errors['author_id'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['author_id']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group full-width">
|
||||
<label>Deelnemers</label>
|
||||
<div class="checkbox-group">
|
||||
<?php foreach ($users as $user): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="participants[]"
|
||||
value="<?= $user['id'] ?>"
|
||||
<?= (isset($oldInput['participants']) && in_array($user['id'], $oldInput['participants'])) ? 'checked' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="season_id">Seizoen *</label>
|
||||
<select name="season_id" id="season_id" required
|
||||
class="form-control <?= isset($errors['season_id']) ? 'error' : '' ?>">
|
||||
<option value="">-- Selecteer seizoen --</option>
|
||||
<?php foreach ($seasons as $season): ?>
|
||||
<option value="<?= $season['id'] ?>"
|
||||
<?= (($oldInput['season_id'] ?? '') == $season['id'] || ($currentSeason && $currentSeason['id'] == $season['id'])) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($season['name']) ?>
|
||||
(<?= date('d-m-Y', strtotime($season['start_date'])) ?> -
|
||||
<?= date('d-m-Y', strtotime($season['end_date'])) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($errors['season_id'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['season_id']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="article_image">Afbeelding (optioneel)</label>
|
||||
<input type="file" name="article_image" id="article_image"
|
||||
accept="image/*" class="form-control">
|
||||
<p class="help-text">Upload een foto bij het artikel. tantoon landscape als portret foto's worden ondersteund.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Artikel Opslaan</button>
|
||||
<a href="/articles" class="btn btn-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
$page_title = htmlspecialchars($article['title'] ?? 'Artikel');
|
||||
ob_start();
|
||||
|
||||
$participantNames = [];
|
||||
foreach ($participants as $participant) {
|
||||
$participantNames[] = htmlspecialchars($participant['display_name']);
|
||||
}
|
||||
$participantsString = implode(', ', $participantNames);
|
||||
?>
|
||||
|
||||
<div class="article-detail">
|
||||
<h2><?= htmlspecialchars($article['title']) ?></h2>
|
||||
|
||||
<div class="article-meta">
|
||||
<span class="meta-item">
|
||||
<strong>Auteur:</strong> <?= htmlspecialchars($article['author_name'] ?? 'Onbekend') ?>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<strong>Datum:</strong> <?= date('d-m-Y', strtotime($article['event_date'])) ?>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<strong>Seizoen:</strong> <?= htmlspecialchars($article['season_name'] ?? 'N/A') ?>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<strong>Deelnemers:</strong> <?= $participantsString ?>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<strong>Status:</strong>
|
||||
<span class="status-badge <?= strtolower($article['status']) ?>">
|
||||
<?= htmlspecialchars($article['status']) ?>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if ($article['article_image']): ?>
|
||||
<div class="article-image-container">
|
||||
<img src="/<?= htmlspecialchars($article['article_image']) ?>"
|
||||
alt="<?= htmlspecialchars($article['title']) ?>"
|
||||
class="article-image">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="article-content">
|
||||
<?= nl2br(htmlspecialchars($article['content'])) ?>
|
||||
</div>
|
||||
|
||||
<div class="article-actions">
|
||||
<a href="/articles" class="btn btn-secondary">Terug naar Overzicht</a>
|
||||
<a href="/articles/<?= $article['id'] ?>/edit" class="btn">Bewerken</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
$page_title = 'Beveiligingscontrole - Artikel Bewerken';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="passphrase-container">
|
||||
<h2>Beveiligingscontrole</h2>
|
||||
|
||||
<?php if ($article): ?>
|
||||
<div class="card mb-30">
|
||||
<h4><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<p class="text-muted">
|
||||
Voer het algemene passphrase in om dit artikel te bewerken.
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div class="alert alert-error">
|
||||
<?= htmlspecialchars($_SESSION['error']) ?>
|
||||
<?php unset($_SESSION['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/articles/verify-passphrase" method="post">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Algemeen Passphrase</label>
|
||||
<input type="password"
|
||||
name="passphrase"
|
||||
id="passphrase"
|
||||
class="form-control"
|
||||
required
|
||||
autocomplete="off"
|
||||
placeholder="Voer het passphrase in">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Verifieer en Bewerk Artikel
|
||||
</button>
|
||||
<a href="/articles" class="btn btn-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
$page_title = 'Verifieer Schrijver';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="writer-verification">
|
||||
<h2>Verifieer Schrijver</h2>
|
||||
|
||||
<?php if ($article): ?>
|
||||
<div class="card mb-30">
|
||||
<h4><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<p class="text-muted">
|
||||
Om dit artikel te bewerken, bevestig aub wie de schrijver is.
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div class="alert alert-error">
|
||||
<?= htmlspecialchars($_SESSION['error']) ?>
|
||||
<?php unset($_SESSION['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/articles/verify-writer" method="post">
|
||||
<input type="hidden" name="article_id" value="<?= htmlspecialchars($article['id'] ?? '') ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="writer_id">Selecteer de Schrijver *</label>
|
||||
<select name="writer_id"
|
||||
id="writer_id"
|
||||
required
|
||||
class="form-control">
|
||||
<option value="">-- Selecteer de schrijver --</option>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"
|
||||
<?= ($article && $article['author_id'] == $user['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Verifieer en Ga Verder
|
||||
</button>
|
||||
<a href="/articles" class="btn btn-secondary">
|
||||
Terug naar Artikelen
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($config['app_name'] ?? 'ProostenKrant') ?> - <?= htmlspecialchars($this->data['page_title'] ?? '') ?></title>
|
||||
<link rel="stylesheet" href="/css/douwco-style.css">
|
||||
<script src="/js/app.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="logo">
|
||||
<a href="/"><?= htmlspecialchars($config['app_name'] ?? 'ProostenKrant') ?></a>
|
||||
</h1>
|
||||
<p class="slogan"><?= htmlspecialchars($config['app_slogan'] ?? 'Een krant van en voor Proosten') ?></p>
|
||||
</div>
|
||||
|
||||
<?php if (isset($this->data['show_nav']) && $this->data['show_nav']): ?>
|
||||
<nav class="nav">
|
||||
<?php if (isset($_SESSION['is_logged_in']) && $_SESSION['is_logged_in']): ?>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/articles">Artikelen</a>
|
||||
<a href="/articles/new">Nieuw Artikel</a>
|
||||
<a href="/seasons">Seizoenen</a>
|
||||
<a href="/generate-pdf" class="btn">PDF Genereren</a>
|
||||
<a href="/logout" class="btn-secondary">Uitloggen</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main">
|
||||
<?php if (isset($_SESSION['success'])): ?>
|
||||
<div class="alert alert-success">
|
||||
<span><?= htmlspecialchars($_SESSION['success']) ?></span>
|
||||
<button class="close" onclick="this.parentElement.style.display='none'">×</button>
|
||||
<?php unset($_SESSION['success']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div class="alert alert-error">
|
||||
<span><?= htmlspecialchars($_SESSION['error']) ?></span>
|
||||
<button class="close" onclick="this.parentElement.style.display='none'">×</button>
|
||||
<?php unset($_SESSION['error']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $content ?? '' ?>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<span class="copyright">Copyright © <?= date('Y') ?></span>
|
||||
<span class="hosted">Made and hosted in <span class="eu-flag">🇪🇺</span></span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
$page_title = 'Dashboard';
|
||||
?>
|
||||
|
||||
<div class="dashboard">
|
||||
<h2>Welkom, <?= htmlspecialchars($_SESSION['display_name'] ?? '') ?>!</h2>
|
||||
|
||||
<?php if ($currentSeason): ?>
|
||||
<div class="season-info" style="background-color: <?= $config['seasons'][$currentSeason['id']]['color'] ?? '#f0f0f0' ?>">
|
||||
<h3>Huidig Seizoen: <?= htmlspecialchars($currentSeason['name']) ?></h3>
|
||||
<p><?= htmlspecialchars($currentSeason['description'] ?? '') ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="dashboard-card">
|
||||
<h3>Artikelen Toevoegen</h3>
|
||||
<p>Voeg nieuwe artikelen toe voor de volgende editie.</p>
|
||||
<a href="/articles/new" class="btn">Nieuw Artikel</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h3>Mijn Artikelen</h3>
|
||||
<p>Beheer uw eigen artikelen.</p>
|
||||
<a href="/articles" class="btn">Bekijk Artikelen</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h3>Seizoenen</h3>
|
||||
<p>Bekijk alle seizoensedities.</p>
|
||||
<a href="/seasons" class="btn">Bekijk Seizoenen</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h3>PDF Genereren</h3>
|
||||
<p>Genereer een PDF van de huidige editie.</p>
|
||||
<a href="/generate-pdf" class="btn">Genereer PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Sneltoetsen</h3>
|
||||
<div class="quick-links">
|
||||
<a href="/articles/new">+ Nieuw Artikel</a>
|
||||
<a href="/articles">Artikelen Lijst</a>
|
||||
<a href="/seasons">Seizoenen</a>
|
||||
<a href="/generate-pdf">PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
?>
|
||||
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<h2>Inloggen</h2>
|
||||
<p>Voer uw gebruikersnaam en het universele wachtwoord in om toegang te krijgen.</p>
|
||||
|
||||
<form action="/login" method="post" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Gebruikersnaam:</label>
|
||||
<select name="username" id="username" required class="form-control">
|
||||
<option value="">-- Selecteer een gebruiker --</option>
|
||||
<?php
|
||||
$users = \App\Models\User::all();
|
||||
foreach ($users as $user):
|
||||
?>
|
||||
<option value="<?= htmlspecialchars($user['username']) ?>"
|
||||
<?= (isset($_POST['username']) && $_POST['username'] === $user['username']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Wachtwoord:</label>
|
||||
<input type="password" name="password" id="password" required class="form-control">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Inloggen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
$page_title = 'Archief';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="archive-container">
|
||||
<div class="section-header">
|
||||
<h2>Archief van ProostenKrant Edities</h2>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($pdfFiles)): ?>
|
||||
<div class="pdf-list">
|
||||
<?php foreach ($pdfFiles as $pdf): ?>
|
||||
<div class="pdf-item slide-up">
|
||||
<div class="pdf-info">
|
||||
<h4><?= htmlspecialchars($pdf['edition_name']) ?></h4>
|
||||
<p>
|
||||
<span class="text-muted">Datum:</span>
|
||||
<?= date('d-m-Y', strtotime($pdf['date'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="pdf-actions">
|
||||
<a href="<?= htmlspecialchars($pdf['path']) ?>"
|
||||
class="pdf-icon"
|
||||
target="_blank"
|
||||
title="Bekijken/Download">
|
||||
<span>📄</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card text-center">
|
||||
<p class="text-muted">Er zijn nog geen PDF edities beschikbaar in het archief.</p>
|
||||
<p class="text-muted mt-20">
|
||||
<a href="/" class="btn btn-secondary">Terug naar Home</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
$page_title = 'Home';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="home-sections">
|
||||
<!-- Section 1: Article Submission -->
|
||||
<section class="submit-section slide-up">
|
||||
<h2>Artikel Indienen</h2>
|
||||
<p>Heb je een leuk verhaal, een bijeenkomst of een bijzondere gebeurtenis meegemaakt?
|
||||
Deel het met de hele familie door een artikel in te dienen voor de ProostenKrant!</p>
|
||||
<a href="/submit" class="btn btn-primary">
|
||||
<span class="icon">+</span> Artikel Indienen
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<!-- Section 2: WIP Newspaper Preview -->
|
||||
<section class="wip-section slide-up">
|
||||
<div class="section-header">
|
||||
<h2>Huidige Editie (Werk in Uitvoering)</h2>
|
||||
<div class="section-actions">
|
||||
<?php if (!empty($draftArticles)): ?>
|
||||
<span class="status-badge draft">
|
||||
<?= count($draftArticles) ?> artikelen in voorbereiding
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">Geen artikelen in voorbereiding</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($draftArticles)): ?>
|
||||
<div class="wip-articles">
|
||||
<?php foreach ($draftArticles as $article): ?>
|
||||
<div class="wip-article">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<h4 style="margin-right: 10px;"><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<a href="/articles/<?= $article['id'] ?>/verify-writer"
|
||||
class="edit-icon"
|
||||
title="Bewerken"
|
||||
style="margin-top: -10px;">
|
||||
<span>✏️</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="wip-content">
|
||||
<span class="text-muted">Deelnemers:</span>
|
||||
<strong>
|
||||
<?php
|
||||
$participantIds = explode(',', $article['participants']);
|
||||
$participantNames = [];
|
||||
foreach ($participantIds as $id) {
|
||||
if (!empty($id) && isset($usersById[$id])) {
|
||||
$participantNames[] = htmlspecialchars($usersById[$id]);
|
||||
}
|
||||
}
|
||||
echo implode(', ', $participantNames);
|
||||
?>
|
||||
</strong>
|
||||
<?php if (!empty($article['event_date'])): ?>
|
||||
<br>
|
||||
<span class="text-muted">Datum:</span>
|
||||
<strong><?= date('d-m-Y', strtotime($article['event_date'])) ?></strong>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card text-center">
|
||||
<p class="text-muted">Er zijn momenteel geen artikelen in voorbereiding.</p>
|
||||
<p class="text-muted">Wees de eerste om een artikel in te dienen!</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- Section 3: Archive -->
|
||||
<section class="archive-section slide-up">
|
||||
<div class="section-header">
|
||||
<h2>Archief</h2>
|
||||
<a href="/archive" class="btn btn-secondary btn-small">
|
||||
Alles Bekijken
|
||||
<span class="icon">→</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($pdfFiles)): ?>
|
||||
<div class="pdf-list">
|
||||
<?php foreach (array_slice($pdfFiles, 0, 5) as $pdf): ?>
|
||||
<div class="pdf-item">
|
||||
<div class="pdf-info">
|
||||
<h4><?= htmlspecialchars(basename($pdf['filename'], '.pdf')) ?></h4>
|
||||
<p>
|
||||
<span class="text-muted">Gepubliceerd op:</span>
|
||||
<?= date('d-m-Y', strtotime($pdf['date'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="pdf-actions">
|
||||
<a href="<?= htmlspecialchars($pdf['path']) ?>"
|
||||
class="pdf-icon"
|
||||
target="_blank"
|
||||
title="Bekijken/Download">
|
||||
<span>📄</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card text-center">
|
||||
<p class="text-muted">Er zijn nog geen eerdere edities beschikbaar.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
$page_title = 'Seizoenen';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="seasons-list">
|
||||
<h2>Seizoensedities</h2>
|
||||
|
||||
<p>Hier vindt u een overzicht van alle seizoensedities.</p>
|
||||
|
||||
<?php if ($currentSeason): ?>
|
||||
<div class="current-season">
|
||||
<h3>Huidig Seizoen</h3>
|
||||
<div class="season-card" style="background-color: <?= $config['seasons'][$currentSeason['id']]['color'] ?? '#f0f0f0' ?>">
|
||||
<h4><?= htmlspecialchars($currentSeason['name']) ?></h4>
|
||||
<p><?= htmlspecialchars($currentSeason['description'] ?? '') ?></p>
|
||||
<p>Van <?= date('d-m-Y', strtotime($currentSeason['start_date'])) ?>
|
||||
tot <?= date('d-m-Y', strtotime($currentSeason['end_date'])) ?></p>
|
||||
<p>Aantal artikelen: <?= $currentSeason['article_count'] ?? 0 ?></p>
|
||||
<a href="/generate-pdf?season_id=<?= $currentSeason['id'] ?>" class="btn">PDF Genereren</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3>Alle Seizoenen</h3>
|
||||
<div class="seasons-grid">
|
||||
<?php foreach ($seasons as $season): ?>
|
||||
<div class="season-card" style="background-color: <?= $config['seasons'][$season['id']]['color'] ?? '#f0f0f0' ?>">
|
||||
<h4><?= htmlspecialchars($season['name']) ?></h4>
|
||||
<p><?= htmlspecialchars($season['description'] ?? '') ?></p>
|
||||
<p>Van <?= date('d-m-Y', strtotime($season['start_date'])) ?>
|
||||
tot <?= date('d-m-Y', strtotime($season['end_date'])) ?></p>
|
||||
<p>Aantal artikelen: <?= $season['article_count'] ?? 0 ?></p>
|
||||
<a href="/generate-pdf?season_id=<?= $season['id'] ?>" class="btn-small">PDF</a>
|
||||
<a href="/seasons/<?= $season['id'] ?>" class="btn-small">Bekijken</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
$page_title = htmlspecialchars($season['name'] ?? 'Seizoen');
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="season-detail">
|
||||
<h2><?= htmlspecialchars($season['name']) ?></h2>
|
||||
|
||||
<div class="season-info">
|
||||
<p><strong>Beschrijving:</strong> <?= htmlspecialchars($season['description'] ?? '') ?></p>
|
||||
<p><strong>Periode:</strong> Van <?= date('d-m-Y', strtotime($season['start_date'])) ?>
|
||||
tot <?= date('d-m-Y', strtotime($season['end_date'])) ?></p>
|
||||
</div>
|
||||
|
||||
<h3>Artikelen in deze editie</h3>
|
||||
|
||||
<?php if (!empty($articles)): ?>
|
||||
<div class="articles-grid">
|
||||
<?php foreach ($articles as $article): ?>
|
||||
<div class="article-card">
|
||||
<h4><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<p class="article-meta">
|
||||
Door: <?= htmlspecialchars($article['author_name']) ?> |
|
||||
<?= date('d-m-Y', strtotime($article['event_date'])) ?>
|
||||
</p>
|
||||
<p><?= htmlspecialchars(substr($article['content'], 0, 150)) ?>...</p>
|
||||
<?php if ($article['article_image']): ?>
|
||||
<img src="/<?= htmlspecialchars($article['article_image']) ?>"
|
||||
alt="<?= htmlspecialchars($article['title']) ?>"
|
||||
class="article-image">
|
||||
<?php endif; ?>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
||||
<a href="/articles/<?= $article['id'] ?>" class="btn-small">Lees meer</a>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<a href="/articles/<?= $article['id'] ?>/verify-writer" class="edit-icon" title="Bewerken">✏️</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="season-actions">
|
||||
<a href="/generate-pdf?season_id=<?= $season['id'] ?>" class="btn">PDF Genereren voor deze editie</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>Er zijn nog geen artikelen voor deze editie.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="/seasons">Terug naar alle seizoenen</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
$page_title = 'Bevestiging';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="confirmation-container slide-up">
|
||||
<div class="icon">✓</div>
|
||||
<h2 class="title">Artikel Succesvol Ingediend!</h2>
|
||||
|
||||
<?php if ($article): ?>
|
||||
<div class="card mt-30">
|
||||
<h4 class="card-title"><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<div class="article-meta">
|
||||
<span><strong>Datum:</strong> <?= date('d-m-Y', strtotime($article['event_date'])) ?></span>
|
||||
</div>
|
||||
<p class="card-content mt-20">
|
||||
Je artikel is opgeslagen en zal zichtbaar zijn in de volgende editie van de ProostenKrant.
|
||||
</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>Je artikel is succesvol ingediend en zal zichtbaar zijn in de volgende editie.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-actions mt-30">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<span class="icon">←</span> Terug naar Home
|
||||
</a>
|
||||
<a href="/submit" class="btn btn-secondary">
|
||||
<span class="icon">+</span> Nog een Artikel Indienen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
$page_title = 'Artikelen Bewerken';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="edit-list">
|
||||
<div class="section-header">
|
||||
<h2>Artikelen om te Bewerken</h2>
|
||||
<a href="/" class="btn btn-secondary btn-small">
|
||||
<span class="icon">←</span> Terug
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($articles)): ?>
|
||||
<div class="cards-grid">
|
||||
<?php foreach ($articles as $article): ?>
|
||||
<div class="article-card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<h4 class="article-title"><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<a href="/submit/edit/<?= $article['id'] ?>"
|
||||
class="edit-icon"
|
||||
title="Bewerken">
|
||||
<span>✏️</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="article-meta">
|
||||
<span><strong>Deelnemers:</strong>
|
||||
<?php
|
||||
$participantIds = explode(',', $article['participants']);
|
||||
$participantNames = [];
|
||||
foreach ($participantIds as $id) {
|
||||
if (!empty($id) && isset($usersById[$id])) {
|
||||
$participantNames[] = htmlspecialchars($usersById[$id]);
|
||||
}
|
||||
}
|
||||
echo implode(', ', $participantNames);
|
||||
?>
|
||||
</span>
|
||||
<span><strong>Datum:</strong> <?= date('d-m-Y', strtotime($article['event_date'])) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card text-center">
|
||||
<p class="text-muted">Er zijn geen artikelen om te bewerken.</p>
|
||||
<p class="mt-20">
|
||||
<a href="/submit" class="btn btn-primary">Nieuw Artikel Indienen</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
$page_title = 'Artikelen Bewerken - Beveiliging';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="passphrase-container slide-up">
|
||||
<h2 class="title">Artikelen Bewerken</h2>
|
||||
<p>Voer de familypaszin in om artikelen te kunnen bewerken:</p>
|
||||
|
||||
<form action="/submit/edit/verify" method="post">
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Familie Paszin</label>
|
||||
<input type="password"
|
||||
name="passphrase"
|
||||
id="passphrase"
|
||||
class="form-control"
|
||||
required
|
||||
autocomplete="off"
|
||||
placeholder="Voer de paszin in">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="icon">→</span> Doorgaan
|
||||
</button>
|
||||
<a href="/" class="btn btn-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
$page_title = 'Nieuw Artikel Indienen';
|
||||
ob_start();
|
||||
|
||||
$users = $users ?? [];
|
||||
$oldInput = $oldInput ?? [];
|
||||
$errors = $errors ?? [];
|
||||
$article = $article ?? null;
|
||||
|
||||
// If editing, populate old input from article
|
||||
if ($article && empty($oldInput)) {
|
||||
$oldInput = [
|
||||
'title' => $article['title'],
|
||||
'content' => $article['content'],
|
||||
'writer' => $article['author_id'],
|
||||
'event_date' => $article['event_date'],
|
||||
'article_id' => $article['id']
|
||||
];
|
||||
|
||||
// Parse participants
|
||||
if (!empty($article['participants'])) {
|
||||
$participantIds = explode(',', $article['participants']);
|
||||
} else {
|
||||
$participantIds = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get selected participants from old input
|
||||
$selectedParticipants = [];
|
||||
if (!empty($oldInput['participants'])) {
|
||||
if (is_array($oldInput['participants'])) {
|
||||
$selectedParticipants = $oldInput['participants'];
|
||||
} else {
|
||||
$selectedParticipants = [$oldInput['participants']];
|
||||
}
|
||||
} elseif (isset($participantIds)) {
|
||||
$selectedParticipants = $participantIds;
|
||||
}
|
||||
|
||||
// Check if we have an uploaded image in session or from old input
|
||||
$uploadedImage = null;
|
||||
if (isset($_SESSION['uploaded_image'])) {
|
||||
$uploadedImage = $_SESSION['uploaded_image'];
|
||||
unset($_SESSION['uploaded_image']);
|
||||
} elseif ($article && $article['article_image']) {
|
||||
$uploadedImage = $article['article_image'];
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="article-form">
|
||||
<div class="section-header">
|
||||
<h2><?= $article ? 'Artikel Bewerken' : 'Nieuw Artikel Indienen' ?></h2>
|
||||
<a href="/" class="btn btn-secondary btn-small">
|
||||
<span class="icon">×</span> Annuleren
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form action="/submit/store" method="post" enctype="multipart/form-data">
|
||||
<?php if ($article): ?>
|
||||
<input type="hidden" name="article_id" value="<?= $article['id'] ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="title">Titel *</label>
|
||||
<input type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
value="<?= htmlspecialchars($oldInput['title'] ?? '') ?>"
|
||||
required
|
||||
class="form-control <?= isset($errors['title']) ? 'error' : '' ?>"
|
||||
placeholder="Voer de titel in">
|
||||
<?php if (isset($errors['title'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['title']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group full-width">
|
||||
<label for="content">Tekst *</label>
|
||||
<textarea name="content"
|
||||
id="content"
|
||||
rows="8"
|
||||
required
|
||||
class="form-control <?= isset($errors['content']) ? 'error' : '' ?>"
|
||||
placeholder="Schrijf hier de inhoud van je artikel..."><?= htmlspecialchars($oldInput['content'] ?? '') ?></textarea>
|
||||
<?php if (isset($errors['content'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['content']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="writer">Schrijver *</label>
|
||||
<select name="writer"
|
||||
id="writer"
|
||||
required
|
||||
class="form-control <?= isset($errors['writer']) ? 'error' : '' ?>">
|
||||
<option value="">-- Selecteer schrijver --</option>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"
|
||||
<?= (($oldInput['writer'] ?? '') == $user['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($errors['writer'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['writer']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="event_date">Datum van het evenement *</label>
|
||||
<input type="date"
|
||||
name="event_date"
|
||||
id="event_date"
|
||||
value="<?= htmlspecialchars($oldInput['event_date'] ?? date('Y-m-d')) ?>"
|
||||
required
|
||||
class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group full-width">
|
||||
<label>Deelnemers *</label>
|
||||
<p class="help-text">Selecteer alle personages die bij dit verhaal betrokken zijn.</p>
|
||||
<div class="checkbox-group">
|
||||
<?php foreach ($users as $user): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox"
|
||||
name="participants[]"
|
||||
value="<?= $user['id'] ?>"
|
||||
<?= (in_array($user['id'], $selectedParticipants)) ? 'checked' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<p class="help-text">Selecteer minstens één deelnemer</p>
|
||||
<?php if (isset($errors['participants'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['participants']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group full-width">
|
||||
<label for="image">Afbeelding *</label>
|
||||
<div class="file-upload-wrapper">
|
||||
<input type="file"
|
||||
name="image"
|
||||
id="image"
|
||||
accept="image/*"
|
||||
required>
|
||||
<label for="image" class="file-upload-label">
|
||||
<span class="icon">📷</span> Kies een afbeelding
|
||||
</label>
|
||||
</div>
|
||||
<p class="help-text file-upload-info">
|
||||
Upload een foto bij het artikel. Zowel landscape als portret foto's worden ondersteund.
|
||||
</p>
|
||||
|
||||
<?php if ($uploadedImage): ?>
|
||||
<div class="image-preview mt-20">
|
||||
<div class="image-preview-container">
|
||||
<img src="/<?= htmlspecialchars($uploadedImage) ?>" alt="Geselecteerde afbeelding">
|
||||
</div>
|
||||
<p class="text-muted mt-10">Afbeelding succesvol geupload: <?= htmlspecialchars(basename($uploadedImage)) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($errors['image'])): ?>
|
||||
<span class="error-message"><?= htmlspecialchars($errors['image']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="icon"><?= $article ? '✓' : '+' ?></span>
|
||||
<?= $article ? 'Artikel Bijwerken' : 'Artikel Indienen' ?>
|
||||
</button>
|
||||
<a href="/" class="btn btn-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
$page_title = 'Artikel Indienen - Beveiliging';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="passphrase-container slide-up">
|
||||
<h2 class="title">Beveiligingscontrole</h2>
|
||||
<p>Voer de familypaszin in om een artikel in te dienen:</p>
|
||||
|
||||
<form action="/submit/verify" method="post">
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Familie Paszin</label>
|
||||
<input type="password"
|
||||
name="passphrase"
|
||||
id="passphrase"
|
||||
class="form-control"
|
||||
required
|
||||
autocomplete="off"
|
||||
placeholder="Voer de paszin in">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="icon">→</span> Doorgaan
|
||||
</button>
|
||||
<a href="/" class="btn btn-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
$page_title = 'Verifieer Schrijver';
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="writer-verification slide-up">
|
||||
<h2 class="title">Verifieer Schrijver</h2>
|
||||
|
||||
<?php if ($article): ?>
|
||||
<div class="card mb-30">
|
||||
<h4 class="card-title"><?= htmlspecialchars($article['title']) ?></h4>
|
||||
<p class="text-muted">
|
||||
Om dit artikel te bewerken, bevestig aub wie de schrijver is.
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/submit/edit/verify-writer" method="post">
|
||||
<input type="hidden" name="article_id" value="<?= htmlspecialchars($article['id'] ?? '') ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="writer_id">Selecteer de Schrijver *</label>
|
||||
<select name="writer_id"
|
||||
id="writer_id"
|
||||
required
|
||||
class="form-control">
|
||||
<option value="">-- Selecteer de schrijver --</option>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"
|
||||
<?= ($article && $article['author_id'] == $user['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($user['display_name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="icon">→</span> Verifieer en Bewerk
|
||||
</button>
|
||||
<a href="/submit/edit/list" class="btn btn-secondary">
|
||||
<span class="icon">←</span> Terug naar Lijst
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../layouts/main.php';
|
||||
?>
|
||||
Executable
Executable
+54
@@ -0,0 +1,54 @@
|
||||
\documentclass{article}
|
||||
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{microtype}
|
||||
|
||||
\usepackage{newspaper}
|
||||
|
||||
\usepackage[dutch]{babel}
|
||||
|
||||
\date{\today}
|
||||
\currentvolume{1}
|
||||
\currentissue{1}
|
||||
|
||||
%% The banner headline on the first page
|
||||
%% (The colon after s: is to get a more
|
||||
%% modern majuscule s in this font instead of
|
||||
%% the medieval tall s.
|
||||
\SetPaperName{Proo{s:}tenKrant}
|
||||
|
||||
%% The name used in the running header after
|
||||
%% the first page
|
||||
\SetHeaderName{ProostenKrant}
|
||||
|
||||
%% and also...
|
||||
\SetPaperLocation{Regio Kempen}
|
||||
\SetPaperSlogan{``Een krant van en voor Proosten.''}
|
||||
\SetPaperPrice{NUL EURO}
|
||||
|
||||
\usepackage{graphicx}
|
||||
\usepackage{tikz}
|
||||
\usepackage{multicol}
|
||||
\usepackage{geometry}
|
||||
\usepackage{array}
|
||||
\usepackage{wrapfig}
|
||||
\usepackage{lipsum}
|
||||
\usepackage{newtxtext,newtxmath}
|
||||
\usepackage{etoolbox}
|
||||
|
||||
% Custom styling for better image handling
|
||||
\input{newspaper-mod}
|
||||
|
||||
%%%%%%%%% Front matter %%%%%%%%%%
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\begin{multicols}{3}
|
||||
|
||||
% Articles will be inserted here by the PHP generator
|
||||
|
||||
\newpage
|
||||
\end{multicols}
|
||||
\end{document}
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
% Improved newspaper-mod.sty for ProostenKrant
|
||||
% Better image handling for different formats and sizes
|
||||
|
||||
\newcommand{\headlinestyle}{\itshape\huge}
|
||||
\newcommand{\bylinestyle}{\scshape\Large}
|
||||
\patchcmd{\headline}{#1}{\headlinestyle #1}{}{}
|
||||
\patchcmd{\byline}{#1}{\bylinestyle #1}{}{}
|
||||
|
||||
% Improved image commands with better handling of different aspect ratios
|
||||
|
||||
% Show inline image with caption (automatically handles landscape and portrait)
|
||||
\newcommand{\showInlineImage}[2]
|
||||
{
|
||||
\\\n \hspace*{0pt}% Ensure proper spacing
|
||||
\vspace*{10pt}% Add space before image
|
||||
\begin{center}
|
||||
\begin{minipage}{0.95\columnwidth}
|
||||
\centering
|
||||
% Use max dimensions that fit in column
|
||||
\setlength{\fboxsep}{5pt}
|
||||
\framebox{
|
||||
% Check image dimensions and scale appropriately
|
||||
\includegraphics[max width=\linewidth, max height=0.6\textheight, keepaspectratio]{#1}
|
||||
}
|
||||
\vspace*{5pt}
|
||||
{\small #2}
|
||||
\end{minipage}
|
||||
\end{center}
|
||||
\vspace*{10pt}% Add space after image
|
||||
\par
|
||||
}
|
||||
|
||||
% Show single writer with circular profile image
|
||||
\newcommand{\showOneWriter}[2]
|
||||
{
|
||||
\\\n \hfill
|
||||
\begin{minipage}[t]{0.4\columnwidth}
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
% Circular clip for profile image
|
||||
\clip (0,0) circle (1.3cm);
|
||||
% Use square aspect ratio and scale to fit
|
||||
\node[anchor=center] at (0,0) {
|
||||
\includegraphics[width=2.8cm, height=2.8cm, keepaspectratio]{#1}
|
||||
};
|
||||
\end{tikzpicture}
|
||||
\vspace*{5pt}
|
||||
{\small #2}
|
||||
\end{minipage}
|
||||
\hfill
|
||||
\\\n}
|
||||
|
||||
% Show two writers with circular profile images
|
||||
\newcommand{\showTwoWriters}[4]
|
||||
{
|
||||
\\\n \begin{center}
|
||||
\begin{minipage}[t]{0.8\columnwidth}
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\clip (0,0) circle (1.1cm);
|
||||
\node[anchor=west] at (0,0) {
|
||||
\includegraphics[width=2.4cm, height=2.4cm, keepaspectratio]{#1}
|
||||
};
|
||||
\end{tikzpicture}
|
||||
\hspace*{15pt}
|
||||
\begin{tikzpicture}
|
||||
\clip (0,0) circle (1.1cm);
|
||||
\node[anchor=east] at (0,0) {
|
||||
\includegraphics[width=2.4cm, height=2.4cm, keepaspectratio]{#3}
|
||||
};
|
||||
\end{tikzpicture}
|
||||
\vspace*{8pt}
|
||||
{\small #2 \\quad #4}
|
||||
\end{minipage}
|
||||
\end{center}
|
||||
\\\n}
|
||||
|
||||
% Alternative commands for different layouts
|
||||
|
||||
% For portrait images - maintains aspect ratio
|
||||
\newcommand{\showPortraitImage}[2]
|
||||
{
|
||||
\\\n \begin{minipage}[t]{0.45\columnwidth}
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
% For portrait images, use vertical layout
|
||||
\clip (0,-1.5cm) rectangle (2cm,1.5cm);
|
||||
\node[anchor=center] at (1cm,0) {
|
||||
\includegraphics[height=3cm, keepaspectratio]{#1}
|
||||
};
|
||||
\end{tikzpicture}
|
||||
\vspace*{5pt}
|
||||
{\small #2}
|
||||
\end{minipage}
|
||||
\\\n}
|
||||
|
||||
% For landscape images - maintains aspect ratio
|
||||
\newcommand{\showLandscapeImage}[2]
|
||||
{
|
||||
\\\n \begin{minipage}[t]{\columnwidth}
|
||||
\centering
|
||||
% For landscape images, use full width
|
||||
\includegraphics[width=\linewidth, height=0.4\textheight, keepaspectratio]{#1}
|
||||
\vspace*{5pt}
|
||||
{\small #2}
|
||||
\end{minipage}
|
||||
\\\n}
|
||||
|
||||
% Flexible image wrapper that automatically handles different orientations
|
||||
\newcommand{\showFlexibleImage}[2]
|
||||
{
|
||||
% First, we need to check the image dimensions
|
||||
% This requires the epstopdf package and shell-escape
|
||||
\IfFileExists{#1}{
|
||||
% Image exists, try to determine orientation
|
||||
% For now, we'll use a balanced approach that works for both
|
||||
\showInlineImage{#1}{#2}
|
||||
}{}
|
||||
}
|
||||
Reference in New Issue
Block a user