PHPackages                             c975l/site-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. c975l/site-bundle

ActiveSymfony-bundle[Framework](/categories/framework)

c975l/site-bundle
=================

Groups common files and settings to create a website

v6.26(3w ago)11.5k[1 issues](https://github.com/975L/SiteBundle/issues)6MITTwigPHP &gt;=8.0CI failing

Since Mar 4Pushed 2w ago1 watchersCompare

[ Source](https://github.com/975L/SiteBundle)[ Packagist](https://packagist.org/packages/c975l/site-bundle)[ Docs](https://github.com/975L/SiteBundle)[ Fund](https://buymeacoff.ee/laurentmarquet)[ Fund](https://opencollective.com/laurent-marquet)[ RSS](/packages/c975l-site-bundle/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (19)Versions (216)Used By (6)

SiteBundle
==========

[](#sitebundle)

Symfony bundle that provides a complete foundation for building websites — layout, pages, SEO, admin, sitemap, legal templates, and more.

[![GitHub](https://camo.githubusercontent.com/1fd6d0358d02751eb75047ee036c4d4d6d32441db662313e7db2a7fc62fb8d7c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f3937354c2f5369746542756e646c65)](https://github.com/975L/SiteBundle/blob/master/LICENSE)[![Packagist Version](https://camo.githubusercontent.com/1b317a10703197226e1b0663a9fb276d744aa122814ad633f9819d21100c1e98/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f633937356c2f736974652d62756e646c65)](https://packagist.org/packages/c975l/site-bundle)[![PHP Version](https://camo.githubusercontent.com/a61e2a3da535f35a6aca8addd6a51c2749c3815c0289218a9df3cc4f38786b05/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f633937356c2f736974652d62756e646c65)](https://packagist.org/packages/c975l/site-bundle)

---

Features
--------

[](#features)

- **Base layout** with SEO-optimized meta tags (OpenGraph, robots, canonical, favicon, Apple touch icon)
- **Page display** from Twig templates (file-based) or from the database via the `Page` entity
- **Page redirects** and 410 Gone handling
- **Admin CRUD** for database pages via EasyAdmin
- **Sitemap generation** from both filesystem templates and database pages
- **Error page templates** for 401, 403, 404, 410, and 500
- **Legal model templates** for France (French): cookies, copyright, legal notice, privacy policy, terms of sales, terms of use
- **Matomo analytics** integration
- **CookieConsent** integration
- **Alternate language** hreflang meta tags
- **Open Graph** image support
- **Email templates** with CSS inlining
- **Asset serving** controller (inline display, access-protected)
- **File download** controller (forced download, access-protected)
- **Twig extensions**: `route_exists`, `template_exists`, `asset_exists`, `nl2br`
- **CSS animations** stylesheet
- **File lists**: `extensions.txt` and `bots.txt`

---

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- [c975L/ConfigBundle](https://github.com/975L/ConfigBundle)
- [c975L/UiBundle](https://github.com/975L/UiBundle)
- Doctrine ORM
- EasyAdmin
- symfony/ux-twig-component
- twig/cssinliner-extra

---

Installation
------------

[](#installation)

### Download

[](#download)

```
composer require c975l/site-bundle
```

### Load configuration values

[](#load-configuration-values)

This bundle uses [c975L/ConfigBundle](https://github.com/975L/ConfigBundle) to manage its settings. Load the default configuration keys into the database:

```
php bin/console c975l:config:load-all
```

Then open the ConfigBundle dashboard to set values for the keys

### Enable routes

[](#enable-routes)

Add the bundle routes to `config/routes.yaml`:

```
c975_l_site:
    resource: "@c975LSiteBundle/Controller/"
    type: attribute
    prefix: /
    # For multilingual websites:
    # prefix: /{_locale}
    # defaults:
    #     _locale: '%locale%'
    # requirements:
    #     _locale: en|fr|es
```

### Install assets

[](#install-assets)

```
php bin/console assets:install --symlink
```

### Register Stimulus controllers

[](#register-stimulus-controllers)

This bundle ships Stimulus controllers (basic, matomo, cookieConsent). They are exposed via AssetMapper under the `@c975l/site-bundle` namespace.

**Add one entry to `importmap.php`** (one-time, at installation):

```
'@c975l/site-bundle/controllers.js' => [
    'path' => './vendor/c975l/site-bundle/assets/controllers.js',
],
```

**Add two lines to `assets/bootstrap.js`** (or `assets/stimulus_bootstrap.js`):

```
import { startStimulusApp } from '@symfony/stimulus-bundle';
import { register as registerc975lSite } from '@c975l/site-bundle/controllers.js';

const app = startStimulusApp();
registerc975lSite(app);
```

After that, all controllers are loaded with hashed filenames (cache busting). Adding or removing controllers in a future bundle update requires no change in your app.

---

Usage
-----

[](#usage)

### Creating your layout

[](#creating-your-layout)

Create `templates/layout.html.twig` in your project and extend the bundle's layout:

```
{% extends '@c975LSite/layout.html.twig' %}
```

### Page-specific variables

[](#page-specific-variables)

Declare these variables in each page template to populate meta tags and the page title:

```
{% set title = 'My Page Title' %}
{% set description = 'A short description of this page.' %}
```

### Template blocks

[](#template-blocks)

The layout exposes the following Twig blocks for you to override or extend:

BlockDescription`head`Entire `` element`meta`Meta tags (charset, viewport, robots, og:\*, etc.)`stylesheets`CSS links`preconnect``` hints`body`Entire `` element`header`Site header`navigation`Main navigation`main`Main content wrapper`title`Page `` title`flashes`Flash messages`container`Container div wrapping `content``content`Page-specific content`share`Sharing widgets`navigationBottom`Bottom navigation`footer`Site footer`javascripts`JavaScript includes**Override a block:**

```
{% block share %}
    {{ parent() }}
    {# your additional content #}
{% endblock %}
```

**Disable a block:**

```
{% block share %}{% endblock %}
```

### Display mode

[](#display-mode)

Use the `display` variable to conditionally include templates (defaults to `html`):

```
{% if display == 'pdf' %}
    {% include 'header-pdf.html.twig' %}
{% else %}
    {% include 'header.html.twig' %}
{% endif %}
```

---

Pages
-----

[](#pages)

### File-based pages

[](#file-based-pages)

Place Twig templates in `templates/pages/`. They are served at `/pages/{slug}` via the `page_display` route.

To **hint the sitemap generator**, add metadata in a Twig comment at the top of the file:

```
{# changeFrequency="monthly" priority="8" #}
```

### Redirects and deleted pages

[](#redirects-and-deleted-pages)

LocationEffect`templates/pages/redirected/{slug}.html.twig`Redirects to the slug written inside the file`templates/pages/deleted/{slug}.html.twig`Throws a 410 Gone exception### Database pages

[](#database-pages)

Use the `Page` entity to manage pages through the database. Each page supports:

- Title, slug (unique), description
- Published status and display position
- Sitemap fields: change frequency and priority (0–10)
- Blocks (content blocks from [c975L/UiBundle](https://github.com/975L/UiBundle))
- Creation / modification timestamps and author reference

Database pages are rendered with the bundle's `@c975LSite/pages/page.html.twig` template, which displays the page title, description, and its associated blocks.

### Admin management

[](#admin-management)

Pages are managed in the EasyAdmin dashboard via `PageCrudController`. The menu entry is registered automatically through `MenuProvider`. Access is controlled by the `site-role-needed` key in ConfigBundle.

---

SEO
---

[](#seo)

### Sitemap generation

[](#sitemap-generation)

Run the following command to generate `public/sitemap-pages.xml`:

```
php bin/console site:sitemaps:create
```

The command aggregates URLs from:

1. Twig files in `templates/pages/` (reads `changeFrequency` and `priority` from comments)
2. Published database pages (uses their `changeFrequency` and `priority` fields)

A sitemap index template is also available at `@c975LSite/sitemap-index.xml.twig`.

### Alternate languages (hreflang)

[](#alternate-languages-hreflang)

Define `languagesAlt` to add `` tags and enable a language switcher navbar component:

```
{% set languagesAlt = {
    en: { title: 'English' },
    fr: { title: 'Français' },
    es: { title: 'Español' }
} %}
```

URLs are built as `https://example.com/{locale}/pages/{slug}`.

### Open Graph image

[](#open-graph-image)

Set a per-page OG image:

```
{% set ogImage = absolute_url(asset('images/my-og-image.jpg')) %}
```

---

General components
------------------

[](#general-components)

All components below read their data from ConfigBundle. No props are needed — just include the tag and set the corresponding keys via the ConfigBundle dashboard.

### Matomo

[](#matomo)

Set `site-matomo-url` and `site-matomo-id` in ConfigBundle, then place the component wherever you want the tracking snippet (typically just before ``):

```

```

The component renders nothing if either config value is missing.

### CookieConsent

[](#cookieconsent)

Set `url-cookies-policy` in ConfigBundle (optional — links the banner to your cookies page), then place the component in your layout:

```

```

The `message`, `dismiss`, and `link` texts are loaded from the `site` translation domain.

### HostedBy / MadeBy

[](#hostedby--madeby)

Set `site-hosted-by-url` + `site-hosted-by-logo` and/or `site-made-by-url` + `site-made-by-logo` in ConfigBundle, then include the components (typically in the footer):

```

```

Each component renders nothing if either its URL or logo config value is missing.

---

Error templates
---------------

[](#error-templates)

Pre-built error templates are available for: `error`, `error401`, `error403`, `error404`, `error410`, and `error500`.

Follow the Symfony guide on [customizing error pages](https://symfony.com/doc/current/controller/error_pages.html), then include the bundle templates in your own error files:

```
{% extends 'layout.html.twig' %}

{% block content %}
    {% include '@c975LSite/Exception/error404.html.twig' %}
{% endblock %}

{% block share %}{% endblock %}
```

---

Legal models
------------

[](#legal-models)

Pre-built legal templates are available for **France** in **French** (`fr`). Available models:

ModelPathCookies policy`@c975LSite/models/france/fr/cookies.html.twig`Copyright`@c975LSite/models/france/fr/copyright.html.twig`Legal notice`@c975LSite/models/france/fr/legal-notice.html.twig`Privacy policy`@c975LSite/models/france/fr/privacy-policy.html.twig`Terms of sales`@c975LSite/models/france/fr/terms-of-sales.html.twig`Terms of use`@c975LSite/models/france/fr/terms-of-use.html.twig`Each model is also available in Markdown format (`.md`).

**Feel free to contribute translations or add templates for other countries.**

### Include the whole model

[](#include-the-whole-model)

```
{% extends 'layout.html.twig' %}

{% trans_default_domain 'site' %}
{% set title = 'label.terms_of_sales'|trans %}

{% block content %}
    {% set latestUpdate = '2024-01-01' %}
    {% include '@c975LSite/models/france/fr/terms-of-sales.html.twig' %}
{% endblock %}
```

### Select specific blocks (embed)

[](#select-specific-blocks-embed)

```
{% extends 'layout.html.twig' %}

{% trans_default_domain 'site' %}
{% set title = 'label.terms_of_sales'|trans %}

{% block content %}
    {% set latestUpdate = '2024-01-01' %}
    {% embed '@c975LSite/models/france/fr/terms-of-sales.html.twig' %}
        {# Disable a block #}
        {% block acceptation %}{% endblock %}

        {# Or extend a block #}
        {% block acceptation %}
            {{ parent() }}
            Additional content here.
        {% endblock %}
    {% endembed %}
{% endblock %}
```

---

Asset and Download controllers
------------------------------

[](#asset-and-download-controllers)

### AssetController

[](#assetcontroller)

Serves a file inline (e.g., images, PDFs). Useful for serving files only to authenticated users.

```
{{ path('asset_file', { file: 'path/to/your_file.pdf' }) }}
```

To restrict access, add an entry to `config/packages/security.yaml`:

```
access_control:
    - { path: ^/asset/protected/, roles: ROLE_USER }
```

### DownloadController

[](#downloadcontroller)

Forces a file download.

```
{{ path('download_file', { file: 'path/to/your_file.csv' }) }}
```

File names may contain letters (including accented), digits, `-`, `_`, `/`, and up to two extensions. Spaces are not allowed.

---

Twig extensions
---------------

[](#twig-extensions)

Function / FilterDescription`route_exists('route_name')`Returns `true` if the named route exists`template_exists('template.html.twig')`Returns `true` if the template file exists`asset_exists('path/to/file')`Returns `true` if the asset exists in `public/` or `assets/``|nl2br`Applies PHP's `nl2br()` with HTML output safe---

Email templates
---------------

[](#email-templates)

Pre-built email templates are available at `@c975LSite/emails/`:

TemplateDescription`layout.html.twig`Base email layout`fullLayout.html.twig`Full email layout`footer.html.twig`Email footerCSS is inlined automatically via `twig/cssinliner-extra`. Minified stylesheets (`emails.min.css`, `styles.min.css`, `animations.min.css`) are embedded.

---

CSS animations
--------------

[](#css-animations)

Link the animations stylesheet to use scroll-triggered CSS animations:

```

```

---

Commands
--------

[](#commands)

CommandDescription`php bin/console site:sitemaps:create`Generates `public/sitemap-pages.xml` from filesystem and database pages`php bin/console site:backup`Backs up the database and `public/` files (replaces `BackupServer.sh`)`php bin/console models:twig2md`Converts Twig model templates to their Markdown equivalent---

Scheduler
---------

[](#scheduler)

The bundle provides `site:sitemaps:create` and `site:backup` as schedulable commands. The schedule itself is defined in your app so each project controls its own timing.

### 1. Create the schedule class

[](#1-create-the-schedule-class)

```
// src/Scheduler/SiteSchedule.php
namespace App\Scheduler;

use Symfony\Component\Console\Messenger\RunCommandMessage;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;
use Symfony\Contracts\Cache\CacheInterface;

#[AsSchedule('site')]
class MaintenanceSchedule implements ScheduleProviderInterface
{
    public function __construct(
        private readonly CacheInterface $cache,
    ) {}

    public function getSchedule(): Schedule
    {
        return (new Schedule())
            ->stateful($this->cache)
            // Sitemap: daily at 00:05
            ->add(RecurringMessage::cron('5 0 * * *', new RunCommandMessage('site:sitemaps:create')))
            // Partial backup: every 6 hours (DB regular tables + modified files only)
            ->add(RecurringMessage::cron('7 */6 * * *', new RunCommandMessage('site:backup')))
            // Full backup + report: every Monday at 03:07 (archive tables + whole DB + all user files)
            ->add(RecurringMessage::cron('7 3 * * 1', new RunCommandMessage('site:backup --full --report')));
    }
}
```

The `stateful()` call persists the last-run time via Symfony Cache so tasks are not re-run if the worker restarts.

### 2. Start the worker

[](#2-start-the-worker)

Run the consumer as a long-lived process (supervised by Supervisor or systemd):

```
php bin/console messenger:consume scheduler_site
```

You may keep a cron entry that restarts the worker daily (e.g., at 00:25) to recover from crashes without monitoring the process continuously:

```
25 0 * * * systemctl --user start messenger-worker@your-site.service
```

---

Lists
-----

[](#lists)

Two plain-text lists are available for validation purposes:

```
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

$extensions = file(
    $this->parameterBag->get('kernel.project_dir') . '/../vendor/c975l/site-bundle/Lists/extensions.txt',
    FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
);

$bots = file(
    $this->parameterBag->get('kernel.project_dir') . '/../vendor/c975l/site-bundle/Lists/bots.txt',
    FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
);
```

---

Full layout example
-------------------

[](#full-layout-example)

```
{% extends '@c975LSite/layout.html.twig' %}

{% set languagesAlt = {
    en: { title: 'English' },
    fr: { title: 'Français' },
    es: { title: 'Español' }
} %}

{% block meta %}
    {{ parent() }}

{% endblock %}

{% block stylesheets %}
    {{ parent() }}
{% endblock %}

{% block navigation %}
    {{ include('navbar.html.twig') }}
{% endblock %}

{% block title %}
    {% if app.request.get('_route') is not null %}
        {{ title }}
    {% endif %}
{% endblock %}

{% block container %}

        {% block content %}{% endblock %}

{% endblock %}

{% block share %}
    {# your sharing widget #}
{% endblock %}

{% block footer %}
    {{ include('footer.html.twig') }}

{% endblock %}

{% block javascripts %}
    {{ parent() }}

{% endblock %}
```

---

If this project **helps you save development time**, consider sponsoring via the **Sponsor** button at the top of the GitHub page. Thank you!

###  Health Score

54

—

FairBetter than 97% of packages

Maintenance76

Regular maintenance activity

Popularity21

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity88

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~14 days

Total

214

Last Release

23d ago

Major Versions

1.x-dev → v3.02021-09-20

v3.2 → v4.02022-07-25

v4.0.2 → v5.02023-12-04

4.x-dev → v6.02024-01-16

5.x-dev → v6.0.12024-01-22

PHP version history (5 changes)v1.0PHP &gt;=5.5.9

v2.0.1PHP ^7

v2.5PHP \*

v4.0PHP ^8

v6.0.1PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/5679e828a48e37afabd92da60ab8d78bf65a3bedc0f618ef3fddf92082840f52?d=identicon)[Laurent3170](/maintainers/Laurent3170)

---

Top Contributors

[![LaurentMarquet](https://avatars.githubusercontent.com/u/16478286?v=4)](https://github.com/LaurentMarquet "LaurentMarquet (223 commits)")

---

Tags

legal-noticelegal-textssymfonysymfony-bundlewebsitesymfonybundlewebsite

### Embed Badge

![Health badge](/badges/c975l-site-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/c975l-site-bundle/health.svg)](https://phpackages.com/packages/c975l-site-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.5M378](/packages/easycorp-easyadmin-bundle)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1615.6k12](/packages/2lenet-crudit-bundle)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M196](/packages/sulu-sulu)[forumify/forumify-platform

122.0k12](/packages/forumify-forumify-platform)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1175.2k](/packages/rcsofttech-audit-trail-bundle)[kimai/kimai

Kimai - Time Tracking

4.8k8.7k1](/packages/kimai-kimai)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
