# Blog Template Patterns Analysis

**Last Updated:** 2026-01-09

Analysis of existing PHP template patterns for blog template development, documenting reusable patterns, component structures, and best practices.

## Overview

This document analyzes existing template patterns from:

- `v2/pages/templates_template.php` - Dynamic template page
- `v2/pages/post.php` - Product updates post page
- `v2/pages/templates_index.php` - Templates archive/index page
- `v2/config/template-meta-generator.php` - Meta tag generation
- `v2/config/template-schema-generator.php` - Schema generation
- `v2/components/` - Reusable component patterns

## Template Structure Pattern

### Standard Template Flow

```php
<?php
session_start();

// 1. Load configuration and helper functions
require_once __DIR__ . '/../config/template-meta-generator.php';
require_once __DIR__ . '/../config/template-schema-generator.php';
require_once __DIR__ . '/../helpers/blog-template-helpers.php';

// 2. Load/process data
$postData = load_blog_post($slug, $category);

// 3. Generate meta tags and schema
$metaTags = generate_blog_meta_tags('single_post', $postData);
$schema = generate_blog_schema('single_post', $postData);
?>

<!doctype html>
<html lang="de">
<head>
    <!-- 4. Render meta tags -->
    <?php echo render_blog_meta_tags('single_post', $postData); ?>

    <!-- 5. Include base head -->
    <?php
    $aosScript = "true";  // or "false"
    $swiperScript = "false";  // or "true"
    include '../base/head.php';
    ?>

    <!-- 6. Render schema -->
    <?php echo render_blog_schema('single_post', $postData); ?>
</head>

<body>
    <!-- 7. Include header -->
    <?php
    $headerwidth = "w-full";  // or "max-w-7xl"
    include '../base/header.php';
    ?>

    <!-- 8. Main content -->
    <main>
        <!-- Content here -->
    </main>

    <!-- 9. Include footer -->
    <?php
    $color_fill = '#fff';
    $color_background = '#fbfbfb';
    $rotate = '0';
    $margin_bottom = 'mb-24';
    include '../base/footer.php';
    ?>
</body>
</html>
```

### Key Patterns

1. **Session Start**: Always start with `session_start()` for session management
2. **Require Order**: Load helpers/config before using them
3. **Data Loading**: Load data early, validate, apply defaults
4. **Meta/Schema Generation**: Generate before HTML output
5. **Base Includes**: Use standard base components (head, header, footer)
6. **Variable Configuration**: Set variables before including base components

## Component Pattern

### Component Structure

Components are PHP files in `v2/components/` that accept props via PHP variables:

```php
<?php
/**
 * Component Name
 *
 * @param string $title Component title
 * @param array $items List of items
 * @param bool $showBadge Whether to show badge
 */

// Default values if not set
if (!isset($title)) {
    $title = 'Default Title';
}

if (!isset($items)) {
    $items = [];
}

if (!isset($showBadge)) {
    $showBadge = false;
}
?>

<div class="component-wrapper">
    <?php if ($showBadge): ?>
        <span class="badge"><?php echo htmlspecialchars($title); ?></span>
    <?php endif; ?>

    <?php foreach ($items as $item): ?>
        <div class="item">
            <?php echo htmlspecialchars($item); ?>
        </div>
    <?php endforeach; ?>
</div>
```

### Component Usage Pattern

```php
<?php
// Set component props
$title = 'Post Title';
$items = ['Item 1', 'Item 2'];
$showBadge = true;

// Include component
include __DIR__ . '/../components/ComponentName.php';
?>
```

### Component Best Practices

1. **Default Values**: Always check `isset()` and provide defaults
2. **HTML Escaping**: Use `htmlspecialchars()` for all output
3. **Conditional Rendering**: Use PHP conditionals for optional elements
4. **Reusability**: Make components flexible with props
5. **Documentation**: Document props in PHPDoc comments

## Meta Tag Generation Pattern

### Generator Function Pattern

