# product-updates-admin Full Instructions

## Admin Panel Patterns

### Authentication

**Requirements:**

- Password-based authentication using PHP sessions
- Session timeout: 30 minutes of inactivity
- "Remember Me" functionality: 30 days
- Login attempt limiting: 5 attempts, then 15-minute lockout
- Session regeneration on successful login

**Implementation:**

- Store password hash in `v2/config/admin_config.php`
- Use `password_verify()` for authentication
- Never expose password in error messages or logs

### Feature Modal – Tabbed UI

**Tab Structure:**

- **Tab 1 – Inhalt (Content):** Title, Description (Quill), Page Content (Quill), Tag, Read More Link
- **Tab 2 – Technisch & SEO:** SEO Title, SEO Description, Generate SEO button, Featured Image, Featured Image Alt, Published Date, Platforms, In-App Banner

**SEO Fields (Technical tab):**

- **SEO Title:** Max 41 chars (user part); suffix `" - Ordio Produkt-Updates"` appended in `post.php`. Char counter `X/41`
- **SEO Description:** Max 155 chars. Char counter `X/155`
- **Featured Image Alt:** Max 125 chars. Char counter `X/125`. Empty = title used
- **Generate SEO:** Button calls `/v2/api/produkt-updates-seo-generate.php` (Gemini primary, OpenAI fallback). Fills SEO Title and SEO Description from title, description, page content

**Character Limits:** Enforced in `produkt-updates-sanitize.php`; truncate with ellipsis when over limit

**In-App Banner Flag:**

- Checkbox "In-App-Banner anzeigen (Web-App)" in Feature and Improvement add/edit modals
- Stored as `show_in_app_banner` (boolean) in JSON
- When true, item appears in feed and can be displayed as update banner in Ordio
- **Bulk update**: "Update In-App Banner" in bulk actions bar – select items, choose enable/disable for all

**Delete Flow:**

1. Show confirmation modal with item details
2. Remove item from JSON and save
3. **Do not** unlink image files from disk when deleting a feature/improvement — files remain in the library for reuse until the user deletes them via **image library** delete (`produkt-updates-upload.php` action delete + `cleanupImageFromJson`)
4. Show success notification

### Image Management

**Upload Requirements:**

- Maximum file size: 5MB
- Supported formats: JPEG, PNG, WebP, GIF
- Automatic optimization: resize to max 1920x1920px
- Compression: JPEG quality 85, PNG compression level 6
- Unique filename: `uniqid('img_', true)` + extension

**Security:**

- Validate MIME type using `finfo_file()`
- Check file extension whitelist
- **Filesystem:** `findWritableImageDir()` writes under **`wp-content/uploads/produkt-updates/`**. **Canonical URLs in JSON and UI** use **`/produkt-updates/bilder/{filename}`** (routed by `.htaccess` to `produkt-updates-bilder.php`; avoids collision with `/produkt-updates/{post-slug}`). Retired `/produkt-updates/media/` 301s to `/bilder/`.
- Path traversal prevention in delete operations; canonicalize refs with `canonicalizeProduktUpdatesImageRef()` where paths are compared or stored
- **Filesystem deletes:** Only the **image library** delete action may `unlink` product-update images. Replacing or removing a featured image, or deleting a feature/improvement post, updates JSON only — files stay on disk for reuse. **`produktUpdatesCountBasenameReferencesInData()`** is for “In Use” / tooling, not for auto-deletion.

**Image Library Loading:**

- Uses `/v2/api/produkt-updates-diagnostics.php` endpoint
- Expects `existing_images` key in response (deduplicated by basename, WebP preferred)
- Scans all directories from `getAllImageDirs()`
- Handles empty directories gracefully
- Provides retry functionality on errors

**Troubleshooting Image Loading:**

When images fail to load in admin panel:

1. **Check Diagnostics Endpoint**:
   ```bash
   curl "http://localhost:8003/v2/api/produkt-updates-diagnostics.php?t=$(date +%s)" | jq '.existing_images'
   ```
   Verify `existing_images` key exists and has correct structure

2. **Run Diagnostic Scripts**:
   - `/v2/admin/produkt-updates/debug-image-loading.php?password=debug2024`
   - `/v2/admin/produkt-updates/scan-image-directories.php?password=debug2024`
   - `/v2/admin/produkt-updates/test-image-api.php?password=debug2024`

3. **Check Browser Console**:
   - Look for network errors (404, 500, CORS)
   - Check JSON parsing errors
   - Review `adminDebugLog()` messages

4. **Verify Directory Permissions**:
   - Directories should be readable (755)
   - Files should be readable (644)
   - Check owner is web server user

5. **Common Issues**:
   - **Missing `existing_images` key**: Check PHP error logs, verify `listExistingImages()` is called
   - **Empty arrays**: Check directory permissions, verify images exist
   - **Path resolution mismatch**: Fixed in diagnostics endpoint - location keys use proper relative paths
   - **Network errors**: Check server status, verify CORS headers, check rate limits

**Error Handling Best Practices:**

- Always validate response structure before processing
- Provide user-friendly error messages with retry button
- Log errors to console for debugging (`adminDebugLog()`)
- Handle network errors separately from JSON errors
- Wrap directory scanning in try-catch blocks
- Check directory readability before scanning
- Return empty arrays instead of errors when possible

### Bulk Operations

**Supported Actions:**

- Bulk delete (features or improvements)
- Bulk update platforms (desktop/mobile)
- Bulk update published date

**Implementation:**

- Select items via checkboxes in table header and rows
- Show bulk action bar when items selected
- Confirm destructive operations (delete)
- Provide undo option when possible

### Search and Filter

**Features:**

- Real-time search as user types
- Search in title and description fields
- Case-insensitive matching
- Display filtered count
- Clear button to reset search

## Security Requirements

### Input Validation

**All User Input:**

