avris / esse
The essential CMS
Requires
- php: ^7.4|^8.0
- ext-gd: *
- ext-json: *
- avris/suml: ^0.3
- symfony/dependency-injection: ^4.3|^5.0
- symfony/string: ^5.0
README
A filesystem-based, git-oriented CMS focusing only on what is (deemed by me as) "essential".
Installation
composer require avris/esse
Enable the bundle in config/bundles.php
:
Avris\Esse\AvrisEsseBundle::class => ['all' => true],
config/routes.yaml
:
esse_image:
path: '/image/{filename}_{size}.{extension}'
requirements: { filename:'.+' }
controller: 'Avris\Esse\Controller\EsseController::image'
esse_file:
path: '/file/{filename}.{extension}'
requirements: { filename:'.+' }
controller: 'Avris\Esse\Controller\EsseController::file'
.gitignore
:
/public/image
(Optionally) overwrite default config in config/packages/avris_esse
:
avris_esse:
entriesDir: '%kernel.project_dir%/content/entries'
imagesDir: '%kernel.project_dir%/content/images'
filesDir: '%kernel.project_dir%/content/files'
imageSizes:
big: { maxwidth: 960 }
small: { maxwidth: 480 }
micro: { maxwidth: 36, maxheight: 36}
Usage
Basic concepts
Content is basically a set of entries (SUML files). They can be of different types:
block
- just a generic string or an arrayimage
file
- any custom type you want
They are put in the /content/entries
, /content/images
and /content/files
directories, respectively.
For instance, if you put the following content in /content/entries/about/skills.suml
:
content:
en:
programming:
php: 'PHP'
js: 'JavaScript'
soft:
teamwork: 'Teamwork'
pl:
soft:
teamwork: 'Praca zespoĊowa'
Then you can fetch it like this:
public function foo(Esse $esse)
{
return $this->render('home/foo.html.twig', [
'skills' => $esse->get('about/skills'),
]);
}
You can also fetch a specific field inside of the content using $esse->getPart('about/skills.soft.teamwork')
,
or in a template: {{ esse('about/skills.soft.teamwork') }}
. It will be automatically translated to the current request locale,
using %locale%
and then a generic "language" _
as a fallback.
You can optionally add any metadata you want
(including type
which defaults to block
, image
or file
depending on which directory the file is in,
and published
which defaults to false
):
type: `article`
createdAt: 2020-03-11 12:34:56
content: [] # ...
Those can be accessed with $entry->meta('createdAt')
.
Images
With an image put in /content/images/album.png
the following content of /content/images/album.suml
:
type: 'image' # optional
filename: 'album.png'
alt: 'My photo album'
source: 'https://example.com/album.png'
Esse will give you that image with $esse->getImage('album')
and under https://127.0.0.1:8000/image/album_sm.png
it will serve the sm
version of that image (as defined in config, under imageSizes
).
Files
With an file put in /content/files/foo.txt
the following content of /content/files/foo.suml
:
type: 'file' # optional
filename: 'foo.txt'
published: true # optional
title: 'The Foo file'
Esse will give you that file with $esse->getFile('foo')
and serve it under https://127.0.0.1:8000/file/file.txt
.
Modifiers
You can implement Avris\Esse\Interfaces\EsseModifier
to modify any entry before it gets served by Esse, for example:
<?php
namespace App\Article;
use App\Service\ArticleProcessor;
use Avris\Esse\Entity\Entry;
use Avris\Esse\Interfaces\EsseModifier;
final class ArticleModifier implements EsseModifier
{
private ArticleProcessor $articleProcessor;
public function __construct(ArticleProcessor $articleProcessor)
{
$this->articleProcessor = $articleProcessor;
}
public function modifyEntry(Entry $entry): ?Entry
{
if ($entry->type()->toString() !== 'article') {
return $entry;
}
if (!$entry->published() || $entry->meta('publishedAt') > new \DateTimeImmutable()) {
return null;
}
$data = $entry->allMeta();
$data['content'] = [];
foreach ($entry->versions() as $version) {
$data['content'][$version] = $this->articleProcessor->process($entry->content($version));
}
return $entry->with($data);
}
}
Indexes
You can implement Avris\Esse\Interfaces\EsseIndex
to create a cached index of entries, for example:
<?php
namespace App\Article;
use Avris\Esse\Interfaces\EsseIndex;
final class TagIndex implements EsseIndex
{
public function id(): string
{
return 'tag';
}
public function build(iterable $rawFiles): array
{
$index = [];
foreach ($rawFiles as $key => $data) {
if (($data['type'] ?? null) !== 'article') {
continue;
}
foreach ($data['content'] ?? [] as $version => $content) {
foreach ($content['tags'] ?? [] as $tag) {
$tag = mb_strtolower($tag);
if (!isset($index[$tag])) {
$index[$tag] = [];
}
$index[$tag][] = $key;
}
}
}
return $index;
}
}
Example usage:
/**
* @Route("/tag/{tag}")
*/
public function tag(string $tag, Esse $esse)
{
return $this->renderFormat('tag', [
'articles' => $esse->fromIndex('tag', mb_strtolower($tag)),
]);
}
Copyright
- Author: Andre Prusinowski (Avris.it)
- Licence: MIT