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