From d9448a673c81772be9adc99d3d41b2fdfd435d4c Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Tue, 9 Dec 2025 10:07:14 +0100 Subject: [PATCH] initial release --- .htaccess | 25 ++ OPENSSL_SETUP.md | 100 ++++++ README.md | 203 ++++++++++++ config.php | 74 +++++ elsa.php | 526 +++++++++++++++++++++++++++++++ functions.php | 435 ++++++++++++++++++++++++++ index.php | 145 +++++++++ license.php | 64 ++++ semesterapparat.php | 200 ++++++++++++ static/styles.css | 619 +++++++++++++++++++++++++++++++++++++ submit_elsa.php | 139 +++++++++ submit_semesterapparat.php | 84 +++++ test_email.php | 56 ++++ 13 files changed, 2670 insertions(+) create mode 100644 .htaccess create mode 100644 OPENSSL_SETUP.md create mode 100644 README.md create mode 100644 config.php create mode 100644 elsa.php create mode 100644 functions.php create mode 100644 index.php create mode 100644 license.php create mode 100644 semesterapparat.php create mode 100644 static/styles.css create mode 100644 submit_elsa.php create mode 100644 submit_semesterapparat.php create mode 100644 test_email.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..c69da62 --- /dev/null +++ b/.htaccess @@ -0,0 +1,25 @@ +# Prevent directory browsing +Options -Indexes + +# Enable rewrite engine (optional, for clean URLs) +RewriteEngine On + +# Redirect /semesterapparat to semesterapparat.php +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^semesterapparat$ semesterapparat.php [L] + +# Redirect /elsa to elsa.php +RewriteRule ^elsa$ elsa.php [L] + +# Protect config files + + Order allow,deny + Deny from all + + +# Set default charset +AddDefaultCharset UTF-8 + +# Error pages (customize as needed) +ErrorDocument 404 /index.php diff --git a/OPENSSL_SETUP.md b/OPENSSL_SETUP.md new file mode 100644 index 0000000..df6e21c --- /dev/null +++ b/OPENSSL_SETUP.md @@ -0,0 +1,100 @@ +# Enabling OpenSSL Extension in PHP (Windows) + +## Problem +Your PHP installation doesn't have the OpenSSL extension enabled, which is required for secure SMTP connections (TLS/SSL). + +Current error: `Unable to find the socket transport "ssl"` or `TLS/SSL transports not available` + +## Solution + +### Option 1: Enable OpenSSL in php.ini (Recommended) + +1. **Find your php.ini file:** + ```powershell + php --ini + ``` + + If it shows "Loaded Configuration File: (none)", you need to create one: + ```powershell + # Find PHP installation directory + where.exe php + + # Copy the example php.ini + cd C:\path\to\php + copy php.ini-development php.ini + ``` + +2. **Edit php.ini** and find this line: + ```ini + ;extension=openssl + ``` + + Remove the semicolon to uncomment it: + ```ini + extension=openssl + ``` + +3. **Restart your PHP server** (or command line if using built-in server) + +4. **Verify OpenSSL is enabled:** + ```powershell + php -m | Select-String -Pattern "openssl" + ``` + + Should output: `openssl` + +### Option 2: Enable OpenSSL temporarily via command line + +You can enable OpenSSL for a single command: + +```powershell +php -d extension=openssl test_email.php +``` + +Or for the built-in server: + +```powershell +php -d extension=openssl -S localhost:8000 +``` + +### Option 3: Install Composer and PHPMailer + +If you can't modify php.ini, install PHPMailer which may handle SMTP better: + +```powershell +# Install Composer first (https://getcomposer.org/) +# Then in the php/ directory: +composer require phpmailer/phpmailer +``` + +The application will automatically detect and use PHPMailer if available. + +## Verifying the Fix + +After enabling OpenSSL, run the test: + +```powershell +cd C:\Users\aky547\GitHub\SemapForm\php +php test_email.php +``` + +You should see: +- `Available transports: tcp, udp, ssl, tls` (or similar) +- `TLS encryption enabled successfully` +- `Email sent successfully via SMTP` + +## Alternative: Use a Different SMTP Port + +If you absolutely cannot enable OpenSSL, you could try: + +1. Contact your email administrator to see if there's an unencrypted SMTP port (not recommended for security) +2. Use a different email solution (e.g., send via a web service API instead of SMTP) + +## Production Deployment + +On a production server (Linux/shared hosting): + +- OpenSSL is usually enabled by default +- Check with `php -m | grep openssl` +- If not available, contact your hosting provider +- Most shared hosting environments have it pre-configured diff --git a/README.md b/README.md new file mode 100644 index 0000000..84a0bb2 --- /dev/null +++ b/README.md @@ -0,0 +1,203 @@ +# PHP Deployment Guide for SemapForm + +## Requirements + +- PHP 7.4 or higher (PHP 8+ recommended) +- PHP extensions: + - `dom` (for XML generation) + - `mbstring` (for string handling) + - `openssl` (for SMTP if using SSL/TLS) +- Web server (Apache, Nginx, or any PHP-capable server) +- Optional: PHPMailer library for advanced SMTP support + +## Installation Steps + +1. **Upload files to your PHP server:** + ``` + php/ + ├── index.php + ├── semesterapparat.php + ├── submit_semesterapparat.php + ├── config.php + ├── functions.php + ├── .htaccess (if using Apache) + └── static/ + └── styles.css + ``` + +2. **Configure email settings:** + + Edit `config.php` or set environment variables: + - `MAIL_ENABLED`: Set to `true` to enable email sending + - `SMTP_HOST`: Your SMTP server (e.g., smtp.ph-freiburg.de) + - `SMTP_PORT`: SMTP port (465 for SSL, 587 for TLS, 25 for plain) + - `SMTP_USERNAME`: Your SMTP username + - `SMTP_PASSWORD`: Your SMTP password + - `MAIL_FROM`: Sender email address + - `MAIL_TO`: Recipient email address + +3. **Set permissions:** + ```bash + chmod 755 *.php + chmod 644 config.php + chmod 644 static/styles.css + ``` + +4. **Test the installation:** + - Navigate to your server URL (e.g., `https://yourserver.com/php/`) + - Try submitting a test form + - Check email delivery or server logs + +## Email Configuration Options + +### Option 1: PHP's built-in mail() function +- Simplest setup +- Requires server to have mail transfer agent (MTA) configured +- No additional configuration needed in PHP + +### Option 2: SMTP with PHP's native functions +- Configure SMTP settings in `config.php` +- Works with basic authentication +- Current implementation in `functions.php` + +### Option 3: PHPMailer (Recommended for production) +- Install PHPMailer via Composer: + ```bash + composer require phpmailer/phpmailer + ``` +- Better error handling and SMTP support +- Already integrated in `functions.php` (will auto-detect if available) + +## Server-Specific Notes + +### Apache +- The `.htaccess` file is included for URL rewriting and security +- Ensure `mod_rewrite` is enabled + +### Nginx +- Add this to your nginx.conf: + ```nginx + location /php { + index index.php; + try_files $uri $uri/ /index.php?$query_string; + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + } + } + ``` + +### Shared Hosting +- Most shared hosts have PHP and mail() pre-configured +- Upload files via FTP/SFTP +- Set environment variables through hosting control panel +- Test mail() function first before adding SMTP + +## Email Logging + +The application now includes comprehensive email logging: + +- **Log Location**: `php/mail.log` +- **Log Format**: `[YYYY-MM-DD HH:MM:SS] [LEVEL] Message` +- **Configuration**: + - Set `LOG_ENABLED = true` in `config.php` to enable logging + - Logs are also written to PHP error_log with `[MAIL]` prefix + +### What Gets Logged + +- Email send attempts with from/to/subject +- Email content length +- SMTP connection details (host, port, encryption) +- PHPMailer initialization and configuration steps +- Success/failure status for each send attempt +- Detailed error messages for failed sends +- When mail is disabled (includes full XML content) + +### Viewing Logs + +```bash +# View recent log entries +tail -f php/mail.log + +# View last 50 lines +tail -n 50 php/mail.log + +# Search for errors +grep ERROR php/mail.log + +# Search for successful sends +grep SUCCESS php/mail.log +``` + +### Log File Management + +The log file is automatically created when needed. To rotate logs: + +```bash +# Manual rotation +mv php/mail.log php/mail.log.$(date +%Y%m%d) +touch php/mail.log +chmod 666 php/mail.log +``` + +## Troubleshooting + +### Emails not sending +1. Check `MAIL_ENABLED` is set to `true` +2. **Check `php/mail.log`** for detailed error messages +3. Verify SMTP credentials are correct +4. Check server error logs: `tail -f /var/log/apache2/error.log` +5. Test with `MAIL_ENABLED=false` to see XML output in logs + +### Form validation errors +- Ensure all required fields have values +- Check email format validation +- Verify POST data is being received + +### Permission errors +- Ensure PHP has read access to all files +- Check web server user/group permissions + +## Security Recommendations + +1. **Move config.php outside web root** if possible +2. **Use environment variables** for sensitive data +3. **Enable HTTPS** for form submissions +4. **Sanitize all inputs** (already implemented in `functions.php`) +5. **Set production error reporting** in `config.php`: + ```php + error_reporting(0); + ini_set('display_errors', 0); + ``` +6. **Regular updates**: Keep PHP and server software updated + +## Migrating from Docker/Python + +The PHP version maintains feature parity with the Python/FastAPI version: +- ✅ Same form fields and validation +- ✅ XML generation with identical structure +- ✅ Email sending with SMTP support +- ✅ Same CSS and frontend behavior +- ✅ Theme toggle functionality +- ✅ Multi-book/media support +- ✅ Optional fields (title, signature, Dauerapparat) + +## Environment Variables + +You can set these as server environment variables instead of editing `config.php`: + +```bash +export MAIL_ENABLED=true +export SMTP_HOST=smtp.ph-freiburg.de +export SMTP_PORT=465 +export MAIL_USERNAME=your_username +export MAIL_PASSWORD=your_password +export MAIL_FROM=alexander.kirchner@ph-freiburg.de +export MAIL_TO=semesterapparate@ph-freiburg.de +``` + +## Support + +For issues specific to your hosting environment, consult your hosting provider's PHP documentation. diff --git a/config.php b/config.php new file mode 100644 index 0000000..304e8f9 --- /dev/null +++ b/config.php @@ -0,0 +1,74 @@ + + + + + ELSA - Elektronischer Semesterapparat + + + + + + + +
+
+

