Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions formwork/config/routes/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Formwork\Http\ResponseStatus;
use Formwork\Router\Router;
use Formwork\Security\CsrfToken;
use Formwork\Utils\Arr;
use Formwork\Utils\FileSystem;
use Formwork\Utils\Str;

Expand All @@ -34,12 +35,18 @@
'path' => '/files/{name}/',
'action' => 'Formwork\Controllers\FilesController@file',
],
'tag.pagination' => [
'path' => '/{page:all}/tag/{tagName:slug}/page/{paginationPage:number}/',
'taxonomy.pagination' => [
'path' => '/{page:all}/{taxonomy}/{taxonomyTerm:slug}/page/{paginationPage:number}/',
'where' => [
'taxonomy' => fn($value, Site $site) => in_array($value, Arr::from($site->get('taxonomies')), true),
],
'action' => 'Formwork\Controllers\PageController@load',
],
'tag' => [
'path' => '/{page:all}/tag/{tagName:slug}/',
'taxonomy' => [
'path' => '/{page:all}/{taxonomy}/{taxonomyTerm:slug}/',
'where' => [
'taxonomy' => fn($value, Site $site) => in_array($value, Arr::from($site->get('taxonomies')), true),
],
'action' => 'Formwork\Controllers\PageController@load',
],
'page.pagination' => [
Expand Down
2 changes: 2 additions & 0 deletions formwork/config/site.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ statistics:
cleanup:
ttl: 86400
probability: 5

taxonomies: []
2 changes: 1 addition & 1 deletion formwork/src/Controllers/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function load(RouteParams $routeParams, Statistics $statistics): Response
return $this->getPageResponse($this->site->errorPage());
}

if ($routeParams->has('tagName') && !$page->scheme()->options()->get('allowTags', false)) {
if ($routeParams->has('taxonomy') && !$page->scheme()->options()->get('allowTaxonomy', false)) {
return $this->getPageResponse($this->site->errorPage());
}

Expand Down
29 changes: 27 additions & 2 deletions formwork/src/Pages/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ public function defaults(): array
'headers' => [],
'responseStatus' => 200,
'metadata' => [],
'taxonomy' => [],
'content' => '',
];

Expand Down Expand Up @@ -386,6 +387,30 @@ public function files(): FileCollection
return $this->files;
}

/**
* Get page taxonomy
*
* @return array<string, list<string>>
*/
public function taxonomy(): array
{
return $this->data['taxonomy'];
}

/**
* Set page taxonomy
*
* @param array<string, list<string>> $taxonomy
*/
public function setTaxonomy(array $taxonomy): void
{
if (!Arr::every($taxonomy, fn($terms, $taxonomyName) => is_string($taxonomyName)
&& is_array($terms) && Arr::every($terms, fn($term) => is_string($term)))) {
throw new InvalidValueException('Invalid taxonomy format');
}
Comment on lines +405 to +410

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method throws InvalidValueException but lacks a @throws annotation in its docblock. Add @throws InvalidValueException to document this behavior.

Copilot uses AI. Check for mistakes.
$this->data['taxonomy'] = $taxonomy;
}

/**
* Get page HTTP response status
*/
Expand Down Expand Up @@ -733,11 +758,11 @@ public function save(?string $language = null): void
|| in_array($field->name(), self::IGNORED_FIELD_NAMES, true)
|| in_array($field->type(), self::IGNORED_FIELD_TYPES, true)
) {
unset($frontmatter[$field->name()]);
Arr::remove($frontmatter, $field->name());
continue;
}

$frontmatter[$field->name()] = $field->value();
Arr::set($frontmatter, $field->name(), $field->value());
}

$content = str_replace("\r\n", "\n", $this->data['content']);
Expand Down
23 changes: 23 additions & 0 deletions formwork/src/Pages/PageCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Formwork\Cms\Site;
use Formwork\Data\AbstractCollection;
use Formwork\Data\Contracts\Paginable;
use Formwork\Utils\Arr;
use Formwork\Utils\Str;
use RuntimeException;

Expand Down Expand Up @@ -82,6 +83,28 @@ public function allowingChildren(): static
return $this->filterBy('allowChildren');
}

/**
* Get all the pages in the collection having the specified taxonomy terms
*
* @param array<string, list<string>> $taxonomy Taxonomy terms to filter by
* @param bool $slug Whether the provided terms are slugs
*/
public function havingTaxonomy(array $taxonomy, bool $slug = false): static
{
return $this->filter(function (Page $page) use ($taxonomy, $slug): bool {
foreach ($taxonomy as $taxonomyName => $terms) {
$pageTerms = $page->taxonomy()[$taxonomyName] ?? [];
if ($slug) {
$pageTerms = Arr::map($pageTerms, fn($term) => Str::slug($term));
Comment thread
giuscris marked this conversation as resolved.
}
if (array_intersect($terms, $pageTerms) === []) {
Comment thread
giuscris marked this conversation as resolved.
return false;
}
}
return true;
});
}