```php
// Plain text fields
$sanitized = htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');

// HTML content
function sanitizeHtmlInput($html) {
    $allowed_tags = '<p><br><strong><em><b><i><u><ul><ol><li><a><h1><h2><h3><blockquote><img><iframe><video><source>';
    $html = strip_tags($html, $allowed_tags);
    // Sanitize img, iframe, video via sanitizeProduktUpdatesMediaTags() (helper)
    $html = sanitizeProduktUpdatesMediaTags($html);
    // Remove dangerous attributes
    $html = preg_replace('/\s*on\w+\s*=\s*["\'][^"\']*["\']/i', '', $html);
    $html = preg_replace('/\s*style\s*=\s*["\'][^"\']*["\']/i', '', $html);
    $html = preg_replace('/\s*javascript:/i', '', $html);
    return trim($html);
}
```

### File Upload Security

**Validation:**

- Check MIME type with `finfo_file()`
- Validate file extension against whitelist
- Enforce file size limits
- Generate unique filenames (no user input)

**Path Security:**

- Use absolute paths for file operations
- Validate paths with `realpath()` before delete
- Ensure paths stay within upload directory

### Session Security

**Best Practices:**

- Regenerate session ID on login
- Set secure session timeout
- Clear sensitive data on logout
- Use HTTP-only cookies when possible

## Production Best Practices

### Atomic Write Operations

**REQUIRED for all file write operations:**

All JSON file writes MUST use atomic write pattern to prevent data corruption:

```php
// CORRECT: Atomic write pattern
$temp_file = $file . '.tmp';
$write_result = file_put_contents($temp_file, $json_data, LOCK_EX);
if ($write_result === false) {
    // Handle error
    return false;
}

// Verify temp file integrity
$temp_content = file_get_contents($temp_file);
if ($temp_content !== $json_data) {
    unlink($temp_file);
    return false;
}

// Atomic rename (critical operation)
if (!rename($temp_file, $file)) {
    unlink($temp_file);
    return false;
}

// Verify final file
if (!file_exists($file) || !is_readable($file)) {
    return false;
}
```

**Why:** Prevents partial writes, data corruption, and race conditions.

### File Locking

**REQUIRED for concurrent operations:**

Use `flock()` for operations that modify shared resources:

```php
$lock_file = __DIR__ . '/../../data/.migration.lock';
$lock_handle = fopen($lock_file, 'c');
if (!$lock_handle) {
    return false;
}

if (!flock($lock_handle, LOCK_EX | LOCK_NB)) {
    fclose($lock_handle);
    return false; // Operation already in progress
}

try {
    // Perform operation
} finally {
    flock($lock_handle, LOCK_UN);
    fclose($lock_handle);
}
```

**When to use:**

- Migration scripts
- Bulk operations
- Any operation that modifies JSON file

### CSRF Protection

**REQUIRED for all POST operations:**

All destructive POST operations MUST include CSRF protection:

```php
require_once __DIR__ . '/../../helpers/csrf-protection.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!checkCSRFProtection(true)) {
        outputCSRFError();
    }
}

// Include token in response for future requests
$response['csrf_token'] = generateCSRFToken();
```

**Implementation:**

- Generate token: `generateCSRFToken()`
- Validate token: `checkCSRFProtection($require_post)`
- Error response: `outputCSRFError()`

### Rate Limiting

**REQUIRED for public endpoints:**

All diagnostic and migration endpoints MUST implement rate limiting:

```php
require_once __DIR__ . '/../../helpers/rate-limiter.php';

// For diagnostic endpoints
if (!checkDiagnosticsRateLimit()) {
    exit; // Rate limiter already sent response
}

// For migration scripts
if (!checkMigrationRateLimit()) {
    exit; // Rate limiter already sent response
}
```

**Limits:**

- Diagnostic endpoints: 10 requests per minute per IP
- Migration scripts: 1 request per 5 minutes per session

### Error Handling

**REQUIRED: No error suppression:**

NEVER use `@` error suppression operator. Always handle errors properly:

```php
// WRONG:
@file_get_contents($file);
@mkdir($dir, 0755, true);

// CORRECT:
$content = file_get_contents($file);
if ($content === false) {
    $error = error_get_last();
    storage_log_error('Failed to read file', [
        'file' => $file,
        'error' => $error['message'] ?? 'Unknown error'
    ]);
    return false;
}

if (!is_dir($dir)) {
    if (!mkdir($dir, 0755, true)) {
        $error = error_get_last();
        storage_log_error('Failed to create directory', [
            'directory' => $dir,
            'error' => $error['message'] ?? 'Unknown error'
        ]);
        return false;
    }
}
```

### Input Validation

**REQUIRED for all user input:**

Validate and sanitize all GET/POST parameters:

```php
// Validate action parameter
$action = $_GET['action'] ?? $_POST['action'] ?? 'check';
$allowed_actions = ['check', 'migrate', 'rollback'];
if (!in_array($action, $allowed_actions)) {
    http_response_code(400);
    echo json_encode(['success' => false, 'message' => 'Invalid action']);
    exit;
}

// Validate file paths (prevent path traversal)
$backup_file = basename($backup_file_param); // Remove any paths
if (!preg_match('/^produkt_updates_\d{4}-\d{2}-\d{2}_\d{6}\.json$/', $backup_file)) {
    // Invalid format
    return false;
}

// Validate boolean parameters
$cleanup = filter_var($cleanup_param, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($cleanup === null) {
    $cleanup = false; // Default value
}
```

### Logging

**REQUIRED: Use centralized logger:**

Always use storage logger for storage-related operations:

```php
require_once __DIR__ . '/../../helpers/storage-logger.php';

storage_log_info('Operation started', ['context' => 'value']);
storage_log_warning('Warning message', ['context' => 'value']);
storage_log_error('Error occurred', ['context' => 'value']);
storage_log_critical('Critical issue', ['context' => 'value']);
```

**Log file:** `v2/logs/produkt-updates-storage.log`
**Rotation:** Automatic daily rotation, keeps 30 days

## Error Handling

### Logging

**Pattern:**

```php
function logError($message, $context = [], $level = 'error') {
    $log_dir = __DIR__ . '/../logs/';
    $log_file = $log_dir . 'produkt_updates_admin_' . date('Y-m-d') . '.log';

    $log_entry = date('Y-m-d H:i:s') . " [{$level}] {$message}";
    if (!empty($context)) {
        $log_entry .= " | Context: " . json_encode($context);
    }

    error_log($log_entry . PHP_EOL, 3, $log_file);
}
```