ELSA - Elektronischer Semesterapparat

+ + + +
+

Allgemeine Informationen

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +

Medien

+
+ + + +
+ Fügen Sie hier die gewünschten Medientypen hinzu. Für jeden Medientyp können Sie mehrere Einträge anlegen. +
+ Sollte ein Beitrag mehrere AutorInnen oder EditorInnen haben, sollten diese mit einem ; getrennt werden. + +
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + + + diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..cd1cc4c --- /dev/null +++ b/functions.php @@ -0,0 +1,435 @@ +formatOutput = true; + + $root = $xml->createElement('form_submission'); + $xml->appendChild($root); + + // Static data + $static = $xml->createElement('static'); + $root->appendChild($static); + + foreach (['name', 'lastname', 'title', 'telno', 'mail', 'apparatsname', 'subject', 'semester', 'dauerapparat'] as $field) { + if (isset($data[$field])) { + $element = $xml->createElement($field, htmlspecialchars($data[$field])); + $static->appendChild($element); + } + } + + if (!empty($data['message'])) { + $messageEl = $xml->createElement('message', htmlspecialchars($data['message'])); + $static->appendChild($messageEl); + } + + // Books + if (isset($data['books']) && is_array($data['books'])) { + $booksNode = $xml->createElement('books'); + $root->appendChild($booksNode); + + foreach ($data['books'] as $book) { + $bookNode = $xml->createElement('book'); + $booksNode->appendChild($bookNode); + + foreach (['authorname', 'year', 'title', 'signature'] as $field) { + $value = isset($book[$field]) ? htmlspecialchars($book[$field]) : ''; + $fieldNode = $xml->createElement($field, $value); + $bookNode->appendChild($fieldNode); + } + } + } + + return $xml->saveXML(); +} + +/** + * Generate XML for ELSA form + */ +function generateELSAXML($data) +{ + $xml = new DOMDocument('1.0', 'UTF-8'); + $xml->formatOutput = true; + + $root = $xml->createElement('elsa_submission'); + $xml->appendChild($root); + + // General info + $generalInfo = $xml->createElement('general_info'); + $root->appendChild($generalInfo); + + $generalFields = [ + 'name', + 'lastname', + 'title', + 'mail', + 'subject', + 'classname', + 'usage_date_from', + 'usage_date_to', + 'availability_date' + ]; + + foreach ($generalFields as $field) { + if (isset($data[$field])) { + $element = $xml->createElement($field, htmlspecialchars($data[$field])); + $generalInfo->appendChild($element); + } + } + + if (!empty($data['message'])) { + $messageEl = $xml->createElement('message', htmlspecialchars($data['message'])); + $generalInfo->appendChild($messageEl); + } + + // Media sections + $mediaRoot = $xml->createElement('media'); + $root->appendChild($mediaRoot); + + // Add different media types (monografie, zeitschrift, herausgeber) + $mediaTypes = [ + 'monografien' => $data['monografien'] ?? [], + 'zeitschriftenartikel' => $data['zeitschriftenartikel'] ?? [], + 'herausgeberwerke' => $data['herausgeberwerke'] ?? [] + ]; + + foreach ($mediaTypes as $type => $entries) { + if (!empty($entries)) { + $section = $xml->createElement($type); + $mediaRoot->appendChild($section); + + foreach ($entries as $entry) { + $entryNode = $xml->createElement('entry'); + $section->appendChild($entryNode); + + foreach ($entry as $key => $value) { + $fieldNode = $xml->createElement($key, htmlspecialchars($value)); + $entryNode->appendChild($fieldNode); + } + } + } + } + + return $xml->saveXML(); +} + +/** + * Send email with XML attachment + * Uses PHP's mail() function or SMTP if configured + */ +function sendEmail($subject, $xmlContent, $toEmail = null) +{ + $to = $toEmail ?? MAIL_TO; + $from = MAIL_FROM; + + writeLog("=========================================================="); + writeLog("Email Send Attempt"); + writeLog("From: {$from}"); + writeLog("To: {$to}"); + writeLog("Subject: {$subject}"); + writeLog("Content Length: " . strlen($xmlContent) . " bytes"); + + if (!MAIL_ENABLED) { + writeLog("MAIL SENDING DISABLED - Email not sent", 'WARNING'); + writeLog("XML Content:\n" . $xmlContent); + writeLog("=========================================================="); + return true; + } + + // Try using SMTP if credentials are configured + if (SMTP_USERNAME && SMTP_PASSWORD) { + writeLog("SMTP credentials configured, attempting SMTP send"); + $result = sendEmailSMTP($subject, $xmlContent, $to, $from); + + if ($result) { + writeLog("Email sent successfully via SMTP", 'SUCCESS'); + } else { + writeLog("Email sending via SMTP failed", 'ERROR'); + } + + writeLog("=========================================================="); + return $result; + } + + // Fallback to PHP mail() only if no SMTP credentials + writeLog("No SMTP credentials configured, using PHP mail() function"); + $headers = "From: " . $from . "\r\n"; + $headers .= "Content-Type: application/xml; charset=UTF-8\r\n"; + $headers .= "X-Mailer: PHP/" . phpversion(); + + $result = mail($to, $subject, $xmlContent, $headers); + + if ($result) { + writeLog("Email sent successfully via mail()", 'SUCCESS'); + } else { + writeLog("Email sending via mail() failed", 'ERROR'); + } + + writeLog("=========================================================="); + return $result; +} + +/** + * Send email via SMTP using PHPMailer (if available) or native PHP sockets + */ +function sendEmailSMTP($subject, $xmlContent, $to, $from) +{ + // Try PHPMailer first if available + if (class_exists('PHPMailer\PHPMailer\PHPMailer')) { + writeLog("Initializing PHPMailer for SMTP send"); + writeLog("SMTP Host: " . SMTP_HOST . ":" . SMTP_PORT); + writeLog("SMTP Encryption: " . SMTP_ENCRYPTION); + writeLog("SMTP Username: " . SMTP_USERNAME); + + $mail = new PHPMailer\PHPMailer\PHPMailer(true); + + try { + // Server settings + $mail->isSMTP(); + $mail->Host = SMTP_HOST; + $mail->SMTPAuth = true; + $mail->Username = SMTP_USERNAME; + $mail->Password = SMTP_PASSWORD; + $mail->SMTPSecure = SMTP_ENCRYPTION; + $mail->Port = SMTP_PORT; + $mail->CharSet = 'UTF-8'; + + writeLog("SMTP connection configured"); + + // Recipients + $mail->setFrom($from); + $mail->addAddress($to); + + writeLog("Recipients configured"); + + // Content - XML as body + $mail->isHTML(false); + $mail->Subject = $subject; + $mail->Body = $xmlContent; + $mail->ContentType = 'text/plain'; + + writeLog("Sending email via SMTP..."); + $mail->send(); + + writeLog("SMTP send() completed successfully", 'SUCCESS'); + return true; + } catch (Exception $e) { + writeLog("SMTP Exception: " . $e->getMessage(), 'ERROR'); + writeLog("PHPMailer ErrorInfo: " . $mail->ErrorInfo, 'ERROR'); + return false; + } + } + + // Helper function to read SMTP multi-line response + $readResponse = function ($socket) { + $response = ''; + while ($line = fgets($socket, 515)) { + $response .= $line; + // Check if this is the last line (code followed by space, not hyphen) + if (preg_match('/^\d{3} /', $line)) { + break; + } + } + return $response; + }; + + // Fallback to native PHP SMTP + writeLog("PHPMailer not available, using native PHP SMTP implementation"); + writeLog("SMTP Host: " . SMTP_HOST . ":" . SMTP_PORT); + writeLog("SMTP Encryption: " . SMTP_ENCRYPTION); + + // Check if required transports are available + $transports = stream_get_transports(); + writeLog("Available transports: " . implode(', ', $transports)); + + if (SMTP_ENCRYPTION === 'ssl' && !in_array('ssl', $transports)) { + writeLog("SSL transport not available. Enable OpenSSL extension in PHP.", 'ERROR'); + writeLog("Try changing SMTP_ENCRYPTION to 'tls' and SMTP_PORT to 587 in config.php", 'ERROR'); + return false; + } + + try { + // Build the connection string - always start with plain TCP for TLS + if (SMTP_ENCRYPTION === 'ssl') { + $host = 'ssl://' . SMTP_HOST; + } else { + $host = SMTP_HOST; + } + $timeout = 30; + + writeLog("Connecting to {$host}:" . SMTP_PORT); + $smtp = @fsockopen($host, SMTP_PORT, $errno, $errstr, $timeout); + + if (!$smtp) { + writeLog("Failed to connect: ({$errno}) {$errstr}", 'ERROR'); + return false; + } + + writeLog("Connected successfully"); + + // Read server response + $response = $readResponse($smtp); + writeLog("Server greeting: " . trim($response)); + + // Send EHLO + fputs($smtp, "EHLO " . SMTP_HOST . "\r\n"); + $response = $readResponse($smtp); + writeLog("EHLO response: " . trim(str_replace("\r\n", " | ", $response))); + + // Start TLS if needed and not using SSL + if (SMTP_ENCRYPTION === 'tls') { + fputs($smtp, "STARTTLS\r\n"); + $response = $readResponse($smtp); + writeLog("STARTTLS response: " . trim($response)); + + if (strpos($response, '220') === 0) { + if (in_array('tls', $transports) || in_array('ssl', $transports)) { + $crypto = @stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + if ($crypto) { + writeLog("TLS encryption enabled successfully"); + fputs($smtp, "EHLO " . SMTP_HOST . "\r\n"); + $response = $readResponse($smtp); + writeLog("EHLO after TLS: " . trim(str_replace("\r\n", " | ", $response))); + } else { + writeLog("Failed to enable TLS encryption", 'WARNING'); + writeLog("Continuing without encryption (not recommended)", 'WARNING'); + } + } else { + writeLog("TLS/SSL transports not available. Enable OpenSSL extension.", 'WARNING'); + writeLog("Continuing without encryption (not recommended)", 'WARNING'); + } + } + } + + // Authenticate + fputs($smtp, "AUTH LOGIN\r\n"); + $response = $readResponse($smtp); + writeLog("AUTH response: " . trim($response)); + + fputs($smtp, base64_encode(SMTP_USERNAME) . "\r\n"); + $response = $readResponse($smtp); + + fputs($smtp, base64_encode(SMTP_PASSWORD) . "\r\n"); + $response = $readResponse($smtp); + + if (strpos($response, '235') !== 0) { + writeLog("Authentication failed: " . trim($response), 'ERROR'); + fclose($smtp); + return false; + } + + writeLog("Authentication successful"); + + // Send MAIL FROM + fputs($smtp, "MAIL FROM: <{$from}>\r\n"); + $response = $readResponse($smtp); + writeLog("MAIL FROM response: " . trim($response)); + + // Send RCPT TO + fputs($smtp, "RCPT TO: <{$to}>\r\n"); + $response = $readResponse($smtp); + writeLog("RCPT TO response: " . trim($response)); + + // Send DATA + fputs($smtp, "DATA\r\n"); + $response = $readResponse($smtp); + writeLog("DATA response: " . trim($response)); + + // Send email headers and body + $headers = "From: {$from}\r\n"; + $headers .= "To: {$to}\r\n"; + $headers .= "Subject: {$subject}\r\n"; + $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $headers .= "X-Mailer: PHP/" . phpversion() . "\r\n"; + $headers .= "\r\n"; + + fputs($smtp, $headers . $xmlContent . "\r\n.\r\n"); + $response = $readResponse($smtp); + writeLog("Message response: " . trim($response)); + + // Quit + fputs($smtp, "QUIT\r\n"); + $readResponse($smtp); // Read QUIT response + fclose($smtp); + + if (strpos($response, '250') === 0) { + writeLog("Email sent successfully via native SMTP", 'SUCCESS'); + return true; + } else { + writeLog("Email sending failed: " . trim($response), 'ERROR'); + return false; + } + + } catch (Exception $e) { + writeLog("Native SMTP Exception: " . $e->getMessage(), 'ERROR'); + return false; + } +} + +/** + * Redirect to URL + */ +function redirect($url) +{ + header("Location: " . $url); + exit; +} + +/** + * Get POST value with default + */ +function post($key, $default = '') +{ + return isset($_POST[$key]) ? sanitizeInput($_POST[$key]) : $default; +} + +/** + * Get all POST values matching a pattern (for arrays) + */ +function postArray($key) +{ + return isset($_POST[$key]) && is_array($_POST[$key]) ? + array_map('sanitizeInput', $_POST[$key]) : []; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..37968eb --- /dev/null +++ b/index.php @@ -0,0 +1,145 @@ + + + + + PH Freiburg Bibliothek - Formulare + + + + + + + +
+
+

