xref: /webtrees/modules_v4/README.md (revision 304fefbe9fb9d0ca1a8f4faba7aef126ff497676)
1# THIRD-PARTY MODULES
2
3Many webtrees functions are provided by “modules”.
4Modules allows you to add additional features to webtrees and modify existing features.
5
6## Installing and uninstalling modules
7
8A module is a folder containing a file called `module.php`.
9There may be other files in the folder, such as CSS, JS, templates,
10languages, data, etc.
11
12To install a module, copy its folder to `modules_v4`.
13
14To uninstall it, delete its folder from `modules_v4`.
15
16Note that module names (i.e. the folder names) must not contain
17spaces or the characters `.`, `[` and `]`.  It must also have a
18maximum length of 30 characters.
19
20TIP: renaming a module from `<module>` to `<module.disable>`
21is a quick way to hide it from webtrees.  This works because
22modules containing `.` are ignored.
23
24## Writing modules
25
26To write a module, you need to understand the PHP programming langauge.
27
28The rest of this document is aimed at PHP developers.
29
30TIP: The built-in modules can be found in `app/Module/*.php`.
31These contain lots of useful examples that you can copy/paste.
32
33## Creating a custom module.
34
35This is the minimum code needed to create a custom module.
36
37```php
38<?php
39
40use Fisharebest\Webtrees\Module\AbstractModule;
41use Fisharebest\Webtrees\Module\ModuleCustomInterface;
42use Fisharebest\Webtrees\Module\ModuleCustomTrait;
43
44return new class extends AbstractModule implements ModuleCustomInterface {
45    use ModuleCustomTrait;
46
47    /**
48     * How should this module be labelled on tabs, menus, etc.?
49     *
50     * @return string
51     */
52    public function title(): string
53    {
54        return 'My Custom module';
55    }
56
57    /**
58     * A sentence describing what this module does.
59     *
60     * @return string
61     */
62    public function description(): string
63    {
64        return 'This module doesn‘t do anything';
65    }
66};
67```
68
69If you plan to share your modules with other webtrees users, you should
70provide them with support/contact/version information.  This way they will
71know where to go for updates, support, etc.
72Look at the functions and comments in `app/ModuleCustomTrait.php`.
73
74## Available interfaces
75
76Custom modules *must* implement `ModuleCustomInterface` interface.
77They *may* implement one or more of the following interfaces:
78
79* `ModuleAnalyticsInterface` - adds a tracking/analytics provider.
80* `ModuleBlockInterface` - adds a block to the home pages.
81* `ModuleChartInterface` - adds a chart to the chart menu.
82* `ModuleListInterface` - adds a list to the list menu.
83* `ModuleConfigInterface` - adds a configuration page to the control panel.
84* `ModuleMenuInterface` - adds an entry to the main menu.
85* `ModuleReportInterface` - adds a report to the report menu.
86* `ModuleSidebarInterface` - adds a sidebar to the individual pages.
87* `ModuleTabInterface` - adds a tab to the individual pages.
88* `ModuleThemeInterface` - adds a theme (this interface is still being developed).
89
90For each module interface that you implement, you must also use the corresponding trait.
91If you don't do this, your module may break whenever the module interface is updated.
92
93Where possible, the interfaces won't change - however new methods may be added
94and existing methods may be deprecated.
95
96Modules may also implement the following interfaces, which allow them to integrate
97more deeply into the application.
98
99* `MiddlewareInterface` - allows a module to intercept the HTTP request/response cycle.
100
101## How to extend/modify an existing modules
102
103To create a module that is just a modified version of an existing module,
104you can extend the existing module (instead of extending `AbstractModule`).
105
106```php
107<?php
108use Fisharebest\Webtrees\Module\ModuleCustomInterface;
109use Fisharebest\Webtrees\Module\ModuleCustomTrait;
110use Fisharebest\Webtrees\Module\PedigreeChartModule;
111
112/**
113 * Creating an anoymous class will prevent conflicts with other custom modules.
114 */
115return new class extends PedigreeChartModule implements ModuleCustomInterface {
116    use ModuleCustomTrait;
117
118    /**
119     * @return string
120     */
121    public function description(): string
122    {
123        return 'A modified version of the pedigree chart';
124    }
125
126    // Change the default layout...
127    public const DEFAULT_ORIENTATION = self::ORIENTATION_DOWN;
128};
129```
130
131## Dependency Injection
132
133webtrees uses the “Dependency Injection” pattern extensively.  This is a system for
134automatically generating objects.  The advantages over using `new SomeClass()` are
135
136* Easier testing - you can pass "dummy" objects to your class.
137* Run-time resolution - you can request an Interface, and webtrees will find a specific instance for you.
138* Can swap implementations at runtime.
139
140Note that you cannot type-hint the following objects in the constructor, as they are not
141created until after the modules.
142
143* other modules
144* interfaces, such as `UserInterface` (the current user)
145* the current tree `Tree` or objects that depend on it (`Statistics`)
146as these objects are not created until after the module is created.
147
148Instead, you can fetch these items when they are needed from the "application container" using:
149``` $user = app(UserInterface::class)```
150
151```php
152<?php
153use Fisharebest\Webtrees\Module\AbstractModule;
154use Fisharebest\Webtrees\Module\ModuleCustomInterface;
155use Fisharebest\Webtrees\Module\ModuleCustomTrait;
156use Fisharebest\Webtrees\Services\TimeoutService;
157use Fisharebest\Webtrees\Tree;
158use Symfony\Component\HttpFoundation\Request;
159use Symfony\Component\HttpFoundation\Response;
160
161/**
162 * Creating an anoymous class will prevent conflicts with other custom modules.
163 */
164return new class extends AbstractModule implements ModuleCustomInterface {
165    use ModuleCustomTrait;
166
167    /** @var TimeoutService */
168    protected $timeout_service;
169
170    /**
171     * IMPORTANT - the constructor is called for *all* modules, even ones
172     * that are disabled.  You should do little more than initialise your
173     * private/protected members.
174     *
175     * @param TimeoutService $timeout_service
176     */
177    public function __construct(TimeoutService $timeout_service)
178    {
179        $this->timeout_service = $timeout_service;
180    }
181
182    /**
183     * Methods that are called in response to HTTP requests use
184     * dependency-injection.  You'll almost certainly need the request
185     * object.
186     *
187     * @param Request   $request
188     * @param Tree|null $tree
189     *
190     * @return Response
191     */
192    public function getFooBarAction(Request $request, ?Tree $tree): Response
193    {
194        return new Response();
195    }
196};
197```
198