### User-Friendly Messages

**Never expose:**

- File paths
- Database structure
- Stack traces
- Sensitive configuration

**Always show:**

- Clear, actionable error messages
- Suggestions for resolution
- Contact information for support

## Performance Requirements

### Image Optimization

**Automatic Processing:**

- Resize to max 1920x1920px
- Apply compression (JPEG: 85 quality, PNG: level 6)
- Convert to WebP when possible
- Report file size savings

### Caching

**Static Assets:**

- Images: 1 year cache
- JSON data: no cache (always fresh)

### Database Operations

**Efficiency:**

- Single JSON file load per request
- Atomic writes (temp file + rename)
- Backup before bulk operations

## Testing Requirements

### Before Deployment

**Code Quality:**

- ✓ No console.log/error/warn statements
- ✓ No var_dump/print_r statements
- ✓ All user input sanitized
- ✓ No linter errors

**Functionality:**

- ✓ Authentication (login, lockout, timeout, remember me)
- ✓ CRUD operations (create, read, update, delete)
- ✓ Image upload and optimization
- ✓ Search and filter
- ✓ Bulk operations
- ✓ Public pages render correctly
- ✓ SEO elements valid (schema, meta tags, sitemap, RSS)

**Browser Testing:**

- ✓ Chrome (desktop and mobile)
- ✓ Firefox (desktop)
- ✓ Safari (desktop and mobile, if available)

**Security:**

- ✓ XSS prevention
- ✓ File upload security
- ✓ Session management
- ✓ Input validation

### Ongoing Monitoring

**Daily:**

- Check error logs: `v2/logs/produkt_updates_admin_*.log`
- Review failed operations
- Monitor disk space for images

**Weekly:**

- Validate JSON structure
- Check for orphaned images
- Review performance metrics

## Keyboard Shortcuts

**Implemented:**

- `Esc`: Close modal/dialog
- `Delete`: Delete selected items (when items selected)

**Not Implemented:**

- ~~`Ctrl+S`: Save current form~~ (not implemented)
- ~~`Ctrl+N`: Create new item~~ (not implemented)

## Unimplemented Features

The following features were documented but **never implemented**:

- ❌ **Drag-and-drop reordering**: No SortableJS integration
- ❌ **Template system**: No save/load template functionality
- ❌ **Auto-save & Drafts**: No localStorage-based draft saving

**If requested in the future:**

- Document requirements clearly
- Update user guide and code documentation
- Test thoroughly before documenting as available
# product-updates-core Full Instructions

## Overview

The Product Updates system allows marketing and customer success teams to manage product feature announcements and improvements through a custom CMS. This document defines patterns, best practices, and requirements for the Product Updates system.

## File Structure

```
v2/
├── base/
│   └── produkt_updates_carousel.php    # Related posts carousel on individual feature pages
├── pages/
│   ├── produkt_updates_admin.php       # Admin CMS interface
│   ├── produkt_updates.php             # Main public listing page
│   ├── produkt_updates_month.php       # Monthly archive page
│   ├── post.php                        # Individual post/feature page
│   ├── produkt-updates-feed.xml.php    # RSS feed
│   ├── produkt-updates-feed.json.php   # JSON Feed (for Ordio in-app banners)
│   └── sitemap-produkt-updates.xml.php # XML sitemap
├── data/
│   ├── produkt_updates.json            # JSON data store (FALLBACK - if writable)
│   ├── images/                         # Persistent image storage (FALLBACK)
│   │   └── produkt-updates/            # Uploaded images directory (FALLBACK)
│   ├── backups/                        # Backup files
│   ├── rate-limit-storage.json         # Rate limiting data
│   └── storage-metrics.json            # Storage operation metrics
wp-content/
└── uploads/
    └── produkt-updates/                # PRIMARY: Persistent storage (data + images)
        ├── produkt_updates.json        # JSON data store (PRIMARY)
        └── [uploaded-images]/          # Uploaded images directory (PRIMARY)
├── api/
│   ├── produkt-updates-upload.php      # Image upload handler
│   ├── produkt-updates-seo-generate.php # AI SEO meta generation (Gemini/OpenAI)
│   ├── produkt-updates-diagnostics.php # Diagnostics endpoint
│   ├── produkt-updates-server-investigation.php # Comprehensive server diagnostics
│   ├── produkt-updates-data-location-check.php # Data file location comparison
│   ├── produkt-updates-image-investigation.php # Image storage investigation
│   ├── produkt-updates-storage-health.php # Quick storage health check
│   └── serve-produkt-updates-image.php # Image proxy (fallback; redirects to direct when in wp-content)
├── admin/produkt-updates/
│   ├── USER_GUIDE.md                   # User documentation
│   ├── CODE_DOCUMENTATION.md           # Developer documentation
│   ├── migrate-to-persistent-storage.php # Data migration script
│   ├── migrate-images-to-persistent.php # Image migration script
│   ├── verify-migration.php            # Migration verification script
│   ├── init-storage.php                # Storage initialization script
│   ├── test-live-endpoints.php         # Endpoint testing tool
│   ├── analyze-server-config.php       # Server configuration analysis
│   └── security-audit.php              # Security audit tool
├── helpers/
│   ├── storage-logger.php              # Centralized storage logging
│   ├── rate-limiter.php                # Rate limiting helper
│   ├── csrf-protection.php             # CSRF protection helper
│   └── produkt-updates-carousel-items.php  # Related carousel rows for post.php
├── config/
│   └── storage-diagnostics-auth.php    # Diagnostic endpoint authentication config
├── includes/
│   └── produkt-updates-paths.php      # Path resolution functions
├── logs/
│   └── produkt-updates-storage.log     # Storage operation logs
├── temp/produkt-updates/               # Secondary image storage (gitignored)
└── cache/produkt-updates/              # Tertiary image storage (gitignored)
```

## Data Structure

### JSON Schema (`produkt_updates.json`)