```php
function generate_blog_meta_tags($page_type, $data) {
    // Get base values with fallbacks
    $title = $data['title'] ?? 'Default Title';
    $description = $data['description'] ?? 'Default Description';

    // Enhance/validate values
    if (strlen($description) > 160) {
        $description = substr($description, 0, 157) . '...';
    }

    // Build meta tags array
    $metaTags = [];

    // Basic meta tags
    $metaTags[] = ['tag' => 'title', 'content' => $title];
    $metaTags[] = ['tag' => 'meta', 'name' => 'description', 'content' => $description];

    // Open Graph tags
    $metaTags[] = ['tag' => 'meta', 'property' => 'og:title', 'content' => $title];
    $metaTags[] = ['tag' => 'meta', 'property' => 'og:description', 'content' => $description];

    // Twitter Card tags
    $metaTags[] = ['tag' => 'meta', 'name' => 'twitter:card', 'content' => 'summary_large_image'];

    return $metaTags;
}
```

### Render Function Pattern

```php
function render_blog_meta_tags($page_type, $data) {
    $metaTags = generate_blog_meta_tags($page_type, $data);
    $html = '';

    foreach ($metaTags as $meta) {
        if ($meta['tag'] === 'title') {
            $html .= '<title>' . htmlspecialchars($meta['content'], ENT_QUOTES, 'UTF-8') . '</title>' . "\n    ";
        } elseif ($meta['tag'] === 'meta') {
            $attrs = [];
            if (isset($meta['name'])) {
                $attrs[] = 'name="' . htmlspecialchars($meta['name'], ENT_QUOTES, 'UTF-8') . '"';
            }
            if (isset($meta['property'])) {
                $attrs[] = 'property="' . htmlspecialchars($meta['property'], ENT_QUOTES, 'UTF-8') . '"';
            }
            if (isset($meta['content'])) {
                $attrs[] = 'content="' . htmlspecialchars($meta['content'], ENT_QUOTES, 'UTF-8') . '"';
            }
            $html .= '<meta ' . implode(' ', $attrs) . ' />' . "\n    ";
        } elseif ($meta['tag'] === 'link') {
            $attrs = [];
            if (isset($meta['rel'])) {
                $attrs[] = 'rel="' . htmlspecialchars($meta['rel'], ENT_QUOTES, 'UTF-8') . '"';
            }
            if (isset($meta['href'])) {
                $attrs[] = 'href="' . htmlspecialchars($meta['href'], ENT_QUOTES, 'UTF-8') . '"';
            }
            $html .= '<link ' . implode(' ', $attrs) . '>' . "\n    ";
        }
    }

    return trim($html);
}
```

### Meta Tag Requirements

- **Title**: < 60 characters, include year, keywords, "Ordio"
- **Description**: 155-160 characters, du tone, benefit-driven
- **Open Graph**: Complete set (title, description, url, image, type, locale)
- **Twitter Card**: Complete set (card, title, description, image)
- **Canonical**: Absolute URL, correct path

## Schema Generation Pattern

### Generator Function Pattern

```php
function generate_blog_schema($page_type, $data) {
    // Build schema graph
    $schema = [
        '@context' => 'https://schema.org',
        '@graph' => []
    ];

    // WebPage schema (all pages)
    $webPage = [
        '@type' => 'WebPage',
        '@id' => $data['url'] . '#webpage',
        'url' => $data['url'],
        'name' => $data['title'],
        'description' => $data['description'],
        'inLanguage' => 'de-DE',
        'isPartOf' => [
            '@id' => 'https://www.ordio.com/#website'
        ],
        'about' => [
            '@id' => 'https://www.ordio.com/#organization'
        ],
        'datePublished' => $data['publication_date'],
        'dateModified' => $data['modified_date']
    ];
    $schema['@graph'][] = $webPage;

    // Page-specific schemas
    if ($page_type === 'single_post') {
        // Article schema
        $article = [
            '@type' => 'Article',
            '@id' => $data['url'] . '#article',
            'headline' => $data['title'],
            'datePublished' => $data['publication_date'],
            'author' => [
                '@id' => 'https://www.ordio.com/#organization'
            ]
        ];
        $schema['@graph'][] = $article;
    }

    return $schema;
}
```

