// BLOG

Kirby CMS - Les pages virtuelles

Dans un site web Kirby, tout le contenu provient généralement des pages créés dans l'application. Cependant, parfois, il peut être intéressant de concevoir des pages avec des données provenant d'une source autre que les pages Kirby. On parle alors de pages virtuelles.

Rédigé le 19.05.2022
Par Gilles Vauvarin

Virtual Page Kirby CMS

Qu'est ce qu'une page virtuelle ?

Depuis la version 3 de kirby, il est possible de combiner les pages kirby présentent dans le système de fichiers de votre projet avec des pages dont les sources de données proviennent de l'extérieur (Base de données, API externe, RSS feed, fichiers CSV/JSON/XML/XLS/MD ... ). Il est même possible de combiner dans une seule page virtuelle des données provenant de plusieurs sources externes. Ces pages dont le contenu n'est pas présent physiquement dans les fichiers textes du dossier /content sont appelées "Virtual Page".

Elles sont intégrées dans Kirby de façon transparente et bénéficieront de son système de routing, seront éditables depuis le panel et seront requêtables depuis l'API native de Kirby.

Comment créer une page virtuelle ?

La création d'une page virtuelle dans Kirby passe par l'utilisation de la méthode Page::factory() qui permet de construire un objet Page avec tout son système de page model.

Voyons un exemple concret :

<?php

// /site/config/config.php

return [
    'routes' => [
        [
          'pattern' => 'my-virtual-page',
          'action'  => function () {
                return Page::factory(
                    [
                    'slug' => 'my-virtual-page',
                    'template' => 'my-virtual-page',
                    'content' => [
                        'title' => 'Voici une page virtuelle',
                        'description'  => 'Lorem ipsum ...'
                    ]
                ]);
          }
        ]
    ]
];

Ici, je passe par une "route" custom que j'enregistre dans le fichier de configuration de Kirby /site/config/config.php car la page n'existe pas en tant que tel via un fichier .txt dans le dossier /content J'utilise donc cette route pour indiquer à Kirby quelle URL il doit utiliser pour afficher cette page virtuelle et quel template il doit utiliser pour afficher les données. Les données proviennent ici directement du code que j'écris dans la fonction anonyme passée en valeur de la clé 'actions'.

Quelques explications sur le code ci-dessus :

La méthode factory() prend en paramètre un tableau contenant les données qui serviront à la création de la page.

Ici notre page virtuelle sera accessible depuis l'URL : https://my-website.com/my-virtual-page

Elle sera affichée à l'aide d'un template /site/templates/my-virtual-page.php qu'il faudra créer.

La page dispose d'un titre et d'une description qu'il est possible d'afficher dans le template.

Voici à quoi pourrait ressembler le template /site/templates/my-virtual-page.php

<?php snippet('header') ?>

    <h1><?= $page->title() ?></h1>

    <div>
      <?= $page->description()->kt() ?>
    </div>

<?php snippet('footer') ?>

Cet exemple n'est pas très réaliste car les données sont transmises en dur directement dans le tableau de données passé en paramètre de la méthode factory() Cependant il a le mérite de montrer très simplement le principe de la création d'une page virtuelle.

Nous allons voir maintenant un exemple plus réaliste qui consiste à afficher dans une page virtuelle des données provenant d'une API externe.

Créer une page virtuelle affichant les données d'une API externe

Je vais utiliser pour cet exemple une API publique accessible sans authentification : https://github.com/Hipo/university-domains-list Elle va me permettre de récupérer des noms d'universités ainsi que l'URL de leur site web.

La route custom

J'utilise, comme dans l'exemple précédent, une route custom que j'enregistre dans le fichier de configuration de Kirby /site/config/config.php

<?php


// /site/config/config.php

return [
    'debug' => true,
    'routes' => [
        [
          'pattern' => 'my-virtual-page',
          'action'  => function () {
                $results = [];
                $page   = [];
                $unis = [];
                $request = Remote::get('http://universities.hipolabs.com/search?name=middle');
                if ( $request->code() === 200 ) :
                    $results = $request->json(false);
                endif;


                foreach ( $results as $uni ) :
                    $unis[] = [
                        'name' => $uni->name,
                        'uni_url' => $uni->web_pages[0],
                    ];
                endforeach;

                $page = [
                        'slug'     => 'my-virtual-page',
                        'template' => 'my-virtual-page',
                        'content' => [
                            'unis' => $unis
                        ]
                    ];

                return Page::factory( $page );
          }
        ]
    ]
];

Quelques explications sur le code ci-dessus :