```json
{
  "current_month": "month-slug",
  "months": {
    "month-slug": {
      "month": "Month Name YYYY",
      "slug": "month-slug",
      "published_date": "YYYY-MM-DD",
      "intro_text": "Brief description",
      "page_title": "Optional *blue* markup",
      "big_features": [
        {
          "id": "unique-id",
          "number": 1,
          "title": "Feature Title",
          "description": "<p>HTML content</p>",
          "page_content": "<p>Full HTML content</p>",
          "read_more_link": "/produkt-updates/feature-slug",
          "published_date": "YYYY-MM-DD",
          "tag": "neues-feature|verbesserung|bugfix",
          "platforms": ["desktop", "mobile"],
          "featured_image": "/produkt-updates/bilder/image.webp",
          "show_in_app_banner": false,
          "seo_title": "Optional SEO title (max 41 chars)",
          "meta_description": "Optional meta description (max 155 chars)",
          "featured_image_alt": "Optional alt text for featured image (max 125 chars)"
        }
      ],
      "small_improvements": [
        {
          "id": "unique-id",
          "number": 1,
          "title": "Improvement Title",
          "description": "<p>HTML content</p>",
          "published_date": "YYYY-MM-DD",
          "tag": "bugfix|verbesserung",
          "platforms": ["desktop", "mobile"],
          "featured_image": "/produkt-updates/bilder/improvement.webp",
          "show_in_app_banner": false
        }
      ]
    }
  },
  "last_updated": "ISO 8601 timestamp",
  "settings": {
    "max_big_features": 5,
    "max_small_improvements": 10,
    "main_page_title": "",
    "main_page_intro_text": ""
  }
}
```

### Feed URLs and Extensions

- **RSS Feed**: `/produkt-updates/feed.xml` – RSS 2.0 with `ordio:type` and `ordio:show_in_app_banner` namespace
- **JSON Feed**: `/produkt-updates/feed.json` – JSON Feed 1.1 with `_ordio` extension
  - Query param `?in_app_banner_only=1`: Return only items with `show_in_app_banner=true`
  - Each item has `_ordio`: `{ type: "big_feature"|"small_improvement", show_in_app_banner: bool, platforms: [] }`
- **show_in_app_banner**: Boolean flag (default `false`) – when true, item is intended for in-app update banner in Ordio

## Common Patterns

### Adding a New Month

```php
// 1. Generate slug from month name
$slug = strtolower(preg_replace('/[^a-z0-9]+/', '-', $month_name));

// 2. Create month structure
$month_data = [
    'month' => $month_name,
    'slug' => $slug,
    'published_date' => date('Y-m-d'),
    'intro_text' => '',
    'page_title' => '',
    'big_features' => [],
    'small_improvements' => []
];

// 3. Add to months array
$data['months'][$slug] = $month_data;

// 4. Update current_month if most recent
if (strtotime($month_data['published_date']) > strtotime($data['months'][$data['current_month']]['published_date'])) {
    $data['current_month'] = $slug;
}
```

### Adding a Feature

```php
// 1. Generate unique ID
$id = uniqid();

// 2. Sanitize input
$title = sanitizeInput($_POST['title']);
$description = sanitizeHtmlInput($_POST['description']);
$page_content = sanitizeHtmlInput($_POST['page_content']);

// 3. Create feature structure
$feature = [
    'id' => $id,
    'number' => count($month['big_features']) + 1,
    'title' => $title,
    'description' => $description,
    'page_content' => $page_content,
    'read_more_link' => '/produkt-updates/' . $slug,
    'published_date' => $_POST['published_date'],
    'tag' => $_POST['tag'],
    'platforms' => $_POST['platforms'] ?? [],
    'featured_image' => $_POST['featured_image'] ?? '',
    'show_in_app_banner' => $_POST['show_in_app_banner'] ?? false
];

// 4. Add to month's features
$data['months'][$month_slug]['big_features'][] = $feature;
```

## Version History

- **2025-11-19**: Production Debugging System

  - Added diagnostic endpoint (`/v2/api/produkt-updates-diagnostics.php`)
  - Implemented production-safe debug panel (`Ctrl+Shift+D`)
  - Added `window.runDiagnostics()` function
  - Created automated test suite (`/v2/test/admin-dashboard-test.php`)
  - Enhanced error handling in `showSection()` and `initActivityChart()`
  - Added Chart.js CDN fallback mechanism
  - Validated all chart data arrays before JSON encoding
  - Added PHP extension checks (json, mbstring)
  - Fixed hash navigation and initial section loading
  - Replaced all console statements with `adminDebugLog()`
  - Added visual error banner for user-facing errors
  - Comprehensive DOM readiness checks

- **2025-11-19**: E2E Testing Completed

  - Comprehensive code review completed
  - Browser testing completed (Chrome)
  - Security review completed
  - SEO review completed
  - Code quality verified (no console statements, no debug code)
  - Documentation updated
  - Known issue: CSRF protection missing (recommended for production)

- **2025-11-18**: Initial production release
  - Removed unimplemented features (drag-drop, templates, auto-save)
  - Removed 58 console statements
  - Updated documentation
  - Created comprehensive testing procedures

## Related Documentation

See [docs/ai/RULE_TO_DOC_MAPPING.md](../../docs/ai/RULE_TO_DOC_MAPPING.md) for complete mapping.

**Key Documentation:**

- [docs/systems/product-updates/](../../docs/systems/product-updates/) - `docs/systems/product-updates/` - Product Updates CMS documentation
- [docs/systems/product-updates/README.md](../../docs/systems/product-updates/README.md) - `docs/systems/product-updates/README.md` - System index
# product-updates-storage Full Instructions

## Storage System Architecture

### SSOT (single source of truth)