### Render Function Pattern

```php
function render_blog_schema($page_type, $data) {
    $schema = generate_blog_schema($page_type, $data);
    $json = json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    return '<script type="application/ld+json">' . "\n    " . $json . "\n    " . '</script>';
}
```

### Schema Requirements

- **@graph Structure**: Use `@graph` for multiple schemas
- **@id References**: Use `@id` for cross-referencing
- **Required Fields**: Include all required fields per schema type
- **Dates**: ISO 8601 format (YYYY-MM-DDTHH:mm:ss+01:00)
- **URLs**: Absolute URLs, correct paths
- **Validation**: Test with Google Rich Results Test

## Data Loading Pattern

### Data Loading Function Pattern

```php
function load_blog_post($slug, $category) {
    // Validate inputs
    if (empty($slug) || empty($category)) {
        return null;
    }

    // Construct file path
    $filePath = __DIR__ . '/../data/blog/posts/' . $category . '/' . $slug . '.json';

    // Check file exists
    if (!file_exists($filePath)) {
        return null;
    }

    // Load and parse JSON
    $content = @file_get_contents($filePath);
    if ($content === false) {
        return null;
    }

    $data = json_decode($content, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        return null;
    }

    // Apply defaults
    $data = apply_defaults($data);

    return $data;
}
```

### Error Handling Pattern

```php
// Try to load data
$postData = load_blog_post($slug, $category);

// Handle errors gracefully
if (!$postData) {
    http_response_code(404);
    header('Location: /insights/');
    exit;
}
```

## HTML Sanitization Pattern

### Sanitization Function (from post.php)

```php
function sanitizeHtmlOutput($html) {
    if (empty($html)) {
        return '';
    }

    // If content doesn't contain HTML tags, treat as plain text
    if (strip_tags($html) === $html) {
        return nl2br(htmlspecialchars($html, ENT_QUOTES, 'UTF-8'));
    }

    // Content contains HTML - sanitize it
    $allowed_tags = '<p><br><strong><em><b><i><ul><ol><li><a><h1><h2><h3>';
    $html = strip_tags($html, $allowed_tags);

    // Clean up attributes - only allow safe attributes on <a> tags
    $html = preg_replace_callback('/<a\s+([^>]*)>/i', function($matches) {
        // Extract and validate href, target, rel
        // Only allow http/https/mailto or relative URLs
        // Only allow safe target values
        // Only allow safe rel values
    }, $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);
}
```

## Pagination Pattern

### Pagination Logic Pattern

```php
// Get current page from query string
$current_page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$posts_per_page = 10;

// Calculate pagination
$total_posts = count($all_posts);
$total_pages = ceil($total_posts / $posts_per_page);

// Get posts for current page
$offset = ($current_page - 1) * $posts_per_page;
$posts = array_slice($all_posts, $offset, $posts_per_page);

// Generate pagination URLs
$base_url = '/insights/';
$prev_url = $current_page > 1 ? $base_url . 'page/' . ($current_page - 1) . '/' : null;
$next_url = $current_page < $total_pages ? $base_url . 'page/' . ($current_page + 1) . '/' : null;
```

## Image Optimization Pattern

### Responsive Image Pattern

```php
// Generate srcset for responsive images
function generate_image_srcset($image_path, $sizes = [640, 768, 1024, 1280]) {
    $srcset = [];
    foreach ($sizes as $size) {
        $srcset[] = $image_path . '-' . $size . 'w.webp ' . $size . 'w';
    }
    return implode(', ', $srcset);
}

// Usage in template
$image_srcset = generate_image_srcset('/v2/img/blog/featured-image');
?>

<img
    src="/v2/img/blog/featured-image-640w.webp"
    srcset="<?php echo htmlspecialchars($image_srcset); ?>"
    sizes="(max-width: 640px) 640px, (max-width: 1024px) 1024px, 1280px"
    alt="<?php echo htmlspecialchars($alt_text); ?>"
    width="1280"
    height="720"
    loading="lazy"
    fetchpriority="<?php echo $is_hero ? 'high' : 'low'; ?>"
/>
```

