commit d9448a673c81772be9adc99d3d41b2fdfd435d4c Author: WorldTeacher Date: Tue Dec 9 10:07:14 2025 +0100 initial release 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"; +}