FreshRSS/docs/fr/developers/03_Backend/05_Extensions.md

262 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Écriture dextensions pour FreshRSS
## Présentation de FreshRSS
FreshRSS est un agrégateur de flux RSS / Atom écrit en PHP depuis octobre
2012. Le site officiel est situé à ladresse
[freshrss.org](https://freshrss.org) et son dépot Git est hébergé par Github
: [github.com/FreshRSS/FreshRSS](https://github.com/FreshRSS/FreshRSS).
## Problème à résoudre
FreshRSS est limité dans ses possibilités techniques par différents facteurs
:
* La disponibilité des développeurs principaux ;
* La volonté dintégrer certains changements ;
* Le niveau de « hack » nécessaire pour intégrer des fonctionnalités à la marge.
Si la première limitation peut, en théorie, être levée par la participation
de nouveaux contributeurs au projet, elle est en réalité conditionnée par la
volonté des contributeurs à sintéresser au code source du projet en
entier. Afin de lever les deux autres limitations quant à elles, il faudra
la plupart du temps passer par un « à-coté » souvent synonyme de « fork ».
Une autre solution consiste à passer par un système dextensions. En
permettant à des utilisateurs décrire leur propre extension sans avoir à
sintéresser au cœur même du logiciel de base, on permet :
1. De réduire la quantité de code source à assimiler pour un nouveau contributeur ;
2. De permettre dintégrer des nouveautés de façon non-officielles ;
3. De se passer des développeurs principaux pour déventuelles améliorations
sans passer par la case « fork ».
Note : il est tout à fait imaginable que les fonctionnalités dune extension
puissent par la suite être intégrées dans le code initial de FreshRSS de
façon officielle. Cela permet de proposer un « proof of concept » assez
facilement.
## Minz Framework
see [Minz documentation](/docs/fr/developers/Minz/index.md)
## Écrire une extension pour FreshRSS
Nous y voilà ! Nous avons abordé les fonctionnalités les plus utiles de Minz
et qui permettent de faire tourner FreshRSS correctement et il est plus que
temps daborder les extensions en elles-même.
Une extension permet donc dajouter des fonctionnalités facilement à
FreshRSS sans avoir à toucher au cœur du projet directement.
### Travailler dans Docker
Quand on travaille sur une extension, cest toujours plus facile de la travailler directement dans son environnement. Avec Docker, on peut exploiter loption ```volume``` quand on démarre le conteneur. Heureusement, on peut lutiliser sans avoir de connaissances particulières de Docker en utilisant la règle du Makefile :
```sh
make start extensions="/chemin/complet/de/l/extension/1 /chemin/complet/de/l/extension/2"
```
### Les fichiers et répertoires de base
La première chose à noter est que **toutes** les extensions **doivent** se
situer dans le répertoire `extensions`, à la base de larborescence de
FreshRSS. Une extension est un répertoire contenant un ensemble de fichiers
et sous-répertoires obligatoires ou facultatifs. La convention veut que lon
précède le nom du répertoire principal par un « x » pour indiquer quil ne
sagit pas dune extension incluse par défaut dans FreshRSS.
Le répertoire principal dune extension doit comporter au moins deux
fichiers **obligatoire** :
* Un fichier `metadata.json` qui contient une description de lextension. Ce
fichier est écrit en JSON ;
* Un fichier `extension.php` contenant le point dentrée de lextension.
Please note that there is a not a required link between the directory name
of the extension and the name of the class inside `extension.php`, but you
should follow our best practice: If you want to write a `HelloWorld`
extension, the directory name should be `xExtension-HelloWorld` and the base
class name `HelloWorldExtension`.
In the file `freshrss/extensions/xExtension-HelloWorld/extension.php` you
need the structure:
```html
class HelloWorldExtension extends Minz_Extension {
public function init() {
// your code here
}
}
```
There is an example HelloWorld extension that you can download from [our
GitHub repo](https://github.com/FreshRSS/xExtension-HelloWorld).
You may also need additional files or subdirectories depending on your
needs:
* `configure.phtml` est le fichier contenant le formulaire pour paramétrer
votre extension
* A `static/` directory containing CSS and JavaScript files that you will
need for your extension (note that if you need to write a lot of CSS it
may be more interesting to write a complete theme)
* A `Controllers` directory containing additional controllers
* An `i18n` directory containing additional translations
* `layout` and `views` directories to define new views or to overwrite the
current views
In addition, it is good to have a `LICENSE` file indicating the license
under which your extension is distributed and a `README` file giving a
detailed description of it.
### The metadata.json file
The `metadata.json` file defines your extension through a number of
important elements. It must contain a valid JSON array containing the
following entries:
* `name` : le nom de votre extension ;
* `author` : votre nom, éventuellement votre adresse mail mais il ny a pas
de format spécifique à adopter ;
* `description` : une description de votre extension ;
* `version` : le numéro de version actuel de lextension ;
* `entrypoint` : indique le point dentrée de votre extension. Il doit
correspondre au nom de la classe contenue dans le fichier `extension.php`
sans le suffixe `Extension` (donc si le point dentrée est `HelloWorld`,
votre classe sappellera `HelloWorldExtension`) ;
* `type` : définit le type de votre extension. Il existe deux types :
`system` et `user`. Nous étudierons cette différence juste après.
Seuls les champs `name` et `entrypoint` sont requis.
### Choisir entre extension « system » ou « user »
A *user* extension can be enabled by some users and not by others
(typically for user preferences).
A *system* extension in comparison is enabled for every account.
### Writing your own extension.php
This file is the entry point of your extension. It must contain a specific
class to function. As mentioned above, the name of the class must be your
`entrypoint` suffixed by `Extension` (`HelloWorldExtension` for example).
In addition, this class must be inherited from the `Minz_Extension` class to
benefit from extensions-specific methods.
Your class will benefit from four methods to redefine:
* `install()` is called when a user clicks the button to activate your
extension. It allows, for example, to update the database of a user in
order to make it compatible with the extension. It returns `true` if
everything went well or, if not, a string explaining the problem.
* `uninstall()` is called when a user clicks the button to disable your
extension. This will allow you to undo the database changes you
potentially made in `install ()`. It returns `true` if everything went
well or, if not, a string explaining the problem.
* `init()` is called for every page load *if the extension is enabled*. It
will therefore initialize the behavior of the extension. This is the most
important method.
* `handleConfigureAction()` is called when a user loads the extension
management panel. Specifically, it is called when the
`?c=extension&a=configured&e=name-of-your-extension` URL is loaded. You
should also write here the behavior you want when validating the form in
your `configure.phtml` file.
In addition, you will have a number of methods directly inherited from
`Minz_Extension` that you should not redefine:
* The "getters" first: most are explicit enough not to detail them here -
`getName()`, `getEntrypoint()`, `getPath()` (allows you to retrieve the
path to your extension), `getAuthor()`, `getDescription()`,
`getVersion()`, `getType()`.
* `getFileUrl($filename, $type)` will return the URL to a file in the
`static` directory. The first parameter is the name of the file (without
`static /`), the second is the type of file to be used (`css` or `js`).
* `registerController($base_name)` will tell Minz to take into account the
given controller in the routing system. The controller must be located in
your `Controllers` directory, the name of the file must be `<base_name>Controller.php` and the name of the
`FreshExtension_<base_name>_Controller` class.
> **À FAIRE**
* `registerViews()`
* `registerTranslates()`
* `registerHook($hook_name, $hook_function)`
### Le système « hooks »
You can register at the FreshRSS event system in an extensions `init()`
method, to manipulate data when some of the core functions are executed.
```php
final class HelloWorldExtension extends Minz_Extension
{
#[\Override]
public function init(): void {
$this->registerHook('entry_before_display', [$this, 'renderEntry']);
$this->registerHook('check_url_before_add', [self::class, 'checkUrl']);
}
public function renderEntry(FreshRSS_Entry $entry): FreshRSS_Entry {
$message = $this->getUserConfigurationValue('message');
$entry->_content("<h1>{$message}</h1>" . $entry->content());
return $entry;
}
public static function checkUrlBeforeAdd(string $url): string {
if (str_starts_with($url, 'https://')) {
return $url;
}
return null;
}
}
```
The following events are available:
* `check_url_before_add` (`function($url) -> Url | null`): will be executed
every time a URL is added. The URL itself will be passed as
parameter. This way a website known to have feeds which doesnt advertise
it in the header can still be automatically supported.
* `entry_auto_read` (`function(FreshRSS_Entry $entry, string $why): void`):
Appelé lorsquune entrée est automatiquement marquée comme lue. Le paramètre *why* supporte les règles {`filter`, `upon_reception`, `same_title_in_feed`}.
* `entry_auto_unread` (`function(FreshRSS_Entry $entry, string $why): void`):
Appelé lorsquune entrée est automatiquement marquée comme non-lue. Le paramètre *why* supporte les règles {`updated_article`}.
* `entry_before_display` (`function($entry) -> Entry | null`): will be
executed every time an entry is rendered. The entry itself (instance of
FreshRSS\_Entry) will be passed as parameter.
* `entry_before_insert` (`function($entry) -> Entry | null`): will be
executed when a feed is refreshed and new entries will be imported into
the database. The new entry (instance of FreshRSS\_Entry) will be passed
as parameter.
* `feed_before_actualize` (`function($feed) -> Feed | null`): will be
executed when a feed is updated. The feed (instance of FreshRSS\_Feed)
will be passed as parameter.
* `feed_before_insert` (`function($feed) -> Feed | null`): will be executed
when a new feed is imported into the database. The new feed (instance of
FreshRSS\_Feed) will be passed as parameter.
* `freshrss_init` (`function() -> none`): will be executed at the end of the
initialization of FreshRSS, useful to initialize components or to do
additional access checks
* `menu_admin_entry` (`function() -> string`): add an entry at the end of
the "Administration" menu, the returned string must be valid HTML
(e.g. `<li class="item active"><a href="url">New entry</a></li>`)
* `menu_configuration_entry` (`function() -> string`): add an entry at the
end of the "Configuration" menu, the returned string must be valid HTML
(e.g. `<li class="item active"><a href="url">New entry</a></li>`)
* `menu_other_entry` (`function() -> string`): add an entry at the end of
the header dropdown menu (i.e. after the "About" entry), the returned
string must be valid HTML (e.g. `<li class="item active"><a href="url">New
entry</a></li>`)
* `nav_reading_modes` (`function($reading_modes) -> array | null`): **TODO**
add documentation
* `post_update` (`function(none) -> none`): **TODO** add documentation
* `simplepie_before_init` (`function($simplePie, $feed) -> none`): **TODO**
add documentation
### Writing your own configure.phtml
When you want to support user configurations for your extension or simply
display some information, you have to create the `configure.phtml` file.
> **À FAIRE**