- **Config:** `v2/config/produkt-updates-storage.php` — default root `v2/data/produkt-updates/`, override via **`ORDIO_PRODUKT_UPDATES_ROOT`**.
- **JSON:** `{root}/produkt_updates.json` — `findWritableDataFile()` and `findReadableDataFile()` return the **same path** (no split-brain).
- **Images (writes):** `{root}/images/` — canonical public URL `/produkt-updates/bilder/{file}`.
- **Legacy reads:** `findReadableImage()` still checks `wp-content/uploads/produkt-updates/` until migration copies files into SSOT.
- **Atomic JSON:** `produktUpdatesAtomicWriteJsonFile()` in `v2/includes/produkt-updates-json.php` (admin save + `cleanupImageFromJson()`).
- **Migration / audit:** `v2/admin/produkt-updates/migrate-to-ssot-storage.php`, `v2/scripts/dev-helpers/audit-produkt-updates-storage.php`, `verify-produkt-updates-ssot.php`.

**Permissions:** ensure `{root}` and `{root}/images` are owned/writable by the PHP user (e.g. `www-data`). If the default path is not writable, set `ORDIO_PRODUKT_UPDATES_ROOT` to a dedicated directory (optionally outside webroot).

**Key functions:** `produktUpdatesStorageRoot()`, `produktUpdatesDataFilePath()`, `produktUpdatesImagesDirPath()`, `findWritableImageDir()`, `findReadableImage()`, `getAllImageDirs()`, `produktUpdatesImagePathIsUnderServedRoot()`.

### Security Features

**Authentication:**

- Diagnostic endpoints: IP whitelist, token, or admin session
- Migration scripts: Admin session required
- Configuration: `v2/config/storage-diagnostics-auth.php`

**Rate Limiting:**

- Diagnostic endpoints: 10 requests/minute per IP
- Migration scripts: 1 request/5 minutes per session
- Image proxy: 100 requests/minute per IP

**CSRF Protection:**

- Required for all POST operations
- Token generation and validation
- Helper functions in `v2/helpers/csrf-protection.php`

**Input Validation:**

- All GET/POST parameters validated
- File paths sanitized (basename, realpath)
- Action parameters whitelisted
- Boolean parameters validated with filter_var

## Troubleshooting

### Diagnostic Tools

**Diagnostic Endpoint:**

Access `/v2/api/produkt-updates-diagnostics.php` to check:

- PHP version and extensions (json, mbstring, curl, gd)
- File system permissions (data file, images directory, logs)
- External resources (Chart.js CDN, Quill.js CDN)
- JSON data validity

**Debug Panel:**

Press `Ctrl+Shift+D` in the admin dashboard to toggle the debug panel:

- View real-time JavaScript logs
- Track function calls and errors
- Monitor chart initialization
- Inspect navigation events

**Diagnostics Function:**

Open browser console and run `window.runDiagnostics()` to check:

- JavaScript functions availability
- DOM elements existence
- Chart.js and Quill.js loading status
- Chart instance status and canvas state
- Recent errors and warnings

### Common Issues

**Tab Navigation Not Working:**

1. Open browser console and check for JavaScript errors
2. Press `Ctrl+Shift+D` to open debug panel
3. Run `window.runDiagnostics()` in console
4. Check if `showSection` function exists
5. Verify all `.content-section` elements exist
6. Check error banner at top of page for specific issues
7. Clear browser cache and reload

**Chart Not Loading or "Canvas is already in use" Error:**

1. Press `Ctrl+Shift+D` to check debug logs
2. Run `window.runDiagnostics()` to verify Chart.js loaded
3. Check diagnostic endpoint for Chart.js CDN availability
4. Verify chart data arrays are valid (check PHP json_encode)
5. Inspect browser console for Chart.js errors
6. Check if dashboard section is actually visible

**Note:** The admin dashboard now automatically destroys previous chart instances before creating new ones, preventing canvas reuse errors. If you still encounter this error, it indicates a deeper initialization issue - check the debug panel for details.

**JSON File Not Saving:**

- Check file permissions (must be writable)
- Verify directory exists
- Check disk space
- Review error logs: `v2/logs/produkt_updates_admin_*.log`
- Access diagnostic endpoint to verify filesystem permissions

**Images Not Uploading:**

- Check PHP `upload_max_filesize` and `post_max_size`
- Verify GD extension installed (check diagnostic endpoint)
- Check directory permissions (check diagnostic endpoint)
- Review error logs: `v2/logs/produkt_updates_admin_*.log`

**Session Expired:**

- Verify session settings in php.ini
- Check cookie settings
- Clear browser cookies
- Use "Remember Me" for longer sessions

**PHP Extension Missing:**

**JSON Extension (Critical):**

If you see "Critical PHP extension missing: json" error, the admin panel will not function:

1. Access diagnostic endpoint to verify: `/v2/api/produkt-updates-diagnostics.php`
2. Install JSON extension:
   - Ubuntu/Debian: `sudo apt-get install php-json`
   - CentOS/RHEL: `sudo yum install php-json`
3. Restart web server: `sudo service apache2 restart` or `sudo service nginx restart`
4. Verify installation via diagnostic endpoint

**mbstring Extension (Non-Critical):**

The mbstring extension is recommended but not required. If missing:

- Admin panel will continue to function using fallback string functions
- A warning will be logged to the PHP error log
- Text processing may be less accurate for non-ASCII characters
- In debug mode, a notice will be displayed in HTML comments

To install mbstring for optimal performance:

1. Ubuntu/Debian: `sudo apt-get install php-mbstring`
2. CentOS/RHEL: `sudo yum install php-mbstring`
3. Restart web server
4. Verify installation via diagnostic endpoint

**Note:** The fallback functions handle basic string operations but may not properly handle UTF-8 multi-byte characters. Installing mbstring is recommended for production environments with international content.

**Storage locations (SSOT):**

- Single JSON path: `findWritableDataFile()` === `findReadableDataFile()` → `produktUpdatesDataFilePath()` under `v2/data/produkt-updates/` by default, or `ORDIO_PRODUKT_UPDATES_ROOT`.
- Image writes: `{root}/images/`. Reads also check legacy `wp-content/uploads/produkt-updates/` until migrated.
- See `docs/systems/product-updates/PRODUCT_UPDATES_STORAGE.md` and `migrate-to-ssot-storage.php`.

**Deployment / Git (CRITICAL):**

