// BLOG

Kirby CMS - Lire ses données via l'API à l'aide du plugin KQL

Kirby nous permet d'accéder à nos données via une API. Pour faciliter l'accès à ces données via l'API, l'équipe Kirby a développé un plugin appelé KQL que je vais vous présenter dans cet article.

Rédigé le 29.04.2023
Par Gilles Vauvarin

Kirby CMS PHP Plugin KQL

Introduction

Les initiales KQL signifient "Kirby Query Language". En effet, le plugin KQL met à notre disposition un language de requêtage des données qui est un mixte entre REST et GraphQL. Attention, le plugin KQL ne permet pas de mutations des données (creation, mise à jour, suppression) mais seulement d'accéder aux données pour ensuite les afficher ou les stocker dans un fichier CSV par exemple. Vous ne pourrez donc pas faire du CRUD à l'aide ce plugin. Pour utiliser ce plugin, commencez par l'installer, puis mettez en place l'authentification pour autoriser l'accès à l'API REST de Kirby.

Authentification

Pour mettre en place une authentification, il y a deux approches possibles selon le cas d'utilisation :

1er cas - Vous allez faire des appels à l'API depuis le frontend de votre propre site/nom de domaine.

Cela peut être le cas si vous utilisez Kirby CMS en mode headless c'est à dire si vous utilisez le CMS Kirby pour gérer uniquement la gestion des données et des utilisateurs. L'affichage des données est géré par un framework type Vue ou un générateur de site statique par exemple.

Dans ce cas nous utiliserons une authentification par Session et Token CSRF

Le token CSRF peut être généré par la fonction d'aide csrf() mis à disposition par Kirby. Il devra ensuite être passé comme valeur de l'option d'en-tête X-CSRF à chaque requête.

Voyons un exemple (ici en JavaScript) :

<?php

// projet_kirby/site/templates/default.php

?>

<html>
  <head>
    <title>API Example</title>
  </head>

  <body>
    <script>
      const csrf = "<?= csrf() ?>";

      fetch("/api/pages/example", {
          method: "GET",
          headers: {
            "X-CSRF" : csrf
          }
        })
        .then(response => response.json())
        .then(response => {
          const page = response.data;
          // do something with the page data
        })
        .catch(error => {
          // something went wrong
        });
    </script>
  </body>
</html>

2eme cas - Vous allez faire des appels à l'API depuis un serveur distant

Vous souhaitez que les données de votre site web soient disponibles pour un autre site web afin qu'il puisse les afficher sur ses propres pages.

Dans ce cas nous utiliserons une authentification HTTP basique

Pour utiliser une authentification basique, il faut commencer par l'indiquer dans le fichier de configuration de Kirby, la valeur true passée à basicAuth activera l'authentification :

<?php

// /site/config/config.php

return [
    'api' => [
        'basicAuth' => true
    ]
];

Pour fonctionner, il faut que la requête se fasse à travers un protocole https à moins que vous ayez activé l'option allowInsecure de la facon suivante :

<?php

// /site/config/config.php

return [
    'api' => [
        'basicAuth' => true,
        'allowInsecure' => true
    ]
];
Pour des raisons de sécurité, n'activez pas l'option allowInsecure en production mais seulment dans un environnement de développement.

Vous devrez également indiquer dans le code qui requête votre API le "login" et "password" d'un utilisateur Kirby ayant le droit d'accès au panel de Kirby. Une bonne pratique serait de créer un rôle spécifique par exemple ici un rôle "apiuser" (mais vous pouvez lui donner le nom que vous voulez) et de définir ses permissions de sorte qu'il n'est le droit que d'accéder au panel :

# /site/blueprints/users/apiuser.yml

title: API Authentification
description: The API user is used to fetch data from the API.

permissions:
  access:
    panel: true
    users: false
    site: false
  site:
    update: false
  pages:
    create: false
    changeTemplate: false
    changeTitle: false
    changeURL: false
    hide: false
    sort: false
    update: false
    delete: false
  users:
    create: false
    createAvatar: false
    deleteAvatar: false
    changeName: false
    changeEmail: false
    changePassword: false
    changeRole: false
    delete: false
    update: false
  files:
    create: false
    changeName: false
    delete: false
    replace: false
    update: false

Vous pouvez maintenant créer un utilisateur avec le rôle "apiuser" et utiliser son login/password dans votre code pour accéder à l'API depuis un autre serveur/site/domain distant.

