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 language. 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* `ModuleDataFixInterface` - adds a data fix to the manage-trees page. 83* `ModuleConfigInterface` - adds a configuration page to the control panel. 84* `ModuleGlobalInterface` - adds CSS and JS to all page. 85* `ModuleListInterface` - adds a list to the list menu. 86* `ModuleMenuInterface` - adds an entry to the main menu. 87* `ModuleReportInterface` - adds a report to the report menu. 88* `ModuleSidebarInterface` - adds a sidebar to the individual pages. 89* `ModuleTabInterface` - adds a tab to the individual pages. 90* `ModuleThemeInterface` - adds a theme (this interface is still being developed). 91 92For each module interface that you implement, you must also use the corresponding trait. 93If you don't do this, your module may break whenever the module interface is updated. 94 95Where possible, the interfaces won't change - however new methods may be added 96and existing methods may be deprecated. 97 98Modules may also implement the following interfaces, which allow them to integrate 99more deeply into the application. 100 101* `MiddlewareInterface` - allows a module to intercept the HTTP request/response cycle. 102 103## How to extend/modify an existing modules 104 105To create a module that is just a modified version of an existing module, 106you can extend the existing module (instead of extending `AbstractModule`). 107 108```php 109<?php 110use Fisharebest\Webtrees\Module\ModuleCustomInterface; 111use Fisharebest\Webtrees\Module\ModuleCustomTrait; 112use Fisharebest\Webtrees\Module\PedigreeChartModule; 113use Fisharebest\Webtrees\Services\ChartService; 114 115/** 116 * Creating an anonymous class will prevent conflicts with other custom modules. 117 */ 118return new class extends PedigreeChartModule implements ModuleCustomInterface { 119 use ModuleCustomTrait; 120 121 /** 122 * The chart needs some chart functions. We could pass in our own version here. 123 */ 124 public function __construct() 125 { 126 parent::__construct(new ChartService()); 127 } 128 129 /** 130 * @return string 131 */ 132 public function description(): string 133 { 134 return 'A modified version of the pedigree chart'; 135 } 136 137 // Change the default layout... 138 protected const DEFAULT_STYLE = self::STYLE_DOWN; 139 140 protected const DEFAULT_PARAMETERS = [ 141 'generations' => self::DEFAULT_GENERATIONS, 142 'style' => self::DEFAULT_STYLE, 143 ]; 144}; 145``` 146 147## Dependency Injection 148 149webtrees uses the “Dependency Injection” pattern extensively. This is a system for 150automatically generating objects. The advantages over using `new SomeClass()` are 151 152* Easier testing - you can pass "dummy" objects to your class. 153* Run-time resolution - you can request an Interface, and webtrees will find a specific instance for you. 154* Can swap implementations at runtime. 155 156Note that you cannot type-hint the following objects in the constructor, as they are not 157created until after the modules. 158 159* other modules 160* interfaces, such as `UserInterface` or `LocaleInterface` (the current user and language) 161* the current tree `Tree` or objects that depend on it (`Statistics`) 162as these objects are not created until after the module is created. 163 164Instead, these objects can be obtained from the request object. e.g. 165```php 166$tree = $request->getAttribute('tree'); 167$user = $request->getAttribute('user'); 168``` 169 170```php 171<?php 172use Fisharebest\Webtrees\Module\AbstractModule; 173use Fisharebest\Webtrees\Module\ModuleCustomInterface; 174use Fisharebest\Webtrees\Module\ModuleCustomTrait; 175use Fisharebest\Webtrees\Services\TimeoutService; 176use Psr\Http\Message\ResponseInterface; 177use Psr\Http\Message\ServerRequestInterface; 178 179/** 180 * Creating an anoymous class will prevent conflicts with other custom modules. 181 */ 182return new class extends AbstractModule implements ModuleCustomInterface { 183 use ModuleCustomTrait; 184 185 /** @var TimeoutService */ 186 protected $timeout_service; 187 188 /** 189 * IMPORTANT - the constructor is called for *all* modules, even ones 190 * that are disabled. You should do little more than initialise your 191 * private/protected members. 192 * 193 * @param TimeoutService $timeout_service 194 */ 195 public function __construct(TimeoutService $timeout_service) 196 { 197 $this->timeout_service = $timeout_service; 198 } 199 200 /** 201 * Methods that are called in response to HTTP requests use 202 * dependency-injection. You'll almost certainly need the request 203 * object. 204 * 205 * @param ServerRequestInterface $request 206 * 207 * @return ResponseInterface 208 */ 209 public function getFooBarAction(ServerRequestInterface $request): ResponseInterface 210 { 211 // This assumes that there is a tree parameter in the URL. 212 $tree = $request->getAttribute('tree'); 213 214 // This will be a User (or a GuestUser for visitors). 215 $user = $request->getAttribute('user'); 216 217 $html = $tree->name() . '/' . $user->realName(); 218 219 return response($html); 220 } 221}; 222``` 223