La méthode Remote::get fournit par Kirby me permet de requêter et récupérer les données de l'API.

Si la requête me renvoie un code "200" (signe que tout c'est bien passé) je lui applique la méthode json() avec le paramètre false pour décoder la requête en tant qu'objet. (par défaut, cette méthode décode la réponse en tant que tableau).

Je boucle sur l'objet en question pour construire un tableau qui contiendra le nom de l'université et l'URL de son site web (données fournient par l'API).

Puis je passe ce tableau en tant que contenu dans le tableau $page qui sera transmis comme paramètre de la méthode factory()

Le template

Je peux maintenant récupérer toutes ces données dans un template /site/templates/my-virtual-page.php pour les afficher :

<?php snippet('header') ?>

    <h1><?= $page->title() ?></h1>

    <div>
      <?php
      foreach ( $page->unis()->value() as $uni ) :
      ?>

        <h2><?= $uni['name'] ?></h2>
        <a href="<?= $uni['uni_url'] ?>"><?= $uni['uni_url'] ?></a>

      <?php
      endforeach;
      ?>
    </div>

<?php snippet('footer') ?>

Afficher les données d'une API externe dans plusieurs pages virtuelles

Voyons maintenant comment lister sur une page parente les universités avec un lien qui renvoie vers une page virtuelle enfant affichant le détail de l'université cliquée.

Pour cela, nous n'allons plus utiliser une route custom mais une page model.

La page parente

Commençons par créer la page parente universities.txt dans le dossier /content/universities/universities.txt

/content/universities/universities.txt

Title: Liste d'universités
----
Description: Lorem ipsum ...

La page parente universities listera les universités récupérées de l'API et servira de model pour les pages enfants university qui elles afficheront le détail d'une université.

La page model

Nous allons étendre la méthode children() de la page parente universities à l'aide d'une page model /site/models/universities.php

<?php

// /site/models/universities.php

class UniversitiesPage extends Page
{
    public function children()
    {
        $results = [];
        $pages   = [];

        $request = Remote::get('http://universities.hipolabs.com/search?name=middle');
        if ( $request->code() === 200 ) :
           $results = $request->json(false);
        endif;


        foreach ( $results as $uni ) :
             $pages[] = [
                        'slug'     => Str::slug( $uni->domains[0] ),
                        'template' => 'university',
                        'model'    => 'university',
                        'content' => [
                            'name' => $uni->name,
                            'uni_url' => $uni->web_pages[0],
                            'details' => $uni->country,
                        ]
                    
             ];
        endforeach;

         return Pages::factory( $pages, $this );
    }
}

Cette fois-ci nous construisons un tableau multi-dimentionnel $pages qui contient le détail de chaque université. Ce tableau multi-dimentionnel $pages est passé en paramètre de la méthode factory()

Vous noterez que j'appelle ici la méthode factory() sur un objet Pages et non plus Page car je souhaite renvoyer une collection de pages Kirby et non plus un objet Page.

Nous définissons un slug, un template et un contenu pour chaque université car le détail de chacune d'elle sera affiché dans une page virtuelle enfant.

Remarque :

Nous pourrions aussi définir une page model /site/models/university.php pour nos pages virtuelles university en ajoutant aux tableaux inclus dans $pages la ligne 'model' => 'university'

Je ne l'ai pas fait ici car nous en avons pas l'usage.

Template de la page parent

Dans le template parent /site/temlpates/universities.php nous pouvons appeler la méthode children() que nous avons étendue dans la page model /site/models/universities.php afin de récupérer les sous-pages virtuelles university

<?php snippet('header') ?>

    <div>
      <?php
      foreach ( $page->children() as $uni ) :
      ?>

        <h2>
            <a href="<?= $uni->url() ?>">
        	<?= $uni->name()->value() ?>
            </a>
        </h2>
        <a href="<?= $uni->uni_url()->value() ?>"><?= $uni->uni_url()->value() ?></a>

      <?php
      endforeach;
      ?>
    </div>

<?php snippet('footer') ?>
Le système de routage de Kirby fonctionne "out of the box" sur mes pages enfants virtuelles.

Template des pages virtuelles enfants

Dans le template /site/templates/university.php qui sert à afficher les données des enfants, j'ai alors accès aux données passées en paramètre de la méthode factory() et qui contiennent les informations de chaque université.

<?php snippet('header') ?>

<h2><?= $page->name()->value() ?></h2>

<div><?= $page->details()->value() ?></div>

<?php snippet('footer') ?>

Afficher les données d'un fichier CSV dans une page virtuelle