Voyons un exemple (ici en PHP) :

<?php

$user = "apiuser@mail.com";
$pass = "my_super_safe_password";

$request = Remote::request( "https://yoursite.com/api/query" , [
   'method' => 'POST',
   'data' => [
   'query' => "page('notes').children",
   'select' => [
      "title",
      "date" => "page.date.toDate('d.m.Y')"
   ]
],
   'headers' => [
      'Authorization: Basic ' . base64_encode( $user . ':' . $pass ),
      'Content-Type: application/json'
   ]
]);

On utilise ici la classe Remote de Kirby qui disposent de plusieurs méthodes static dont request qui permet d'envoyer une requête. A noter qu'on aurait pu également s'authentifier et requêter un "endpoint" de l'API en JavaScript à l'aide de la fonction Fetch().

Utilisation de KQL

Maintenant que nous avons installé le plugin KQL et vue comment mettre en place l'authentification, nous allons pouvoir voir plus en détail la façon d'utiliser les fonctionnalités du plugin KQL pour sélectionner les données qui nous intéressent.

Query

L'instruction Query permet d'indiquer les données que l'on souhaite récupérer. Il peut s'agir de pages, de users, de fichiers, de champs ... et la synthaxe utilisée pour récupérer ces données est très proche de celle habituellement utilisée dans Kirby dans les templates, les controllers, les snippets ou les blueprints.

Généralement, les Query retournent des collections c'est à dire des éléments de même types (pages, users, fichiers ...)

Par exemple, si on veut récupérer toutes les pages enfants (les articles) de la page blog, on écrira la Query de la façon suivante :

'query' => "page('blog').children",

Vous pouvez utiliser l'instruction Query sans la compléter par l'instruction Select. Dans ce cas KQL récupérera l'information qu'il jugera la plus pertinente. Dans l'exemple précédent, la Query page('blog').children sans instruction Select, retournera les ID des pages enfants de la page 'blog'.

Dans votre Query, vous pouvez utiliser des méthodes de Kirby comme filterBy(), sort() ... pour filtrer vos résultats ou des méthodes de champs comme upper() ... pour appliquer une transformation aux données récupérées.

Cette page de référence vous permettra de voir l'ensemble des objets et méthodes disponibles dans Kirby.

Select

L'instruction Select vient compléter l'instruction Query. Alors que l'instruction Query peux s'utiliser seul, l'instruction Select ne le peux pas car elle s'applique directement sur l'instruction Query.

L'instruction Select va nous permettre d'indiquer précisement les informations que l'on souhaite récupérer sur les éléments qui ont été retournés par la Query. Ces informations sont généralement des propriétés ou des champs.

Par exemple, si je veux récupérer le titre et l'URL des articles de mon blog, je vais passer à l'instruction Select un tableau avec les champs ou propriétés qui m'intéressent :

<?php

'query' => "page('blog').children",
'select' => [
      "title",
      "url"
],
Si nous écrivons notre Query/Select en JavaScript, nous pouvons passer à l'instruction Select un tableau ou un objet.
<script>

// Tableau

const response = await $fetch(api, {
  method: "post",
  body: {
    query: "site.children",
    select: ["title", "url"],
  },
  headers,
});

// Object

const response = await $fetch(api, {
  method: "post",
  body: {
    query: "site.children",
    select: {
      title: true,
      url: true,
    },
  },
  headers,
});

</script>

Executer des méthodes

Comme pour les Query, nous pouvons appliquer des méthodes Kirby aux champs ou propriétés appelés dans l'instruction Select.

<?php

'query' => "page('blog').children",
'select' => [
      'title' => 'page.title.upper',
      'url'
],

Sous-requêtes

Au sein d'une instruction Select il est possible d'executer des sous-requêtes qui retournent des collections d'éléments.

Par exemple, on peut faire une requête qui retourne des sous-pages et pour chacune de ces sous-pages faire une sous-requête qui retournera une collection de toutes les images liées.

<?php

'query' => "page('blog').children",
'select' => [
      'images' => 'page.images',
],

Sous-requêtes avec instruction Select

Nous avons également la possibilité d'imbriquer des instructions Query/Select

<?php

'query' => "page('blog').children",
'select' => [
      'images' => [
          'query' => "page.images",
          'select' => [
              'filename',
              ],
           ],
       ],

Pagination

Lorsqu'une requête Query renvoie une collection (pages, users, files ...), une pagination est par défaut mise en place qui limite le resultat à 100 entrées.