Willkommen

+

Bitte wählen Sie den gewünschten Service:

+
+ + + +
+
+

Hinweise

+

+ Weitere Informationen zu den Semesterapparaten und elektronischen Angeboten finden Sie auf den Seiten der Hochschulbibliothek. +

+ +
+
+
+ +
+
+ +
+
+
Erfolgreich gesendet
+
Mail wurde geschickt, Sie erhalten demnächst mehr Informationen
+
+ +
+ + + + diff --git a/license.php b/license.php new file mode 100644 index 0000000..2c8b757 --- /dev/null +++ b/license.php @@ -0,0 +1,64 @@ + + + + + Lizenzinformationen + + + + + + +
+
+

Lizenz & Copyright

+ +

Please review and adjust the project license below to match your chosen license.

+ +

Project License

+

MIT

+ +

Third-party assets

+
    +
  • Icons: @mdi/font (Material Design Icons) — license & details: https://github.com/Templarian/MaterialDesign
  • +
  • Fonts: system and web fonts used (e.g. Segoe UI, Roboto) — please verify license terms with the respective vendors/providers. + Useful links: Google Fonts +
  • +
  • Any other third-party libraries used (e.g. PHPMailer) — check their repository for license text.
  • +
+ + +
+
+ + + + diff --git a/semesterapparat.php b/semesterapparat.php new file mode 100644 index 0000000..1042863 --- /dev/null +++ b/semesterapparat.php @@ -0,0 +1,200 @@ + + + + + Semesterapparatsantrag + + + + + + + +
+
+