Imaginons maintenant que nous souhaitions afficher dans une page Kirby "Projects" des données de projets contenues dans un fichier CSV. Supposons également que cette page "Projects" est accessible depuis le panel Kirby et possède un champ de type "Field upload" permettant de télécharger le fichier CSV.

La page "Projects" sera contenu dans le dossier /content/projects/projects.txt et le fichier CSV une fois téléchargé depuis cette page dans le panel sera stocké au même niveau que le fichier projects.txt c'est à dire dans /content/projects/projects.csv

Le fichier CSV

Voici à quoi va ressembler la structure de mon fichier CSV :

Pour lire les données de ce fichier CSV, nous allons utiliser une petite fonction "helper" proposée dans la documentation de Kirby sous forme de plugin.

<?php

// /site/plugins/helpers/index.php

function csv(string $file, string $delimiter = ','): array
{
    $lines = file($file);

    $lines[0] = str_replace("\xEF\xBB\xBF", '', $lines[0]);

    $csv = array_map(function($d) use($delimiter) {
        return str_getcsv($d, $delimiter);
    }, $lines);

    array_walk($csv, function(&$a) use ($csv) {
       $a = array_combine($csv[0], $a);
    });

    array_shift($csv);

    return $csv;
}

Cette fonction csv() prend en paramètre le chemin absolue du fichier CSV et le caractère délmimiteur à prendre en compte. En retour, la fonction construit un tableau avec nos données.

La page model "projects"

Comme dans l'exemple précédent, nous allons afficher tous les projets sur une page parente avec un lien pour accéder au détail de chaque projet sur une page enfant. Pour cela, nous allons étendre la méthode children() de la page parente à l'aide d'une page model pour qu'elle nous renvoie tous les projets issues du fichier CSV sous forme d'une collection de pages qui équivaut à un objet Pages. Cela est possible grâce à l'utilisation de la méthode factory() :

<?php

// /site/models/projects.php

class ProjectsPage extends Page
{

    public function children()
    {
        $csv      = csv( $this->root() . '/projects.csv', ';' );

        foreach ( $csv as $project ) :
             $pages[] = [
                'slug'     => Str::slug( $project['Nom du projet'] ),
                'template' => 'project',
                'content'  => [
                    'name'          => $project['Nom du projet'],
                    'datecreation'  => $project['Date de réalisation'],
                    'description'   => $project['Description'],
                    'client'        => $project['Client'],
               ]
                    
             ];
        endforeach;

        return Pages::factory( $pages, $this );
    }

}

Nous utilisons ici notre fonction helper csv() pour lire le fichier CSV projects.csv qui se trouve à la racine du dossier /content/projects Cette fonction nous renvoie un tableau contenant les données de nos différents projets. Il suffit ensuite de boucler sur ce tableau et de construire un autre tableau multidimensionnel contenant les informations de nos progets selon un format attendu par la fonction factory()

Le template de la page parent "projects"

Maintenant, dans mon template projects.php ,lorsque j'appelle la méthode children() j'ai accès aux données de mes projets de la même manière que si elles avaient été contenus dans un fichier texte Kirby. Je peux appeller par exemple la méthode $project->url() puisque $project est un objet Page Kirby

<?php
// /site/templates/projects.php
?>

<?php snippet('header') ?>


  <h1><?= $page->title() ?></h1>

  <ul>
    <?php foreach ( $page->children() as $project ) : ?>
    <li>
      <a href="<?= $project->url() ?>">
        <?= $project->name() ?>
      </a>
    </li>
    <?php endforeach ?>
  </ul>


<?php snippet('footer') ?>

Le template des pages enfants "project"

Les pages enfants project sont des pages virtuelles qui bénéficient du même fonctionnement que les pages classiques de Kirby. On peut donc accéder aux différentes données avec une méthode ->nom_de_la_donnée() sur l'objet $page

<?php
// /site/templates/project.php
?>

<?php snippet('header') ?>


  <div>Projet: <?= $page->name() ?></div>
  <div>
    <?= $page->datecreation()->value() ?><br>
    <?= $page->description()->kt() ?><br>
    <?= $page->client()->value() ?><br>
  </div>


<?php snippet('footer') ?>

Nous avons vu comment accéder à des données externes provenant d'une API externe et d'un fichier CSV et comment les intégrer dans un système de pages "virtuelles" grâce à la méthode factory() qui nous rétribue nos données dans un objet Page ou sous la forme d'une collection de pages via l'objet Pages. Vous pouvez faire la même chose avec des données provenant d'une base de données, d'un fichier JSON ou d'un flux RSS.

La vidéo YouTube

Disponible prochainement ...