# Pages

[![Commercial](https://img.shields.io/badge/License-Commercial-red.svg)](LICENSE.md)
[![Cockpit CMS](https://img.shields.io/badge/Cockpit%20CMS-Addon-blue.svg)](https://getcockpit.com)
[![Pages](https://img.shields.io/badge/Feature-Website%20Builder-green.svg)](#features)

> **Professional website and page management system for Cockpit CMS**

Pages transforms Cockpit CMS into a powerful website builder with hierarchical page structures, dynamic routing, multi-language support, and SEO optimization. Build complex websites with visual layouts, content-driven pages, and sophisticated navigation systems.

## ✨ Features

### 🏗️ **Page Types System**
- **Layout Pages**: Visual page builder using Layout components
- **Collection Pages**: Dynamic pages connected to content collections with list/detail views
- **Singleton Pages**: Pages connected to singleton content models
- **Custom Page Types**: Extensible system for unlimited page type variations

### 🌲 **Hierarchical Structure**
- **Parent/Child Relationships**: Build complex site structures with unlimited nesting
- **Automatic Route Generation**: SEO-friendly URLs based on page hierarchy
- **Slug Management**: Customizable URL slugs with automatic fallbacks
- **Breadcrumb Support**: Automatic parent-child navigation tracking

### 🌍 **Multi-language Support**
- **Locale-specific Routes**: Different URLs for each language
- **Content Localization**: Full i18n support for all page content
- **Custom Slugs per Language**: Localized URLs for better SEO
- **Global/Page-level Control**: Enable/disable languages globally or per page

### 🎯 **SEO & Performance**
- **Meta Management**: Title, description, keywords per page
- **Open Graph Support**: Social media optimization
- **XML Sitemap**: Automatic sitemap generation
- **Custom Scripts**: Header/footer code injection
- **Route Optimization**: Efficient URL resolution and caching

### 📊 **Dynamic Content Pages**
- **Automatic List/Detail Views**: Generate listing and individual item pages
- **Advanced Filtering**: Custom content filters and sorting
- **Pagination Support**: Built-in pagination for large datasets
- **Layout Integration**: Before/after layout components for custom designs
- **Flexible Routing**: Use any field as URL slug with i18n support

## 🚀 Quick Start

### 1. Page Types Overview

**Layout Page** - Visual page builder:
```php
'type' => 'layout',
'data' => [
    'layout' => [
        ['component' => 'heading', 'text' => 'Welcome'],
        ['component' => 'richtext', 'html' => '<p>Page content</p>']
    ]
]
```

**Collection Page** - Dynamic content listing:
```php
'type' => 'collection',
'data' => [
    'collection' => 'blog',
    'slugField' => 'slug',
    'limit' => 10,
    'filter' => ['published' => true]
]
```

**Singleton Page** - Single content item:
```php
'type' => 'singleton',
'data' => [
    'singleton' => 'homepage'
]
```

### 2. Basic Site Structure

```
Home (/)
├── About (/about)
├── Blog (/blog) [Collection Page]
│   ├── Article 1 (/blog/my-first-post)
│   └── Article 2 (/blog/another-post)
├── Services (/services)
│   ├── Web Design (/services/web-design)
│   └── Development (/services/development)
└── Contact (/contact) [Layout Page]
```

### 3. Creating Your First Page

1. **Navigate to Pages** in the admin sidebar
2. **Create New Page** and choose page type
3. **Configure page settings**:
   - Title and slug
   - Parent page (optional)
   - SEO settings
   - Locale configuration

## 📋 Page Configuration

### General Settings

```php
// Basic page properties
[
    'title' => 'Page Title',           // Display title
    'slug' => 'page-url',             // URL segment
    'type' => 'layout',               // Page type
    '_pid' => null,                   // Parent page ID (null for root)
    '_state' => 1,                    // Published state (1=published, 0=draft, -1=deleted)
    '_o' => 0,                        // Sort order
]
```

### SEO Configuration

```php
'seo' => [
    'title' => 'SEO Title',           // Override page title for SEO
    'description' => 'Meta description for search engines',
    'keywords' => 'keyword1, keyword2, keyword3',
    'noindex' => false,               // Prevent search engine indexing
    'nofollow' => false,              // Prevent following links
    'og' => [                         // Open Graph for social media
        'title' => 'Social Title',
        'description' => 'Social description',
        'image' => 'path/to/image.jpg'
    ]
]
```

### Multi-language Configuration

```php
// Locale-specific settings
'_locales' => [
    'en' => true,                     // Enable English
    'de' => true,                     // Enable German
    'fr' => false                     // Disable French for this page
],

// Locale-specific slugs
'slug_de' => 'deutsche-url',          // German slug
'slug_fr' => 'url-francaise',        // French slug

// Locale-specific routes (auto-generated)
'_r' => '/about',                     // Default route
'_r_de' => '/ueber-uns',             // German route
'_r_fr' => '/a-propos'               // French route
```

## 🎨 Layout Pages

Layout pages use the Layout addon for visual page building:

### Basic Layout Configuration

```php
'type' => 'layout',
'data' => [
    'layout' => [
        [
            'component' => 'section',
            'children' => [
                [
                    'component' => 'heading',
                    'text' => 'Welcome to Our Site',
                    'level' => 1
                ],
                [
                    'component' => 'richtext',
                    'html' => '<p>This is the main content of our homepage.</p>'
                ]
            ]
        ],
        [
            'component' => 'grid',
            'colWidth' => 3,
            'children' => [
                [
                    'component' => 'image',
                    'asset' => ['_id' => 'asset-id', 'path' => 'image.jpg']
                ],
                [
                    'component' => 'button',
                    'caption' => 'Learn More',
                    'url' => '/about'
                ]
            ]
        ]
    ]
]
```

### Layout Component Restrictions

```php
// In Pages settings, restrict available components
'allowedLayoutComponents' => [
    'heading', 'richtext', 'image', 'button', 'section', 'grid'
]
```

## 📊 Collection Pages

Collection pages create dynamic listings and detail views:

### Collection Page Configuration

```php
'type' => 'collection',
'data' => [
    'collection' => 'blog',           // Content collection name
    'slugField' => 'slug',            // Field to use for URLs (default: _id)
    'limit' => 10,                    // Items per page
    'filter' => [                     // MongoDB-style filter
        'published' => true,
        'category' => 'news'
    ],
    'sort' => [                       // Sort order
        '_created' => -1              // Newest first
    ],
    'lstFields' => [                  // Fields to return for list view
        'title' => 1,
        'excerpt' => 1,
        'image' => 1,
        'slug' => 1
    ]
]
```

### Advanced Collection Features

**Custom Layouts for List/Detail Views**:
```php
'layoutList' => [
    'before' => [                     // Layout before item list
        ['component' => 'heading', 'text' => 'Latest Articles']
    ],
    'after' => [                      // Layout after item list
        ['component' => 'button', 'caption' => 'View Archive', 'url' => '/archive']
    ]
],
'layoutDetail' => [
    'before' => [                     // Layout before item content
        ['component' => 'breadcrumb']
    ],
    'after' => [                      // Layout after item content
        ['component' => 'related-posts']
    ]
]
```

**Dynamic Filtering with URL Parameters**:
```php
// URL: /blog?category=news&tag=announcement&limit=5
// These parameters override the page configuration:
$options = [
    'filter' => ['category' => 'news', 'tag' => 'announcement'],
    'limit' => 5
];
```

### Collection Page URLs

Collection pages generate two types of URLs:

1. **List Page**: `/blog` - Shows paginated list of items
2. **Detail Pages**: `/blog/{slug}` - Shows individual items

Example URL structure:
```
/blog                           # List page
/blog/my-first-post            # Detail page (slug field)
/blog/507f1f77bcf86cd799439011 # Detail page (_id fallback)
```

## 📖 Singleton Pages

Connect pages to singleton content models:

```php
'type' => 'singleton',
'data' => [
    'singleton' => 'homepage'         // Singleton model name
]
```

The page will automatically display the singleton's content with full localization support.

## 🧭 Menu System

### Creating Menus

Create navigation menus through the admin interface or programmatically:

```php
// Menu structure
[
    'name' => 'main-menu',
    'label' => 'Main Navigation',
    'links' => [
        [
            'title' => 'Home',
            'url' => '/',
            'active' => true
        ],
        [
            'title' => 'About',
            'url' => '/about',
            'children' => [
                [
                    'title' => 'Team',
                    'url' => '/about/team'
                ],
                [
                    'title' => 'History',
                    'url' => '/about/history'
                ]
            ]
        ],
        [
            'title' => 'Blog',
            'url' => 'pages://blog-page-id',  // Reference to page
            'target' => '_self'
        ]
    ]
]
```

### Page Reference URLs

Use `pages://page-id` to reference pages directly:
- **Automatic URL resolution** based on current locale
- **Maintains links** when page URLs change
- **Supports multi-language** menu generation

## 🔧 Custom Page Types

Create custom page types for specific functionality:

### Defining Custom Page Types

```php
// In your addon's bootstrap.php
$this->on('pages.pagetype', function($pageTypes) {
    
    $pageTypes['product'] = [
        'name' => 'product',
        'label' => 'Product Page',
        'icon' => 'shopping_cart',
        'group' => 'E-commerce',
        
        // Define configuration fields
        'data' => function() use($app) {
            return [
                [
                    'name' => 'productId',
                    'type' => 'text',
                    'label' => 'Product ID',
                    'required' => true
                ],
                [
                    'name' => 'showReviews',
                    'type' => 'boolean',
                    'label' => 'Show Reviews'
                ]
            ];
        },
        
        // Route resolution logic
        'resolve' => function($page, array $options = []) use($app) {
            $productId = $options['params'][0] ?? null;
            
            if (!$productId) {
                return false;
            }
            
            // Fetch product data
            $product = $app->module('content')->item('products', [
                'slug' => $productId,
                '_state' => 1
            ]);
            
            if (!$product) {
                return false;
            }
            
            $page['data']['product'] = $product;
            return $page;
        },
        
        // Content processing
        'process' => function($page, $locale = 'default', $options = []) use($app) {
            
            // Add related products
            if (isset($page['data']['product'])) {
                $category = $page['data']['product']['category'];
                
                $related = $app->module('content')->items('products', [
                    'filter' => [
                        'category' => $category,
                        '_id' => ['$ne' => $page['data']['product']['_id']]
                    ],
                    'limit' => 4
                ]);
                
                $page['data']['relatedProducts'] = $related;
            }
            
            return $page;
        }
    ];
});
```

### Page Type URL Patterns

Custom page types can handle complex URL patterns:

```php
// URL: /products/electronics/smartphone-x
'resolve' => function($page, array $options = []) use($app) {
    $category = $options['params'][0] ?? null;  // electronics
    $product = $options['params'][1] ?? null;   // smartphone-x
    
    // Your resolution logic here
}
```

## 🌐 Multi-language Setup

### Global Locale Configuration

```php
// In Pages settings
'locales' => [
    'en' => true,                     // Enable English
    'de' => true,                     // Enable German
    'fr' => false,                    // Disable French globally
    'es' => true                      // Enable Spanish
]
```

### Page-level Locale Control

```php
// Per-page locale settings override global settings
'_locales' => [
    'en' => true,                     // Page available in English
    'de' => false,                    // Page NOT available in German
    'es' => true                      // Page available in Spanish
]
```

### Locale-specific Content

```php
// Localized page content
'title' => 'English Title',
'title_de' => 'Deutscher Titel',
'title_es' => 'Título Español',

'slug' => 'english-url',
'slug_de' => 'deutsche-url',
'slug_es' => 'url-espanol'
```

### Language Switching

```php
// Get page in different locales
$pageEn = $app->module('pages')->pageByRoute('/about', ['locale' => 'en']);
$pageDe = $app->module('pages')->pageByRoute('/ueber-uns', ['locale' => 'de']);

// Access locale-specific routes
$routes = $page['_routes'];         // ['en' => '/about', 'de' => '/ueber-uns']
```

## 🔧 Advanced Configuration

### Pages Settings

```php
// Global Pages settings
$settings = [
    'revisions' => true,              // Enable page revisions
    'meta' => [                       // Global meta tags
        'viewport' => 'width=device-width, initial-scale=1',
        'robots' => 'index, follow'
    ],
    'preview' => [                    // Preview configuration
        'url' => 'https://preview.site.com/{route}',
        'auth' => 'bearer-token'
    ],
    'images' => [                     // Site images
        'logo' => 'path/to/logo.svg',
        'favicon' => 'path/to/favicon.ico',
        'small' => 'path/to/small-logo.png'
    ],
    'scripts' => [                    // Global scripts
        'header' => '<script>/* analytics */</script>',
        'footer' => '<script>/* tracking */</script>'
    ],
    'seo' => [                        // Default SEO settings
        'title' => 'Default Site Title',
        'description' => 'Default meta description',
        'keywords' => 'default, keywords'
    ],
    'allowedLayoutComponents' => [     // Restrict layout components
        'heading', 'richtext', 'image', 'button'
    ]
];

$app->dataStorage->setKey('pages/options', 'settings', $settings);
```

### Route Customization

```php
// Custom route resolution
$this->on('pages.resolve.path', function($route, &$page, $locale, $options) {
    
    // Handle custom routes
    if (preg_match('#^/custom/(.+)#', $route, $matches)) {
        
        $customId = $matches[1];
        
        // Find or create page dynamically
        $page = $this->dataStorage->findOne('pages', [
            'type' => 'custom',
            'data.customId' => $customId
        ]);
    }
});
```

## 📊 API Reference

### REST API Endpoints

#### Pages

**Get all published pages**:
```http
GET /api/pages/pages
```

Query Parameters:
- `locale` - Return pages for specified locale
- `filter` - URL-encoded JSON filter
- `sort` - URL-encoded JSON sort order
- `fields` - URL-encoded JSON field projection
- `limit` - Maximum number of pages
- `skip` - Number of pages to skip

**Get page by ID**:
```http
GET /api/pages/page/{id}
```

**Get page by route**:
```http
GET /api/pages/page?route=/about
```

#### Menus

**Get all menus**:
```http
GET /api/pages/menus
```

**Get specific menu**:
```http
GET /api/pages/menu/main-navigation
```

#### Utility

**Get site settings**:
```http
GET /api/pages/settings
```

**Get routes/sitemap**:
```http
GET /api/pages/routes
GET /api/pages/sitemap
```

### GraphQL API

#### Queries

**Page by Route**:
```graphql
query GetPage($route: String!, $locale: String) {
    pageByRoute(route: $route, locale: $locale) {
        _id
        title
        slug
        type
        data
        seo {
            title
            description
        }
        _routes
    }
}
```

**Page by ID**:
```graphql
query GetPageById($id: String!, $locale: String) {
    pageByID(id: $id, locale: $locale) {
        _id
        title
        data
        _parent
        _children
    }
}
```

**Menu**:
```graphql
query GetMenu($name: String!, $locale: String) {
    menu(name: $name, locale: $locale) {
        name
        links {
            title
            url
            children {
                title
                url
            }
        }
    }
}
```

**Settings**:
```graphql
query GetSettings($locale: String) {
    pagesSettings(locale: $locale) {
        seo {
            title
            description
        }
        images {
            logo
            favicon
        }
        locales
    }
}
```

### JavaScript API (Frontend)

**Page Data Access**:
```javascript
// Fetch page data
fetch('/api/pages/page?route=/about')
    .then(response => response.json())
    .then(page => {
        console.log(page.title);
        console.log(page.data.layout);  // Layout components
        console.log(page.seo);          // SEO data
    });

// Multi-language support
fetch('/api/pages/page?route=/about&locale=de')
    .then(response => response.json())
    .then(page => {
        console.log(page._routes);      // All locale routes
    });
```

**Menu Rendering**:
```javascript
// Fetch and render menu
fetch('/api/pages/menu/main-navigation')
    .then(response => response.json())
    .then(menu => {
        renderMenu(menu.links);
    });

function renderMenu(links) {
    const nav = document.createElement('nav');
    
    links.forEach(link => {
        const a = document.createElement('a');
        a.href = link.url;
        a.textContent = link.title;
        
        if (link.children) {
            // Handle sub-navigation
            const submenu = renderSubmenu(link.children);
            nav.appendChild(submenu);
        }
        
        nav.appendChild(a);
    });
    
    return nav;
}
```

## 🐛 Troubleshooting

### Common Issues

**❌ Page not found (404)**
- Check page `_state` is set to `1` (published)
- Verify route matches page slug and parent structure
- Ensure locale is enabled for the page
- Check custom page type resolution logic

**❌ Layout components not rendering**
- Verify Layout addon is enabled
- Check `allowedLayoutComponents` restriction
- Ensure component data is properly formatted
- Validate component field configurations

**❌ Collection page showing empty results**
- Verify collection name exists in Content addon
- Check filter and sort configuration
- Ensure content items have `_state: 1`
- Validate slug field exists and has values

**❌ Multi-language routes not working**
- Check locale is enabled in Pages settings
- Verify page has locale enabled in `_locales`
- Ensure locale-specific slugs are configured
- Check route generation for locale prefixes

### Debug Mode

Enable debug logging for route resolution:

```php
// Enable route debugging
$this->on('pages.resolve.path', function($route, &$page, $locale, $options) {
    error_log("Resolving route: {$route} for locale: {$locale}");
});
```

### Performance Optimization

1. **Route Caching**: Enable caching for route resolution
2. **Field Projection**: Use `fields` parameter to limit returned data
3. **Collection Limits**: Set reasonable limits for collection pages
4. **Locale Filtering**: Only enable needed locales per page
5. **Menu Caching**: Cache menu data for better performance

## 📄 License

This is a commercial addon for Cockpit CMS. See [LICENSE.md](LICENSE.md) for full terms.

## 🙏 Credits

Pages is developed by [Agentejo](https://agentejo.com) as part of the Cockpit CMS Pro ecosystem.

---

**Ready to build your website?** Set up your page hierarchy and start creating professional websites with powerful routing and multi-language support!