- Do **not** overwrite **`v2/data/produkt-updates/produkt_updates.json`** or **`v2/data/produkt-updates/images/`** on deploy (gitignored live content).
- **Public image URLs** should be **`/produkt-updates/bilder/{file}`**. Legacy `/wp-content/uploads/produkt-updates/` and `/produkt-updates/media/` 301 to bilder (`.htaccess` / Nginx).
- **Deleting files from disk** only via admin image library (`produkt-updates-upload.php`); `cleanupImageFromJson()` uses the same JSON path + atomic save.

3. **Log Files** (admin logs):
   - **PRIMARY:** `v2/logs/` (persistent)
   - **FALLBACK:** `/tmp/produkt-updates-logs/` (volatile)
   - **LAST RESORT:** `v2/data/logs/` (persistent)
   - Path resolution function: `findWritableLogDir()`

**Storage Migration:**

If system is using `/tmp/` storage (volatile), migration is REQUIRED:

- **Data Migration:** `/v2/admin/produkt-updates/migrate-to-persistent-storage.php?action=migrate`
- **Image Migration:** `/v2/admin/produkt-updates/migrate-images-to-persistent.php?action=migrate`
- **Full Guide:** See `docs/systems/product-updates/PRODUCT_UPDATES_STORAGE_MIGRATION.md`

**Diagnostic Endpoints:**

- **Server Investigation:** `/v2/api/produkt-updates-server-investigation.php` (comprehensive diagnostics)
- **Data Location Check:** `/v2/api/produkt-updates-data-location-check.php` (writable vs readable comparison)
- **Image Investigation:** `/v2/api/produkt-updates-image-investigation.php` (image storage analysis)
- **Storage Health:** `/v2/api/produkt-updates-storage-health.php` (quick health check)
- **Diagnostics:** `/v2/api/produkt-updates-diagnostics.php` (general diagnostics)

**Admin Panel Storage Status:**

Admin panel Settings section includes Storage Status card showing:

- Current data file location
- Current image directory location
- Warnings if using volatile storage
- Migration links if migration needed
- Link to detailed diagnostics

**Checking Active Storage Locations:**

Access diagnostic endpoints to see which locations are being used:

- **Server Investigation:** `/v2/api/produkt-updates-server-investigation.php` (comprehensive)
- **Storage Health:** `/v2/api/produkt-updates-storage-health.php` (quick check)
- **Diagnostics:** `/v2/api/produkt-updates-diagnostics.php` (general)

The responses show:

- Which location is active for each file type
- Whether it's volatile (`is_volatile: true`) or persistent
- Whether the location is writable
- Warnings if using `/tmp/` fallback

**Warnings and Notifications:**

- When `/tmp/` fallback is used, warnings are logged to PHP error log
- Admin panel Settings > Storage Status shows warnings
- Diagnostic endpoints flag volatile storage as critical issue
- Migration scripts provide step-by-step guidance

**Fixing Permission Issues:**

To use persistent locations instead of `/tmp/` fallback:

1. **Data File:**

   ```bash
   # Ensure directory exists and is writable
   mkdir -p v2/data/
   chmod 755 v2/data/
   chown www-data:www-data v2/data/  # Adjust user/group as needed

   # If file exists, set permissions
   chmod 644 v2/data/produkt_updates.json
   chown www-data:www-data v2/data/produkt_updates.json
   ```

2. **Image Directory:**

   ```bash
   # Create new persistent location (preferred)
   mkdir -p v2/data/images/produkt-updates/
   chmod 755 v2/data/images/produkt-updates/
   chown www-data:www-data v2/data/images/produkt-updates/

   # Or use existing temp/cache directories
   mkdir -p v2/temp/produkt-updates/
   chmod 755 v2/temp/produkt-updates/
   chown www-data:www-data v2/temp/produkt-updates/
   ```

3. **Log Directory:**
   ```bash
   mkdir -p v2/logs/
   chmod 755 v2/logs/
   chown www-data:www-data v2/logs/
   ```

**Migration from Volatile to Persistent:**

If data/images are in `/tmp/` (volatile), migrate immediately:

1. **Check current state:**

   - Visit: `/v2/admin/produkt-updates/migrate-to-persistent-storage.php?action=check`
   - Visit: `/v2/admin/produkt-updates/migrate-images-to-persistent.php?action=check`

2. **Fix permissions** (see above)

3. **Migrate data:**

   - Visit: `/v2/admin/produkt-updates/migrate-to-persistent-storage.php?action=migrate`
   - Script creates backup automatically
   - Verifies data integrity after migration

4. **Migrate images:**

   - Visit: `/v2/admin/produkt-updates/migrate-images-to-persistent.php?action=migrate&cleanup=0`
   - Updates JSON references automatically
   - Verify: `/v2/admin/produkt-updates/migrate-images-to-persistent.php?action=verify`

5. **Verify everything works:**
   - Check admin panel shows data
   - Check public pages display correctly
   - Check images load correctly
   - Run health check: `/v2/api/produkt-updates-storage-health.php`

**Security Notes:**

- Persistent locations (`v2/data/`, `v2/temp/`, `v2/cache/`) are within project structure
- `/tmp/` is outside web root (secure but volatile)
- All file operations validate paths to prevent directory traversal
- Path resolution is logged for audit purposes
- Images served via direct paths (primary wp-content) or proxy (fallback for non-web-accessible storage)

## Deployment Checklist

**Pre-Deployment:**

- [ ] Backup JSON data file
- [ ] Backup images directory
- [ ] Run linter on all files
- [ ] Test all CRUD operations
- [ ] Validate SEO elements
- [ ] Check for console errors
- [ ] Verify mobile responsiveness

**Post-Deployment:**

- [ ] Monitor error logs for 24 hours
- [ ] Test authentication flow
- [ ] Verify sitemap submission to Google
- [ ] Test RSS feed in readers
- [ ] Check analytics for anomalies

## Support

**For Issues:**

1. Check error logs: `v2/logs/produkt_updates_admin_*.log`
2. Review USER_GUIDE.md for user documentation
3. Review CODE_DOCUMENTATION.md for technical details
4. Contact: hady@ordio.com

## Diagnostic Features (Production Debugging)

### Debug Panel

**Activation:** Press `Ctrl+Shift+D` in the admin dashboard

**Features:**