Semesterapparatsinformationen

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + + +
+
+ + +
+
+ +
+ + + +
+
+ +
+
+
+ +

Medien

+ + + + + + + + + + + +
AutorennameJahrAuflageTitelSignatur (wenn vorhanden)
+ + +
+ + +
+ +
+ +
+
+ + +
+
+ + + + diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..d600351 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,619 @@ +:root { + --bg: #f7f8fb; + --card-bg: #ffffff; + --text: #1f2937; + --muted: #6b7280; + --primary: #0b7bd6; /* Accessible blue */ + --primary-600: #0a6ec0; + --primary-700: #095fa6; + --border: #e5e7eb; + --ring: rgba(11, 123, 214, 0.35); + --table-head-bg: #f3f4f6; + --input-bg: #ffffff; + --control-accent: var(--primary); +} + +/* Dark theme variables */ +[data-theme="dark"] { + --bg: #0b1220; + --card-bg: #121a2a; + --text: #e5e7eb; + --muted: #9aa4b2; + --primary: #5aa1ff; + --primary-600: #4b90ea; + --primary-700: #3c7ace; + --border: #243045; + --ring: rgba(90, 161, 255, 0.35); + --table-head-bg: #172136; + --input-bg: #0f1726; + --control-accent: #2a3448; +} + +* { box-sizing: border-box; } + +html, body { height: 100%; } + +body { + margin: 0; + font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; + color: var(--text); + background: var(--bg); + line-height: 1.5; +} + +/* Header */ +.site-header { + background: var(--card-bg); + border-bottom: 1px solid var(--border); + /* Make header fixed so it remains visible on long pages */ + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1100; +} +.header-inner { + display: flex; + align-items: center; + gap: 14px; + height: 72px; + padding: 14px 0; + justify-content: space-between; +} +.header-left { + display: flex; + align-items: center; + gap: 14px; +} +.logo { height: 48px; width: auto; } +.brand-title { font-weight: 700; letter-spacing: .2px; } +.brand-sub { color: var(--muted); font-size: .95rem; } + +.theme-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + scale: 2; + border-radius: 9999px; + border: none; + background: var(--card-bg); + color: var(--text); + cursor: pointer; +} +.theme-toggle:hover { filter: brightness(1.05); } +.theme-toggle:focus-visible { outline: none; box-shadow: 0 0 0 4px var(--ring); } + +/* Layout */ +.container { + /* max-width: 1250px; */ + margin: 0 auto; + padding: 0 20px; +} + +/* Reserve space for fixed header so content doesn't jump under it */ +main.container { + padding-top: 92px; + padding-bottom: 20px; +} + +.card { + background: var(--card-bg); + margin: 24px 0; + padding: 22px; + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: 0 8px 30px rgba(0,0,0,.05); +} + +h1 { + margin: 0 0 14px; + font-size: 1.5rem; +} + +h2 { + margin: 20px 0 10px; + font-size: 1.25rem; +} + +h3 { + margin: 14px 0 10px; + font-size: 1.1rem; +} + +/* Tables */ +.table-wrapper { overflow-x: auto; } + +.form-table, +.data-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: var(--card-bg); + border: none; + border-radius: 12px; + overflow: hidden; + position: relative; +} + +.form-table::after, +.data-table::after { + content: ""; + position: absolute; + inset: 0; + border: 1px solid var(--border); + border-radius: 12px; + pointer-events: none; +} + +.form-table td, +.form-table th { + padding: 10px 12px; + border-bottom: 1px solid var(--border); +} + +.data-table td, +.data-table th { + padding: 10px 12px; + border-bottom: 0; +} + +.form-table tr:last-child td, +.data-table tr:last-child td { border-bottom: 0; } + +.data-table th { + background: var(--table-head-bg); + text-align: left; + font-weight: 600; +} + +/* Inputs */ +input[type="text"], +input[type="email"], +input[type="number"], +input[type="tel"], +input[type="date"], +select, +textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--input-bg); + color: var(--text); + outline: none; + transition: border-color .2s ease, box-shadow .2s ease; + font-family: inherit; +} + +input:focus, +select:focus, +textarea:focus { + border-color: var(--primary); + box-shadow: 0 0 0 4px var(--ring); +} + +input[type="radio"], +input[type="checkbox"] { + accent-color: var(--control-accent); +} + +.radio { margin-right: 12px; } + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 16px; + border-radius: 10px; + border: 1px solid transparent; + cursor: pointer; + font-weight: 600; + transition: background-color .2s ease, color .2s ease, border-color .2s ease, transform .02s ease; +} + +.btn:active { transform: translateY(1px); } + +.btn-primary { + background: var(--primary); + color: #fff; +} +.btn-primary:hover { background: var(--primary-600); } +.btn-primary:focus { box-shadow: 0 0 0 4px var(--ring); } + +.btn-secondary { + background: #e7eef8; + color: var(--primary-700); + border-color: #cfd9ea; +} +.btn-secondary:hover { background: #dfe8f6; } +.btn-secondary:disabled { + opacity: 0.5; + cursor: not-allowed; + background: #e7eef8; + color: var(--muted); +} +.btn-secondary:disabled:hover { + background: #e7eef8; +} + +[data-theme="dark"] .btn-secondary { + background: #1c2637; + color: var(--text); + border-color: #2a3852; +} +[data-theme="dark"] .btn-secondary:hover { background: #1f2b3f; } +[data-theme="dark"] .btn-secondary:disabled { + opacity: 0.5; + background: #1c2637; + color: var(--muted); +} +[data-theme="dark"] .btn-secondary:disabled:hover { + background: #1c2637; +} + +.actions { + margin-top: 16px; + display: flex; + justify-content: flex-end; +} + +.footer { + margin-top: 18px; + color: var(--muted); + margin-bottom: 15px; + padding-bottom: 15px; +} +.footer-note { + margin: 5px; +} +.footer-note a { color: var(--primary-700); text-decoration: none; } +.footer-note a:hover { text-decoration: underline; } + +/* Static Information grid */ +.grid-form { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px 16px; + margin-bottom: 16px; +} +.form-field { + display: flex; + flex-direction: column; + gap: 6px; +} +.form-field label { + font-size: 0.9rem; + color: var(--muted); +} + +.field-info { + margin-top: 4px; + font-size: 0.85rem; + color: var(--primary-700); +} +[data-theme="dark"] .field-info { color: #a9c8ff; } +.hidden { display: none !important; } + +.inline-controls { + display: flex; + align-items: center; + gap: 12px; + justify-content: center; + text-align: center; +} +.inline-controls.start { + justify-content: flex-start; + text-align: left; +} +.inline-controls input[type="number"] { + width: 120px; + text-align: center; +} + +/* Landing page styles */ +.landing-hero { + text-align: center; + margin: 40px 0 30px; +} +.landing-hero h1 { + font-size: 2.5rem; + margin-bottom: 10px; +} +.hero-subtitle { + font-size: 1.1rem; + color: var(--muted); +} + +.service-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin: 30px 0; +} + +.service-card { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 14px; + padding: 24px; + text-decoration: none; + color: var(--text); + transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; + display: flex; + flex-direction: column; + position: relative; + align-items: center; +} + +.service-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 40px rgba(0,0,0,.1); + border: 2px solid var(--primary); +} + +.service-icon { + font-size: 3rem; + color: var(--primary); + margin-bottom: 12px; +} + +.service-card h2 { + margin: 0 0 8px; + font-size: 1.4rem; +} + +.service-card p { + color: var(--muted); + margin: 0; + flex-grow: 1; +} + +.service-arrow { + position: absolute; + bottom: 20px; + right: 20px; + font-size: 1.5rem; + color: var(--primary); + opacity: 0; + transition: opacity .2s ease, transform .2s ease; +} + +.service-card:hover .service-arrow { + opacity: 1; + transform: translateX(4px); +} + +.info-section { + margin-top: 40px; +} + +.info-links { + display: flex; + gap: 20px; + margin-top: 12px; + flex-wrap: wrap; +} + +.info-links a { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--primary); + text-decoration: none; + font-weight: 500; +} + +.info-links a:hover { + text-decoration: underline; +} + +/* Toast notification */ +.toast { + position: fixed; + top: 92px; + right: 20px; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + box-shadow: 0 8px 30px rgba(0,0,0,.15); + display: flex; + align-items: center; + gap: 12px; + max-width: 400px; + z-index: 1000; + opacity: 0; + transform: translateY(-20px); + transition: opacity .3s ease, transform .3s ease; +} + +.toast.show { + opacity: 1; + transform: translateY(0); +} + +.toast-icon { + font-size: 1.5rem; + color: #10b981; +} + +.toast-content { + flex-grow: 1; +} + +.toast-title { + font-weight: 600; + margin-bottom: 2px; +} + +.toast-message { + font-size: 0.9rem; + color: var(--muted); +} + +.toast-close { + background: none; + border: none; + color: var(--muted); + cursor: pointer; + font-size: 1.2rem; + padding: 4px; + line-height: 1; +} + +.toast-close:hover { + color: var(--text); +} + +/* Responsive */ +@media (max-width: 1024px) { + .grid-form { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +@media (max-width: 720px) { + .header-inner { padding: 12px 0; height: auto; } + .logo { height: 40px; } + .form-table td:first-child { width: 40%; } + .landing-hero h1 { font-size: 2rem; } +} + +@media (max-width: 640px) { + .grid-form { grid-template-columns: 1fr; } + .inline-controls { flex-wrap: wrap; } + .toast { + left: 10px; + right: 10px; + max-width: none; + } +} + +.data-table + .btn { margin-top: 14px; } + +/* ELSA-specific styles */ +.legal-notice { + background: rgba(11, 123, 214, 0.1); + border-left: 4px solid var(--primary); + border-radius: 12px; + padding: 20px; + margin: 20px 0; + border-radius: 8px; +} + +.legal-notice h3 { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 10px; + color: var(--primary); +} + +.legal-notice p { + margin: 0; + line-height: 1.6; +} + +[data-theme="dark"] .legal-notice { + background: rgba(90, 161, 255, 0.15); + border-left-color: var(--primary); +} + +.media-controls { + display: flex; + gap: 12px; + margin: 20px 0; + flex-wrap: wrap; +} + +.media-section { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 20px; + margin: 20px 0; +} + +.media-section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.media-section-header h3 { + display: flex; + align-items: center; + gap: 10px; + margin: 0; + font-size: 1.2rem; +} + +.media-table { + margin-bottom: 12px; +} + +.media-table input[type="text"], +.media-table input[type="number"] { + min-width: 80px; +} + +.btn-icon { + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 6px; + border-radius: 6px; + transition: background-color .2s ease, color .2s ease; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; +} + +.btn-icon:hover { + background: rgba(0,0,0,.05); + color: var(--text); +} + +[data-theme="dark"] .btn-icon:hover { + background: rgba(255,255,255,.05); +} + +.btn-sm { + padding: 8px 12px; + font-size: 0.9rem; +} + +/* Signature validation states */ +.signature-validating { + border-color: #f59e0b !important; + background-image: linear-gradient(45deg, rgba(245, 158, 11, 0.1) 25%, transparent 25%, transparent 50%, rgba(245, 158, 11, 0.1) 50%, rgba(245, 158, 11, 0.1) 75%, transparent 75%, transparent); + background-size: 20px 20px; + animation: signature-loading 1s linear infinite; +} + +@keyframes signature-loading { + 0% { background-position: 0 0; } + 100% { background-position: 20px 20px; } +} + +.signature-valid { + border-color: #10b981 !important; + background-color: rgba(16, 185, 129, 0.05); +} + +.signature-invalid { + border-color: #ef4444 !important; + background-color: rgba(239, 68, 68, 0.05); +} + +.threshold-exceeded { + background-color: rgba(239, 68, 68, 0.08); +} + +[data-theme="dark"] .threshold-exceeded { + background-color: rgba(239, 68, 68, 0.12); +} + diff --git a/submit_elsa.php b/submit_elsa.php new file mode 100644 index 0000000..0cb1b58 --- /dev/null +++ b/submit_elsa.php @@ -0,0 +1,139 @@ + $authors[$i] ?? '', + 'year' => $years[$i] ?? '', + 'edition' => $editions[$i] ?? '', + 'title' => $titles[$i] ?? '', + 'signature' => $signatures[$i] ?? '', + 'pages_from' => $pagesFrom[$i] ?? '', + 'pages_to' => $pagesTo[$i] ?? '' + ]; + } +} + +// Process Zeitschriftenartikel entries +if (isset($_POST['zeitschrift_author']) && is_array($_POST['zeitschrift_author'])) { + $authors = postArray('zeitschrift_author'); + $years = postArray('zeitschrift_year'); + $volumes = postArray('zeitschrift_volume'); + $articleTitles = postArray('zeitschrift_article_title'); + $journalTitles = postArray('zeitschrift_journal_title'); + $signatures = postArray('zeitschrift_signature'); + $pagesFrom = postArray('zeitschrift_pages_from'); + $pagesTo = postArray('zeitschrift_pages_to'); + + for ($i = 0; $i < count($authors); $i++) { + $zeitschriftenartikel[] = [ + 'author' => $authors[$i] ?? '', + 'year' => $years[$i] ?? '', + 'volume' => $volumes[$i] ?? '', + 'article_title' => $articleTitles[$i] ?? '', + 'journal_title' => $journalTitles[$i] ?? '', + 'signature' => $signatures[$i] ?? '', + 'pages_from' => $pagesFrom[$i] ?? '', + 'pages_to' => $pagesTo[$i] ?? '' + ]; + } +} + +// Process Herausgeberwerk entries +if (isset($_POST['herausgeber_publisher']) && is_array($_POST['herausgeber_publisher'])) { + $publishers = postArray('herausgeber_publisher'); + $workTitles = postArray('herausgeber_work_title'); + $years = postArray('herausgeber_year'); + $editions = postArray('herausgeber_edition'); + $articleAuthors = postArray('herausgeber_article_author'); + $articleTitles = postArray('herausgeber_article_title'); + $signatures = postArray('herausgeber_signature'); + $pagesFrom = postArray('herausgeber_pages_from'); + $pagesTo = postArray('herausgeber_pages_to'); + + for ($i = 0; $i < count($publishers); $i++) { + $herausgeberwerke[] = [ + 'publisher' => $publishers[$i] ?? '', + 'work_title' => $workTitles[$i] ?? '', + 'year' => $years[$i] ?? '', + 'edition' => $editions[$i] ?? '', + 'article_author' => $articleAuthors[$i] ?? '', + 'article_title' => $articleTitles[$i] ?? '', + 'signature' => $signatures[$i] ?? '', + 'pages_from' => $pagesFrom[$i] ?? '', + 'pages_to' => $pagesTo[$i] ?? '' + ]; + } +} + +// Prepare data for XML generation +$data = [ + 'name' => $name, + 'lastname' => $lastname, + 'title' => $title, + 'mail' => $mail, + 'subject' => $subject, + 'classname' => $classname, + 'usage_date_from' => $usage_date_from, + 'usage_date_to' => $usage_date_to, + 'availability_date' => $availability_date, + 'message' => $message, + 'monografien' => $monografien, + 'zeitschriftenartikel' => $zeitschriftenartikel, + 'herausgeberwerke' => $herausgeberwerke +]; + +// Generate XML +$xml = generateELSAXML($data); + +// Send email +$emailSent = sendEmail('New ELSA Form Submission', $xml); + +if ($emailSent || !MAIL_ENABLED) { + redirect('index.php?success=true'); +} else { + die('Fehler: E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.'); +} diff --git a/submit_semesterapparat.php b/submit_semesterapparat.php new file mode 100644 index 0000000..e70c81e --- /dev/null +++ b/submit_semesterapparat.php @@ -0,0 +1,84 @@ + $authornames[$i] ?? '', + 'year' => $years[$i] ?? '', + 'edition' => $edition[$i] ?? '', + 'title' => $booktitles[$i] ?? '', + 'signature' => $signatures[$i] ?? '' + ]; +} + +// Prepare data for XML generation +$data = [ + 'name' => $name, + 'lastname' => $lastname, + 'title' => $title, + 'telno' => $telno, + 'mail' => $mail, + 'apparatsname' => $apparatsname, + 'subject' => $subject, + 'semester' => $semester_type . ' ' . $semester_year, + 'dauerapparat' => $dauerapparat, + 'message' => $message, + 'books' => $books +]; + +// Generate XML +$xml = generateXML($data); + +// Send email +$emailSent = sendEmail('Antrag für neuen Semesterapparat', $xml); + +if ($emailSent || !MAIL_ENABLED) { + redirect('index.php?success=true'); +} else { + die('Fehler: E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.'); +} diff --git a/test_email.php b/test_email.php new file mode 100644 index 0000000..4b48885 --- /dev/null +++ b/test_email.php @@ -0,0 +1,56 @@ + + + This is a test email from SemapForm PHP + %s + +XML; + +$testXML = sprintf($testXML, date('Y-m-d H:i:s')); + +$subject = "SemapForm Test Email - " . date('Y-m-d H:i:s'); + +echo "Attempting to send test email...\n"; +echo "Subject: {$subject}\n\n"; + +$result = sendEmail($subject, $testXML); + +if ($result) { + echo "\n✓ Email sent successfully!\n"; + echo "Check the recipient inbox and mail.log for details.\n"; +} else { + echo "\n✗ Email sending failed!\n"; + echo "Check mail.log for error details.\n"; +} + +echo "\n=== Log File Contents ===\n\n"; +if (file_exists(LOG_FILE)) { + echo "Last 20 lines of mail.log:\n"; + echo "----------------------------------------\n"; + $lines = file(LOG_FILE); + $lastLines = array_slice($lines, -20); + echo implode('', $lastLines); +} else { + echo "Log file not found at: " . LOG_FILE . "\n"; +}