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