- Real-time JavaScript logging (replaces console.log in production)
- Function call tracking
- Error monitoring
- Chart initialization debugging
- Navigation event tracking

**Usage:**

```javascript
// Logging is automatic - all admin functions use adminDebugLog()
// Examples of what gets logged:
// - showSection('dashboard') calls
// - Chart initialization steps
// - DOM element checks
// - Error messages with stack traces
```

### Diagnostic Endpoint

**URL:** `/v2/api/produkt-updates-diagnostics.php`

**Method:** GET

**Response:**

```json
{
  "status": "ok|warning|critical",
  "timestamp": "2025-11-19 10:30:00",
  "php": {
    "version": "8.1.0",
    "extensions": { "json": true, "mbstring": true }
  },
  "filesystem": {
    "data_file_writable": true,
    "images_dir_writable": true
  },
  "external_resources": {
    "chartjs_primary": { "available": true, "response_time": "120ms" }
  },
  "issues": []
}
```

**Use Cases:**

- Quick health check of server environment
- Verify PHP extensions before deployment
- Test external CDN availability
- Confirm file permissions

### Browser Diagnostics Function

**Activation:** Open browser console, run `window.runDiagnostics()`

**Output:**

```javascript
{
  "functions": {
    "showSection": true,
    "initActivityChart": true
  },
  "dom": {
    "contentSections": 5,
    "dashboard": true,
    "activityChart": true
  },
  "libraries": {
    "Chart": true,
    "chartJsLoadFailed": false
  },
  "errors": []
}
```

**Use Cases:**

- Verify JavaScript functions loaded
- Check DOM elements exist
- Confirm libraries loaded (Chart.js, Quill.js)
- Review recent errors

### Test Suite

**URL:** `/v2/test/admin-dashboard-test.php`

**Features:**

- Automated testing of critical functions
- Library loading verification
- DOM element checks
- Chart data validation
- Navigation testing
- localStorage compatibility

**Use Cases:**

- Pre-deployment verification
- Troubleshooting production issues
- Regression testing after changes
- docs/ai/rules-archive/product-updates-admin-full.md

# Product Updates System Rules - Admin Panel

**Note:** This file is part of a split from `product-updates.mdc`. See also:

- `product-updates-core.mdc` - Overview, file structure, data structure, common patterns
- `product-updates-content.mdc` - Public page patterns, content guidelines, SEO
- `product-updates-storage.mdc` - Storage system architecture, troubleshooting, diagnostics


## Heavy Instructions Moved

**CRITICAL:** The detailed instructions, edge cases, and massive data for this rule have been moved to optimize AI context.
You MUST read the full documentation before proceeding:
`docs/ai/rules-archive/product-updates-admin-full.md`

# Product Updates System Rules - Content & Public Pages

**Note:** This file is part of a split from `product-updates.mdc`. See also:

- `product-updates-core.mdc` - Overview, file structure, data structure, common patterns
- `product-updates-admin.mdc` - Admin panel patterns, security, production best practices
- `product-updates-storage.mdc` - Storage system architecture, troubleshooting, diagnostics

## Public Page Patterns

### Main Updates Page (`/produkt-updates`)

**Display Logic:**

- Show current month's big features prominently
- Display all small improvements chronologically
- List past months as archive links
- Support blue text markup: `*text*` (inline) and `**text**` (block)
- Hero intro paragraph uses class `produkt-updates-hero-intro`: inline links are **not** underlined (overrides `main .text-base a` content styles on that `<p>`)

**SEO Requirements:**

- Title: "Ordio Produkt-Updates - Neueste Features & Verbesserungen" (or from settings.main_page_title)
  - **IMPORTANT:** Main page title must NOT depend on current_month to avoid duplicate titles with month pages
  - Use "-" separator, NOT "|" for better SEO
  - Default fallback: "Ordio Produkt-Updates - Neueste Features & Verbesserungen - Produkt-Updates"
- Meta description: 155-160 characters, du tone, unique, SEO-optimized
  - Default: "Entdecke die neuesten Features und Verbesserungen von Ordio. Alle Updates für deine Schichtplanung, Zeiterfassung und Personalmanagement auf einen Blick."
- Schema.org CollectionPage markup with proper name matching title
- Open Graph tags (og:title, og:description, og:url, og:image, og:type) matching title/description
- Twitter Card tags matching title/description
- Canonical URL: absolute path `https://www.ordio.com/produkt-updates`

### Month Page (`/produkt-updates/[month-slug]`)

**Display Logic:**

- Show month name and intro text (same hero class `produkt-updates-hero-intro` as main page for consistent link styling)
- List features for that month
- List improvements for that month
- Provide navigation back to main page

**SEO Requirements:**

- Title: "Ordio Updates [Month Name] - Produkt-Updates"
  - **IMPORTANT:** Use "-" separator, NOT "|" for consistency and better SEO
  - Format: "Ordio Updates November 2025 - Produkt-Updates"
- Meta description: Unique per month, 155-160 characters, du tone, SEO-optimized
  - Format: "[month intro_text] Entdecke neue Features und Verbesserungen für deine Schichtplanung und Zeiterfassung mit Ordio."
- Schema.org BlogPosting markup (not CollectionPage) with headline matching title
- Open Graph tags with og:type="article" matching title/description
- Twitter Card tags matching title/description
- Canonical URL with month slug: `https://www.ordio.com/produkt-updates/[month-slug]`

### Individual Post Page (`/produkt-updates/[post-slug]`)

**Display Logic:**

- Full-width hero image
- Rich content from `page_content` field
- Tag badge (Neues Feature, Verbesserung, Bugfix)
- Platform icons (Desktop/Mobile)
- Published date
- Back navigation to month and main updates
- **Related carousel** (`v2/base/produkt_updates_carousel.php`): after the “Entdecke unsere Produkte …” block and before the footer; shows other big-feature posts with `read_more_link`, newest first, excluding the current slug (dynamic from `produkt_updates.json`; hidden if there are no other posts)

**SEO Override Logic (Feature-Level):**

When set in the feature data, these fields override defaults in `post.php`:

