PHP/MySQL Simply.com Skill
Formål
Denne skill bruges når TuxCore hjælper Thomas med PHP/MySQL-projekter, især projekter der skal køre på Simply.com uden tunge frameworks.
Den er relevant for:
- TuxiNet.dk projekter
- RU-Teknik / Rute & Udstyr - Teknik
- EventPortal
- Byggemøde-systemer
- Billed-upload/moderation
- Adminpaneler
- Login-systemer
- Invitationer og adgangskoder
- Små interne webapps
- PHP-sider med MySQL/MariaDB
Standardprincipper
- Brug almindelig PHP uden unødvendige frameworks.
- Brug MySQL/MariaDB via PDO.
- Brug prepared statements.
- Brug sessions til login.
- Brug includes til config, database og fælles funktioner.
- Brug dansk UI som standard.
- Gør løsningen mobilvenlig.
- Gør filer copy/paste-klar.
- Hold kode Simply.com-venlig.
- Undgå hardcodede secrets i public filer.
- Brug PHPMailer-skillen ved mailintegration.
Typisk struktur
project/
├── index.php
├── login.php
├── logout.php
├── dashboard.php
├── admin/
│ └── index.php
├── assets/
│ ├── style.css
│ └── app.js
├── includes/
│ ├── config.php
│ ├── db.php
│ ├── auth.php
│ ├── functions.php
│ └── csrf.php
├── uploads/
│ └── .htaccess
└── .htaccess
Hvis config kan ligge udenfor public webroot, foretrækkes det:
/private/
├── db_config.php
├── mail_config.php
└── vendor/
└── autoload.php
/public_html/
└── project/
├── index.php
└── includes/
Databaseforbindelse
Brug PDO som standard:
<?php
declare(strict_types=1);
$DB_HOST = 'localhost';
$DB_NAME = 'database_name';
$DB_USER = 'database_user';
$DB_PASS = 'database_password';
$DB_CHARSET = 'utf8mb4';
$dsn = "mysql:host={$DB_HOST};dbname={$DB_NAME};charset={$DB_CHARSET}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $DB_USER, $DB_PASS, $options);
} catch (PDOException $e) {
http_response_code(500);
exit('Databaseforbindelse fejlede.');
}
Prepared statements
Alle brugerinput skal gennem prepared statements:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
$stmt->execute([
':email' => $email,
]);
$user = $stmt->fetch();
Indsættelse:
$stmt = $pdo->prepare('INSERT INTO users (name, email, created_at) VALUES (:name, :email, NOW())');
$stmt->execute([
':name' => $name,
':email' => $email,
]);
Opdatering:
$stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
$stmt->execute([
':name' => $name,
':id' => $id,
]);
Login og sessions
Sessions skal startes sikkert:
<?php
declare(strict_types=1);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
Login-check:
function require_login(): void
{
if (empty($_SESSION['user_id'])) {
header('Location: /login.php');
exit;
}
}
Efter login:
session_regenerate_id(true);
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_role'] = $user['role'];
Logout:
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
session_destroy();
header('Location: /login.php');
exit;
Passwords
Brug altid PHPs password-funktioner:
$hash = password_hash($password, PASSWORD_DEFAULT);
Verificering:
if (!password_verify($password, $user['password_hash'])) {
$error = 'Forkert login.';
}
Roller og rettigheder
Brug simple roller som standard:
admin
suadmin
user
guest
crew
dj
tekniker
dc
kontor
Rolle-check:
function has_role(array|string $roles): bool
{
$roles = (array)$roles;
return isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], $roles, true);
}
CSRF-beskyttelse
Forms der ændrer data bør bruge CSRF-token:
function csrf_token(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function csrf_validate(?string $token): bool
{
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], (string)$token);
}
Form input:
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') ?>">
POST-check:
if (!csrf_validate($_POST['csrf_token'] ?? null)) {
http_response_code(403);
exit('Ugyldig forespørgsel.');
}
Output escaping
Alt output fra database/brugerinput skal escapes:
function e(?string $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
Brug:
<?= e($row['title']) ?>
Uploads
Uploadfunktioner skal validere:
- Filtype
- MIME type
- Filstørrelse
- Filnavn
- Uploadmappe
- Adgang til filen
Uploads bør ikke kunne køre PHP.
Eksempel .htaccess i uploadmappe:
Options -Indexes
<FilesMatch "\.(php|php3|php4|php5|phtml|phar)$">
Require all denied
</FilesMatch>
Gem upload med sikkert navn:
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'webp', 'gif'];
if (!in_array($ext, $allowed, true)) {
exit('Ugyldig filtype.');
}
$filename = bin2hex(random_bytes(16)) . '.' . $ext;
$target = __DIR__ . '/../uploads/' . $filename;
move_uploaded_file($_FILES['file']['tmp_name'], $target);
.htaccess
Standard for at beskytte mapper:
Options -Indexes
Hvis clean URLs bruges:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
Mailintegration
Ved mail skal denne skill kombineres med:
skills/php-simply-mail/SKILL.md
Standardregel:
- Brug ikke PHP
mail()direkte. - Brug PHPMailer via Composer autoload.
- Mail config skal ikke ligge public.
- Returnér kontrolleret status fra mailfunktioner.
Standard UI
PHP-projekter bør have:
- Mobilvenligt layout
- Tydelige knapper
- Mørk/neutral farvepalette når relevant
- Brugbare fejlbeskeder
- Ikke for små touch targets
- Simpel navigation
- Footer med TuxiNet.dk når relevant
Fejlhåndtering
Brug ikke rå PHP-fejl på produktion.
I udvikling:
ini_set('display_errors', '1');
error_reporting(E_ALL);
I produktion:
ini_set('display_errors', '0');
error_reporting(E_ALL);
Log fejl server-side:
error_log('Fejlbesked her');
Vis brugervenlig fejl:
exit('Der opstod en fejl. Prøv igen senere.');
Når TuxCore laver $php $sql $full
Lever som udgangspunkt:
- Komplet filstruktur.
- Komplet database schema.
- Komplet config/database include.
- Komplet login/session-flow hvis relevant.
- Alle nødvendige PHP-filer.
- CSS og JS hvis relevant.
- Installationsvejledning.
- Testtrin.
- Sikkerhedsnoter.
Når TuxCore laver $php $patch
Lever kun:
- De nødvendige ændringer.
- Filnavn og placering.
- Kodeblok til udskiftning/indsættelse.
- Kort forklaring.
- Testtrin.
Når TuxCore laver $debug
Svar med:
- Hvad fejlen betyder.
- Mest sandsynlige årsag.
- Testkommandoer eller testtrin.
- Konkret fix.
- Næste skridt.
Typiske fejl
Blank side
Mulige årsager:
- PHP syntax-fejl
- Fatal error skjult
- Forkert include path
- Manglende databaseforbindelse
Test:
ini_set('display_errors', '1');
error_reporting(E_ALL);
Headers already sent
Mulige årsager:
- Output før
header() - Ekstra mellemrum før
<?php - BOM i filen
Fix:
- Sørg for ingen output før redirects
- Fjern afsluttende
?>i rene PHP-filer
Database login fejler
Mulige årsager:
- Forkert databasehost
- Forkert brugernavn/password
- Forkert databasenavn
- Manglende rettigheder
Fix:
- Tjek Simply databaseoplysninger
- Test med en minimal PDO-testfil
Session virker ikke
Mulige årsager:
session_start()mangler- Output før session start
- Forkert cookie path
- Redirect sker for sent
Fix:
- Start session tidligt i bootstrap/auth include
Acceptkriterier
En PHP/MySQL løsning er godkendt når:
- Den virker på Simply.com uden framework-krav.
- Databasekald bruger prepared statements.
- Login bruger sessions korrekt.
- Passwords hashes med
password_hash. - Output escapes med
htmlspecialchars. - Forms der ændrer data har CSRF-beskyttelse.
- Secrets ligger ikke i public filer.
- Uploads er beskyttet.
- Koden er copy/paste-klar.
- Der findes klare testtrin.
Tags
php, mysql, mariadb, pdo, simply, tuxcore, tuxinet, login, adminpanel, csrf, upload