/**
* Search pages in the collection
*
Expand Down
29 changes: 29 additions & 0 deletions formwork/src/Router/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Formwork\Router;

use Closure;
use InvalidArgumentException;

class Route
Expand Down Expand Up @@ -41,6 +42,13 @@ class Route
*/
protected array $types = self::DEFAULT_TYPES;

/**
* Route parameter constraints
*
* @var array<string, array<mixed>|Closure>
*/
protected array $constraints = [];

/**
* Route prefix
*/
Expand Down Expand Up @@ -148,4 +156,25 @@ public function getPrefix(): string
{
return $this->prefix;
}

/**
* Set route parameter constraint
*
* @param array<mixed>|Closure $constraint
*/
public function where(string $parameter, Closure|array $constraint): self
{
$this->constraints[$parameter] = $constraint;
return $this;
}

/**
* Get route parameter constraints
*
* @return array<string, array<mixed>|Closure>
*/
public function getConstraints(): array
{
return $this->constraints;
}
}
22 changes: 22 additions & 0 deletions formwork/src/Router/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ public function dispatch(): Response
}
}

/**
* @var Route $route
*/
foreach ($this->routes as $route) {
if (!$this->matchRoute($route)) {
continue;
Expand All @@ -211,6 +214,18 @@ public function dispatch(): Response

$this->params = $this->buildParams($compiledRoute->params(), $matches);

// Check route constraints
foreach ($route->getConstraints() as $param => $constraint) {
$value = $this->params->get($param);
if ($constraint instanceof Closure) {
if (!$this->container->call($constraint, ['value' => $value])) {
continue 2;
}
} elseif (!in_array($value, $constraint, true)) {
continue 2;
}
}

$this->container->define(RouteParams::class, $this->params);

$routeCallback = $this->parseAction($route->getAction());
Expand Down Expand Up @@ -285,6 +300,13 @@ public function loadFromFile(string $path, ?string $prefix = null): void
foreach ($data['routes'] as $routeName => $route) {
$r = $this->addRoute($routeName, $route['path'])
->action($route['action']);

if (isset($route['where'])) {
foreach ($route['where'] as $param => $constraint) {
$r->where($param, $constraint);
}
}

$setProps($r, $route);
}
}
Expand Down
5 changes: 5 additions & 0 deletions formwork/src/Schemes/SchemeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Formwork\Schemes;

use Formwork\Services\Container;
use Formwork\Utils\Str;

final class SchemeFactory
{
Expand All @@ -17,6 +18,10 @@ public function __construct(
*/
public function make(string $id, array $data = []): Scheme
{
if (Str::startsWith($id, 'pages.') && isset($data['options']['allowTags'])) {
trigger_error('The Scheme option "allowTags" is deprecated since Formwork 2.2.0, use "allowTaxonomy"', E_USER_DEPRECATED);
$data['options']['allowTaxonomy'] = $data['options']['allowTags'];
}
return $this->container->build(Scheme::class, compact('id', 'data'));
}
}
3 changes: 3 additions & 0 deletions formwork/translations/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Aktiviert
site.maintenance.page: Wartungsseite
site.pages: Seiten
site.pages.defaultTemplate: Standardvorlage
site.pages.taxonomies: Taxonomien
site.pages.taxonomies.description: Taxonomien zur Organisation von Seiten, zu verwenden in Template-Controllern zum Filtern von Seiten
site.pages.taxonomies.noTaxonomies: Keine Taxonomien
site.statistics: Statistiken
site.statistics.enabled: Seitenbesuche verfolgen
site.statistics.enabled.disabled: Deaktiviert
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Enabled
site.maintenance.page: Maintenance page
site.pages: Pages
site.pages.defaultTemplate: Default template
site.pages.taxonomies: Taxonomies
site.pages.taxonomies.description: Taxonomies for organizing pages, to be used in template controllers to filter pages
site.pages.taxonomies.noTaxonomies: No taxonomies
site.statistics: Statistics
site.statistics.enabled: Track page visits
site.statistics.enabled.disabled: Disabled
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Activado
site.maintenance.page: Página de mantenimiento
site.pages: Páginas
site.pages.defaultTemplate: Plantilla predeterminada
site.pages.taxonomies: Taxonomías
site.pages.taxonomies.description: Taxonomías para organizar páginas, a usar en los controladores de plantillas para filtrar páginas
site.pages.taxonomies.noTaxonomies: Sin taxonomías
site.statistics: Estadísticas
site.statistics.enabled: Rastrear visitas a la página
site.statistics.enabled.disabled: Desactivado
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Activé
site.maintenance.page: Page de maintenance
site.pages: Pages
site.pages.defaultTemplate: Modèle (template) par défaut
site.pages.taxonomies: Taxonomies
site.pages.taxonomies.description: Taxonomies pour organiser les pages, à utiliser dans les contrôleurs de modèles pour filtrer les pages
site.pages.taxonomies.noTaxonomies: Aucune taxonomie
site.statistics: Statistiques
site.statistics.enabled: Suivre les visites de la page
site.statistics.enabled.disabled: Désactivé
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/it.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Attivata
site.maintenance.page: Pagina manutenzione
site.pages: Pagine
site.pages.defaultTemplate: Template predefinito
site.pages.taxonomies: Tassonomie
site.pages.taxonomies.description: Tassonomie per organizzare le pagine, da usare nei controller dei template per filtrare le pagine
site.pages.taxonomies.noTaxonomies: Nessuna tassonomia
site.statistics: Statistiche
site.statistics.enabled: Traccia visite alle pagine
site.statistics.enabled.disabled: Disabilitato
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/nl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Ingeschakeld
site.maintenance.page: Onderhoudspagina
site.pages: Pagina’s
site.pages.defaultTemplate: Standaardsjabloon
site.pages.taxonomies: Taxonomieën
site.pages.taxonomies.description: Taxonomieën voor het organiseren van pagina’s, te gebruiken in templatecontrollers om pagina’s te filteren
site.pages.taxonomies.noTaxonomies: Geen taxonomieën
site.statistics: Statistieken
site.statistics.enabled: Paginaweergaven bijhouden
site.statistics.enabled.disabled: Uitgeschakeld
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/pl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Włączony
site.maintenance.page: Strona konserwacyjna
site.pages: Strony
site.pages.defaultTemplate: Domyślny szablon
site.pages.taxonomies: Taksonomie
site.pages.taxonomies.description: Taksonomie do organizowania stron, używane w kontrolerach szablonów do filtrowania stron
site.pages.taxonomies.noTaxonomies: Brak taksonomii
site.statistics: Statystyki
site.statistics.enabled: Śledzenie wizyt na stronie
site.statistics.enabled.disabled: Wyłączone
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/pt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Ativado
site.maintenance.page: Página de manutenção
site.pages: Páginas
site.pages.defaultTemplate: Template padrão
site.pages.taxonomies: Taxonomias
site.pages.taxonomies.description: Taxonomias para organizar páginas, usadas nos controladores de templates para filtrar páginas
site.pages.taxonomies.noTaxonomies: Sem taxonomias
site.statistics: Estatísticas
site.statistics.enabled: Rastrear visitas à página
site.statistics.enabled.disabled: Desativado
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/ro.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Activat
site.maintenance.page: Pagină de mentenanță
site.pages: Pagini
site.pages.defaultTemplate: Șablon implicit
site.pages.taxonomies: Taxonomii
site.pages.taxonomies.description: Taxonomii pentru organizarea paginilor, folosite în controllerele șabloanelor pentru a filtra pagini
site.pages.taxonomies.noTaxonomies: Nicio taxonomie
site.statistics: Statistici
site.statistics.enabled: Urmărește vizitele paginilor
site.statistics.enabled.disabled: Dezactivat
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Включено
site.maintenance.page: Страница обслуживания
site.pages: Страницы
site.pages.defaultTemplate: Шаблон по умолчанию
site.pages.taxonomies: Таксономии
site.pages.taxonomies.description: Таксономии для организации страниц, используются в контроллерах шаблонов для фильтрации страниц
site.pages.taxonomies.noTaxonomies: Нет таксономий
site.statistics: Статистика
site.statistics.enabled: Отслеживать посещения страницы
site.statistics.enabled.disabled: Отключено
Expand Down
3 changes: 3 additions & 0 deletions formwork/translations/uk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ site.maintenance.enabled.enabled: Увімкнено
site.maintenance.page: Сторінка обслуговування
site.pages: Сторінки
site.pages.defaultTemplate: Шаблон за замовчуванням
site.pages.taxonomies: Таксономії
site.pages.taxonomies.description: Таксономії для впорядкування сторінок, використовуються в контролерах шаблонів для фільтрації сторінок
site.pages.taxonomies.noTaxonomies: Немає таксономій
site.statistics: Статистика
site.statistics.enabled: Відстежувати відвідування сторінки
site.statistics.enabled.disabled: Вимкнено
Expand Down
2 changes: 2 additions & 0 deletions site/config/site.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ title: Formwork
languages:
available:
- en
taxonomies:
- tag
7 changes: 4 additions & 3 deletions site/pages/2-blog/20180615-hello-world/post.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ summary: |-

This simple phrase has become a time-honored tradition in programming. It’s the output of the very first program most developers write when learning a new language, a framework, or — like now — setting up a new site.
coverImage: nasa-vhsz50aafas-unsplash.jpg
tags:
- ideas
- 'getting started'
taxonomy:
tag:
- ideas
- 'getting started'
---
## Why "Hello World"?

Expand Down
11 changes: 6 additions & 5 deletions site/pages/2-blog/20220624-another-blog-post/post.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ title: 'Coffee, Mornings, and Ideas'
publishDate: '2022-06-24 22:13:00'
summary: 'There’s something magical about the first sip of coffee in the morning. The day is still quiet, thoughts begin to form, and the smell alone seems to awaken possibility.'
coverImage: vruyr-martirosyan-0n632k-mow4-unsplash.jpg
tags:
- 'morning routine'
- ideas
- lifestyle
- productivity
taxonomy:
tag:
- 'morning routine'
- ideas
- lifestyle
- productivity
---
## Little rituals

Expand Down
Loading