- **seo_title** (optional, max 41 chars): User part only; suffix `" - Ordio Produkt-Updates"` appended server-side. Fallback: `title`
- **meta_description** (optional, max 155 chars): Fallback: truncated `description`
- **featured_image_alt** (optional, max 125 chars): For `og:image:alt` and img alt. Fallback: `title`

**SEO Requirements:**

- Title: `seo_title` if set, else `title`; suffix `" - Ordio Produkt-Updates"`
  - **IMPORTANT:** Use "-" separator, NOT "|" for consistency and better SEO
- Schema.org Article markup
- Open Graph tags with featured image; `og:image:alt` from `featured_image_alt` or `title`
- Structured data for published date, modified date
- Canonical URL with post slug

### XML Sitemap (`/sitemap-produkt-updates.xml`)

**Requirements:**

- Include main updates page
- Include all month pages
- Include all individual post pages
- Set `lastmod` to item's published date
- Priority: 0.7 for updates pages
- Update frequency: weekly

### RSS Feed (`/produkt-updates/feed.xml`)

**Requirements:**

- RSS 2.0 format with `xmlns:ordio="https://www.ordio.com/ns/produkt-updates"`
- Include month aggregates, big features (with read_more_link), and small improvements
- Each item: `<ordio:type>big_feature</ordio:type>` or `<ordio:type>small_improvement</ordio:type>`, `<ordio:show_in_app_banner>true|false</ordio:show_in_app_banner>`
- Use `htmlToPlainText()` (from `v2/helpers/html-to-plain-text.php`) for descriptions – never raw `strip_tags()` on multi-paragraph HTML (preserves spaces between block elements)
- Proper UTF-8 encoding, valid XML structure

### JSON Feed (`/produkt-updates/feed.json`)

**Requirements:**

- JSON Feed 1.1 format (application/feed+json)
- Flat items list: big features + small improvements, sorted by date DESC
- `_ordio` extension per item: `{ type, show_in_app_banner, platforms }`
- Use `htmlToPlainText()` for `summary` field – never raw `strip_tags()` on multi-paragraph HTML (preserves spaces between block elements)
- Query param `?in_app_banner_only=1`: Filter to items with show_in_app_banner=true
- CORS: `Access-Control-Allow-Origin: *`

## Content Guidelines

### Copy Tone

**Requirements:**

See `.cursor/rules/shared-patterns.mdc` for universal copy guidelines (du tone, Ordio mentions, competitor language, copy quality).

**Product Updates-Specific:**

- Focus on user benefits and improvements
- Highlight what's new and why it matters
- Use clear, concise language for technical features

### Rich Text Editor (Quill.js)

**Allowed Formatting:**

- **Bold**, _Italic_, <u>Underline</u>
- Blockquote (for quotes/callouts)
- Headings: H1, H2, H3
- Lists: bulleted and numbered
- Links with URL validation (external links get target="_blank" rel="noopener noreferrer")
- Line breaks
- **Images**: Inline via upload; `<img>` allowed with src validation (https, relative, /wp-content/uploads/, serve-produkt-updates-image for backward compat, ordio.com)
- **Video/Embeds**: YouTube, Vimeo iframes; `<iframe>` allowed from whitelist domains only
- **Native video**: `<video>` and `<source>` tags with src validation

**Embed Domain Whitelist:**

- youtube.com, www.youtube.com, youtu.be
- vimeo.com, www.vimeo.com, player.vimeo.com
- ordio.com, www.ordio.com

**Sanitization:**

- Strip all tags except: `<p>`, `<strong>`, `<em>`, `<b>`, `<i>`, `<u>`, `<h1>`, `<h2>`, `<h3>`, `<ul>`, `<ol>`, `<li>`, `<a>`, `<br>`, `<blockquote>`, `<img>`, `<iframe>`, `<video>`, `<source>`
- Remove `onclick`, `onerror`, and other event handlers
- Remove `style` attributes
- Remove `javascript:` protocol in links
- Validate link URLs (http/https/mailto only)

### Image Guidelines

**Format:**

- WebP preferred for modern browsers
- JPEG/PNG fallbacks
- Descriptive alt text required
- Consistent aspect ratios per content type

**Sizes:**

- Featured images: 1280x720px (16:9)
- Inline images: max 1920px width
- Thumbnails: auto-generated if needed

# Product Updates System Rules - Core

**Note:** This file is part of a split from `product-updates.mdc`. See also:

- `product-updates-admin.mdc` - Admin panel patterns, security, production best practices
- `product-updates-content.mdc` - Public page patterns, content guidelines, SEO
- `product-updates-storage.mdc` - Storage system architecture, troubleshooting, diagnostics


## Heavy Instructions Moved

**CRITICAL:** The detailed instructions, edge cases, and massive data for this rule have been moved to optimize AI context.
You MUST read the full documentation before proceeding:
`docs/ai/rules-archive/product-updates-core-full.md`

# Product Updates System Rules - Storage & Diagnostics

**Note:** This file is part of a split from `product-updates.mdc`. See also:

- `product-updates-core.mdc` - Overview, file structure, data structure, common patterns
- `product-updates-admin.mdc` - Admin panel patterns, security, production best practices
- `product-updates-content.mdc` - Public page patterns, content guidelines, SEO


## Heavy Instructions Moved

**CRITICAL:** The detailed instructions, edge cases, and massive data for this rule have been moved to optimize AI context.
You MUST read the full documentation before proceeding:
`docs/ai/rules-archive/product-updates-storage-full.md`

## Overview

**This rule has been split into focused files for better maintainability:**

- **`product-updates-core.mdc`** - Overview, file structure, data structure, common patterns, version history
- **`product-updates-admin.mdc`** - Admin panel patterns, security requirements, production best practices, error handling, performance requirements, testing requirements, keyboard shortcuts, unimplemented features
- **`product-updates-content.mdc`** - Public page patterns, content guidelines
- **`product-updates-storage.mdc`** - Storage system architecture, troubleshooting, deployment checklist, support, diagnostic features

The Product Updates system allows marketing and customer success teams to manage product feature announcements and improvements through a custom CMS.

**See the split files above for detailed documentation.**