Limit

Vous pouvez contrôler cette limite de la façon suivante :

<?php

'query' => "page('blog').children",
'pagination' => [ 
            'limit' => '1'
        ],
        'select' => [
              'title',
              'url'
        ],
    ],

Page

Vous pouvez également choisir sur quelle page de la pagination vous placer :

<?php

'query' => "page('blog').children",
        'pagination' => [
             'page' => '2',
             'limit' => '1'
           ],
        'select' => [
              'title',
              'url'
        ],

Pagination dans les sous-requêtes

Ce contrôle de la pagination peut également se faire au niveau des sous-requêtes :

<?php

'query' => "page('blog').children",
    'query' => "page('notes').children",
        'select' => [
          'title' => 'page.title',
          'images' => [
            'query' => 'page.images',
            'pagination' => [
              'page' => '2',
              'limit' => '5',
            ],
            'select' => [
              'filename',
            ],
          ],
        ],
    ],

Plusieurs requêtes dans un seul appel

Avec le système d'instruction Query/Select vous pouvez effectuer autant de requêtes que vous le souhaitez dans un seul appel à l'API :

<?php

'query'=> "site",
    'select' => [
      'title' => "site.title",
      'url' => "site.url",
      'notes' => [
        'query' => "page('notes').children.listed",
        'select' => [
          'title',
          'url',
          'date' => "page.date.toDate('d.m.Y')",
          'text' => "page.text.kirbytext",
        ],
      ],
      'photography' => [
        'query' => "page('photography').children.listed",
        'select' => [
          'title',
          'images' => [
            'query' => "page.images",
            'select' => [
              'url',
              'alt',
              'caption'=> "file.caption.kirbytext",
            ],
          ],
        ],
      ],
      'about' => [
        'text' => "page.text.kirbytext",
      ],
    ],

Permettre l'accès à des méthodes personnalisées

Par défaut, KQL n'autorise l'accès qu'aux méthodes mises à disposition dans Kirby. Si vous avez mis en place des méthodes personnalisées par exemple dans des models ou lors de la surcharge de méthodes de page, celle ci ne sont pas accessibles lors de votre requêtage.

Pour autoriser l'accès à vos méthodes personnalisées dans une requête KQL, deux solutions :

L'indiquer dans le fichier de configuration de Kirby

<?php

// /site/config/config.php

return [
  'kql' => [
    'methods' => [
      'allowed' => [
        'MyCustomPage::myCustomMethod'
      ]
    ]
  ]
];

L'indiquer sous forme de commentaire dans la définission de votre méthode :

Avec le mot clé @kql-allowed

<?php


// /site/models/MyCustomPage.php

class MyCustomPage extends Page
{
  /**
   * @kql-allowed
   */
  public function myCustomMethod()
  {
    return ...;
  }
}

Bloquer des méthodes de Classe

Si vous le souhaitez, vous pouvez interdire l'accès dans les requêtes à des méthodes de Classe Kirby qui sont disponibles par défaut :

<?php

// /site/config/config.php

return [
  'kql' => [
    'methods' => [
      'blocked' => [
        'Kirby\Cms\Page::url'
      ]
    ]
  ]
];

Bloquer des Classes

Vous pouvez être plus radical dans vos interdictions en interdisant l'accès aux méthodes d'une Classe entière :

<?php


// /site/config/config.php

return [
  'kql' => [
    'classes' => [
      'blocked' => [
        'Kirby\Cms\User'
      ]
    ]
  ]
];

Conclusion

Le plugin KQL est un superbe outil pour requêter l'API de Kirby de façon simple et précise grâce à son système très souple de Query/Select. Ce plugin étant développé et maintenue par l'équipe de Kirby, vous pouvez avoir confiance dans sa qualité. Si vous voulez vous exercer à requêter des données via KQL, vous pouvez utiliser le "Playground" en ligne mis à disposition par Kirby sur le Starterkit.

Dans cet article, j'ai privilégié l'utilisation de KQL dans une synthaxe PHP, si vous souhaitez voir des exemples de code de l'utilisation de KQL en JavaScript, je vous invite à consulter la documentation officielle de Kirby sur KQL.

Sachez également que ce n'est pas la seule manière d'exposer ces données vers l'exterieure. Vous pouvez aussi utiliser les représentations de contenu qui affichent vos données au format JSON via une simple URL.