## URL Structure Pattern

### URL Generation Pattern

```php
function get_blog_post_url($slug, $category) {
    return '/insights/' . $category . '/' . $slug . '/';
}

function get_category_url($category) {
    return '/insights/' . $category . '/';
}

function get_blog_index_url() {
    return '/insights/';
}

function get_topic_hub_url($topic) {
    return '/insights/topics/' . $topic . '/';
}
```

## Error Handling Pattern

### Validation Pattern

```php
// Validate data before use
$validation = validate_blog_post_data($postData);
if (!$validation['valid']) {
    // Log errors
    if (function_exists('ordio_log')) {
        ordio_log('ERROR', 'Invalid blog post data', [
            'errors' => $validation['errors'],
            'slug' => $slug
        ]);
    }

    // Redirect or show error
    http_response_code(404);
    header('Location: /insights/');
    exit;
}
```

## Caching Pattern

### Template Caching Pattern (from comparison pages)

```php
// Start output buffering
ob_start();

// Cache key
$cache_key = 'blog_post_' . md5($slug . $category);
$cache_file = __DIR__ . '/../cache/' . $cache_key . '.html';

// Check if cached version exists and is fresh (24 hours)
if (file_exists($cache_file) && (time() - filemtime($cache_file)) < 86400) {
    readfile($cache_file);
    exit;
}

// Generate content
// ... template code ...

// Save to cache
$content = ob_get_contents();
file_put_contents($cache_file, $content);
ob_end_flush();
```

## Reusable Patterns Summary

### 1. Template Structure

- Session start
- Load helpers/config
- Load data
- Generate meta/schema
- Include base components
- Render content

### 2. Component Pattern

- Props via PHP variables
- Default values with `isset()`
- HTML escaping
- Conditional rendering
- Reusable across pages

### 3. Meta Tag Pattern

- Generator function returns array
- Render function outputs HTML
- Validation (length, format)
- Complete OG/Twitter tags

### 4. Schema Pattern

- JSON-LD format
- @graph structure
- @id references
- Required fields per type
- Validation required

### 5. Data Loading

- File path construction
- JSON parsing
- Error handling
- Default values
- Validation

### 6. HTML Sanitization

- Allow safe tags only
- Clean attributes
- Remove dangerous code
- Preserve formatting

### 7. Pagination

- Query string parsing
- Offset calculation
- URL generation
- Previous/Next logic

### 8. Image Optimization

- Responsive srcset
- Sizes attribute
- Lazy loading
- Fetchpriority
- Explicit dimensions

### 9. URL Structure

- Consistent patterns
- Category-based
- Slug-based
- Trailing slashes

### 10. Error Handling

- Validation functions
- Graceful fallbacks
- Error logging
- User-friendly redirects

## Best Practices

1. **Consistency**: Follow existing patterns exactly
2. **Security**: Always sanitize user input and HTML output
3. **Performance**: Use caching where appropriate
4. **Accessibility**: Semantic HTML, ARIA attributes
5. **SEO**: Complete meta tags, valid schema
6. **Error Handling**: Graceful fallbacks, logging
7. **Documentation**: PHPDoc comments, inline comments
8. **Testing**: Test locally before deployment

## Related Documentation

- [Migration Template Requirements](MIGRATION_TEMPLATE_REQUIREMENTS.md)
- [Frontend Layouts](FRONTEND_LAYOUTS.md)
- [Frontend Components](FRONTEND_COMPONENTS.md)
- [Template Best Practices](TEMPLATE_BEST_PRACTICES.md)
