]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - doc/md/dev/Plugin-system.md
Plugin system: allow plugins to provide custom routes
[github/shaarli/Shaarli.git] / doc / md / dev / Plugin-system.md
1 # Plugin system
2
3 ## Developer API
4
5 ### What can I do with plugins?
6
7 The plugin system lets you:
8
9 - insert content into specific places across templates.
10 - alter data before templates rendering.
11 - alter data before saving new links.
12
13
14 ### How can I create a plugin for Shaarli?
15
16 First, chose a plugin name, such as `demo_plugin`.
17
18 Under `plugin` folder, create a folder named with your plugin name. Then create a <plugin_name>.meta file and a <plugin_name>.php file in that folder.
19
20 You should have the following tree view:
21
22 ```
23 | index.php
24 | plugins/
25 |---| demo_plugin/
26 | |---| demo_plugin.meta
27 | |---| demo_plugin.php
28 ```
29
30
31 ### Plugin initialization
32
33 At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function in the <plugin_name>.php to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter.
34
35 <plugin_name>_init($conf)
36
37 This function can be used to create initial data, load default settings, etc. But also to set *plugin errors*. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users.
38
39 The plugin system also looks for a `description` variable in the <plugin_name>.meta file, to be displayed in the plugin administration page.
40
41 description="The plugin does this and that."
42
43 ### Understanding hooks
44
45 A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.
46
47 These functions need to be named with this pattern:
48
49 ```
50 hook_<plugin_name>_<hook_name>($data, $conf)
51 ```
52
53 Parameters:
54
55 - data: see [$data section](https://shaarli.readthedocs.io/en/master/Plugin-System/#plugins-data)
56 - conf: the `ConfigManager` instance.
57
58 For example, if my plugin want to add data to the header, this function is needed:
59
60 hook_demo_plugin_render_header
61
62 If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.
63
64
65 ### Plugin's data
66
67 #### Parameters
68
69 Every hook function has a `$data` parameter. Its content differs for each hooks.
70
71 **This parameter needs to be returned every time**, otherwise data is lost.
72
73 return $data;
74
75 #### Special data
76
77 Special additional data are passed to every hook through the
78 `$data` parameter to give you access to additional context, and services.
79
80 Complete list:
81
82 * `_PAGE_` (string): if the current hook is used to render a template, its name is passed through this additional parameter.
83 * `_LOGGEDIN_` (bool): whether the user is logged in or not.
84 * `_BASE_PATH_` (string): if Shaarli instance is hosted under a subfolder, contains the subfolder path to `index.php` (e.g. `https://domain.tld/shaarli/` -> `/shaarli/`).
85 * `_BOOKMARK_SERVICE_` (`BookmarkServiceInterface`): bookmark service instance, for advanced usage.
86
87 Example:
88
89 ```php
90 if ($data['_PAGE_'] === TemplatePage::LINKLIST && $data['LOGGEDIN'] === true) {
91 // Do something for logged in users when the link list is rendered
92 }
93 ```
94
95 #### Filling templates placeholder
96
97 Template placeholders are displayed in template in specific places.
98
99 RainTPL displays every element contained in the placeholder's array. These element can be added by plugins.
100
101 For example, let's add a value in the placeholder `top_placeholder` which is displayed at the top of my page:
102
103 ```php
104 $data['top_placeholder'][] = 'My content';
105 # OR
106 array_push($data['top_placeholder'], 'My', 'content');
107
108 return $data;
109 ```
110
111
112 #### Data manipulation
113
114 When a page is displayed, every variable send to the template engine is passed to plugins before that in `$data`.
115
116 The data contained by this array can be altered before template rendering.
117
118 For example, in linklist, it is possible to alter every title:
119
120 ```php
121 // mind the reference if you want $data to be altered
122 foreach ($data['links'] as &$value) {
123 // String reverse every title.
124 $value['title'] = strrev($value['title']);
125 }
126
127 return $data;
128 ```
129
130 ### Metadata
131
132 Every plugin needs a `<plugin_name>.meta` file, which is in fact an `.ini` file (`KEY="VALUE"`), to be listed in plugin administration.
133
134 Each file contain two keys:
135
136 - `description`: plugin description
137 - `parameters`: user parameter names, separated by a `;`.
138 - `parameter.<PARAMETER_NAME>`: add a text description the specified parameter.
139
140 > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file.
141
142 ### Register plugin's routes
143
144 Shaarli lets you register custom Slim routes for your plugin.
145
146 To register a route, the plugin must include a function called `function <plugin_name>_register_routes(): array`.
147
148 This method must return an array of routes, each entry must contain the following keys:
149
150 - `method`: HTTP method, `GET/POST/PUT/PATCH/DELETE`
151 - `route` (path): without prefix, e.g. `/up/{variable}`
152 It will be later prefixed by `/plugin/<plugin name>/`.
153 - `callable` string, function name or FQN class's method to execute, e.g. `demo_plugin_custom_controller`.
154
155 Callable functions or methods must have `Slim\Http\Request` and `Slim\Http\Response` parameters
156 and return a `Slim\Http\Response`. We recommend creating a dedicated class and extend either
157 `ShaarliVisitorController` or `ShaarliAdminController` to use helper functions they provide.
158
159 A dedicated plugin template is available for rendering content: `pluginscontent.html` using `content` placeholder.
160
161 > **Warning**: plugins are not able to use RainTPL template engine for their content due to technical restrictions.
162 > RainTPL does not allow to register multiple template folders, so all HTML rendering must be done within plugin
163 > custom controller.
164
165 Check out the `demo_plugin` for a live example: `GET <shaarli_url>/plugin/demo_plugin/custom`.
166
167 ### Understanding relative paths
168
169 Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder.
170 This means that you can *never* use absolute paths (eg `/plugins/mything/file.png`).
171
172 If a file needs to be included in server end, use simple relative path:
173 `PluginManager::$PLUGINS_PATH . '/mything/template.html'`.
174
175 If it needs to be included in front end side (e.g. an image),
176 the relative path must be prefixed with special data:
177
178 * if it's a link that will need to be processed by Shaarli, use `_BASE_PATH_`:
179 for e.g. `$data['_BASE_PATH_'] . '/admin/tools`.
180 * if you want to include an asset, you need to add the root URL (base path without `/index.php`, for people using Shaarli without URL rewriting), then use `_ROOT_PATH_`:
181 for e.g
182 `$['_ROOT_PATH_'] . '/' . PluginManager::$PLUGINS_PATH . '/mything/picture.png`.
183
184 Note that special placeholders for CSS and JS files (respectively `css_files` and `js_files`) are already prefixed
185 with the root path in template files.
186
187 ### It's not working!
188
189 Use `demo_plugin` as a functional example. It covers most of the plugin system features.
190
191 If it's still not working, please [open an issue](https://github.com/shaarli/Shaarli/issues/new).
192
193
194 ### Hooks
195
196 | Hooks | Description |
197 | ------------- |:-------------:|
198 | [render_header](#render_header) | Allow plugin to add content in page headers. |
199 | [render_includes](#render_includes) | Allow plugin to include their own CSS files. |
200 | [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. |
201 | [render_linklist](#render_linklist) | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. |
202 | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |
203 | [render_tools](#render_tools) | Allow to add content at the end of the page. |
204 | [render_picwall](#render_picwall) | Allow to add content at the top and bottom of the page. |
205 | [render_tagcloud](#render_tagcloud) | Allow to add content at the top and bottom of the page, and after all tags. |
206 | [render_taglist](#render_taglist) | Allow to add content at the top and bottom of the page, and after all tags. |
207 | [render_daily](#render_daily) | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |
208 | [render_feed](#render_feed) | Allow to do add tags in RSS and ATOM feeds. |
209 | [save_link](#save_link) | Allow to alter the link being saved in the datastore. |
210 | [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. |
211 | [save_plugin_parameters](#save_plugin_parameters) | Allow to manipulate plugin parameters before they're saved. |
212
213
214 #### render_header
215
216 Triggered on every page - allows plugins to add content in page headers.
217
218
219 ##### Data
220
221 `$data` is an array containing:
222
223 - [Special data](#special-data)
224
225 ##### Template placeholders
226
227 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
228
229 List of placeholders:
230
231 - `buttons_toolbar`: after the list of buttons in the header.
232
233 ![buttons_toolbar_example](http://i.imgur.com/ssJUOrt.png)
234
235 - `fields_toolbar`: after search fields in the header.
236
237 > Note: This will only be called in linklist.
238
239 ![fields_toolbar_example](http://i.imgur.com/3GMifI2.png)
240
241
242 #### render_includes
243
244 Triggered on every page - allows plugins to include their own CSS files.
245
246 ##### data
247
248 `$data` is an array containing:
249
250 - [Special data](#special-data)
251
252 ##### Template placeholders
253
254 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
255
256 List of placeholders:
257
258 - `css_files`: called after loading default CSS.
259
260 > Note: only add the path of the CSS file. E.g: `plugins/demo_plugin/custom_demo.css`.
261
262
263 #### render_footer
264
265 Triggered on every page.
266
267 Allow plugin to add content in page footer and include their own JS files.
268
269 ##### data
270
271 `$data` is an array containing:
272
273 - [Special data](#special-data)
274
275 ##### Template placeholders
276
277 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
278
279 List of placeholders:
280
281 - `text`: called after the end of the footer text.
282 - `endofpage`: called at the end of the page.
283
284 ![text_example](http://i.imgur.com/L5S2YEH.png)
285
286 - `js_files`: called at the end of the page, to include custom JS scripts.
287
288 > Note: only add the path of the JS file. E.g: `plugins/demo_plugin/custom_demo.js`.
289
290
291 #### render_linklist
292
293 Triggered when `linklist` is displayed (list of links, permalink, search, tag filtered, etc.).
294
295 It allows to add content at the begining and end of the page, after every link displayed and to alter link data.
296
297 ##### data
298
299 `$data` is an array containing:
300
301 - All templates data, including links.
302 - [Special data](#special-data)
303
304 ##### template placeholders
305
306 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
307
308 List of placeholders:
309
310 - `action_plugin`: next to the button "private only" at the top and bottom of the page.
311
312 ![action_plugin_example](http://i.imgur.com/Q12PWg0.png)
313
314 - `link_plugin`: for every link, between permalink and link URL.
315
316 ![link_plugin_example](http://i.imgur.com/3oDPhWx.png)
317
318 - `plugin_start_zone`: before displaying the template content.
319
320 ![plugin_start_zone_example](http://i.imgur.com/OVBkGy3.png)
321
322 - `plugin_end_zone`: after displaying the template content.
323
324 ![plugin_end_zone_example](http://i.imgur.com/6IoRuop.png)
325
326
327 #### render_editlink
328
329 Triggered when the link edition form is displayed.
330
331 Allow to add fields in the form, or display elements.
332
333 ##### data
334
335 `$data` is an array containing:
336
337 - All templates data.
338 - [Special data](#special-data)
339
340 ##### template placeholders
341
342 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
343
344 List of placeholders:
345
346 - `edit_link_plugin`: after tags field.
347
348 ![edit_link_plugin_example](http://i.imgur.com/5u17Ens.png)
349
350
351 #### render_tools
352
353 Triggered when the "tools" page is displayed.
354
355 Allow to add content at the end of the page.
356
357 ##### data
358
359 `$data` is an array containing:
360
361 - All templates data.
362 - [Special data](#special-data)
363
364 ##### template placeholders
365
366 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
367
368 List of placeholders:
369
370 - `tools_plugin`: at the end of the page.
371
372 ![tools_plugin_example](http://i.imgur.com/Bqhu9oQ.png)
373
374
375 #### render_picwall
376
377 Triggered when picwall is displayed.
378
379 Allow to add content at the top and bottom of the page.
380
381 ##### data
382
383 `$data` is an array containing:
384
385 - All templates data.
386 - [Special data](#special-data)
387
388 ##### template placeholders
389
390 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
391
392 List of placeholders:
393
394 - `plugin_start_zone`: before displaying the template content.
395 - `plugin_end_zone`: after displaying the template content.
396
397 ![plugin_start_end_zone_example](http://i.imgur.com/tVTQFER.png)
398
399
400 #### render_tagcloud
401
402 Triggered when tagcloud is displayed.
403
404 Allow to add content at the top and bottom of the page.
405
406 ##### data
407
408 `$data` is an array containing:
409
410 - All templates data.
411 - [Special data](#special-data)
412
413 ##### Template placeholders
414
415 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
416
417 List of placeholders:
418
419 - `plugin_start_zone`: before displaying the template content.
420 - `plugin_end_zone`: after displaying the template content.
421
422 For each tag, the following placeholder can be used:
423
424 - `tag_plugin`: after each tag
425
426 ![plugin_start_end_zone_example](http://i.imgur.com/vHmyT3a.png)
427
428
429 #### render_taglist
430
431 Triggered when taglist is displayed - allows to add content at the top and bottom of the page.
432
433 ##### data
434
435 `$data` is an array containing:
436
437 - All templates data.
438 - [Special data](#special-data)
439
440 ##### Template placeholders
441
442 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
443
444 List of placeholders:
445
446 - `plugin_start_zone`: before displaying the template content.
447 - `plugin_end_zone`: after displaying the template content.
448
449 For each tag, the following placeholder can be used:
450
451 - `tag_plugin`: after each tag
452
453 #### render_daily
454
455 Triggered when tagcloud is displayed.
456
457 Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.
458
459
460 ##### data
461
462 `$data` is an array containing:
463
464 - All templates data, including links.
465 - [Special data](#special-data)
466
467 ##### Template placeholders
468
469 Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
470
471 List of placeholders:
472
473 - `link_plugin`: used at bottom of each link.
474
475 ![link_plugin_example](http://i.imgur.com/hzhMfSZ.png)
476
477 - `plugin_start_zone`: before displaying the template content.
478 - `plugin_end_zone`: after displaying the template content.
479
480
481 #### render_feed
482
483 Triggered when the ATOM or RSS feed is displayed.
484
485 Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered.
486
487 ##### data
488
489 `$data` is an array containing:
490
491 - All templates data, including links.
492 - [Special data](#special-data)
493
494 ##### Template placeholders
495
496 Tags can be added in feeds by adding an entry in `$data['<placeholder>']` array.
497
498 List of placeholders:
499
500 - `feed_plugins_header`: used as a header tag in the feed.
501
502 For each links:
503
504 - `feed_plugins`: additional tag for every link entry.
505
506
507 #### save_link
508
509 Triggered when a link is save (new link or edit).
510
511 Allow to alter the link being saved in the datastore.
512
513 ##### data
514
515 `$data` is an array containing the link being saved:
516
517 - id
518 - title
519 - url
520 - shorturl
521 - description
522 - private
523 - tags
524 - created
525 - updated
526
527 Also [special data](#special-data).
528
529
530 #### delete_link
531
532 Triggered when a link is deleted.
533
534 Allow to execute any action before the link is actually removed from the datastore
535
536 ##### data
537
538 `$data` is an array containing the link being deleted:
539
540 - id
541 - title
542 - url
543 - shorturl
544 - description
545 - private
546 - tags
547 - created
548 - updated
549
550 Also [special data](#special-data).
551
552 #### save_plugin_parameters
553
554 Triggered when the plugin parameters are saved from the plugin administration page.
555
556 Plugins can perform an action every times their settings are updated.
557 For example it is used to update the CSS file of the `default_colors` plugins.
558
559 ##### data
560
561 `$data` input contains the `$_POST` array.
562
563 So if the plugin has a parameter called `MYPLUGIN_PARAMETER`,
564 the array will contain an entry with `MYPLUGIN_PARAMETER` as a key.
565
566 Also [special data](#special-data).
567
568 ## Guide for template designers
569
570 ### Plugin administration
571
572 Your theme must include a plugin administration page: `pluginsadmin.html`.
573
574 > Note: repo's template link needs to be added when the PR is merged.
575
576 Use the default one as an example.
577
578 Aside from classic RainTPL loops, plugins order is handle by JavaScript. You can just include `plugin_admin.js`, only if:
579
580 - you're using a table.
581 - you call orderUp() and orderUp() onclick on arrows.
582 - you add data-line and data-order to your rows.
583
584 Otherwise, you can use your own JS as long as this field is send by the form:
585
586 <input type="hidden" name="order_{$key}" value="{$counter}">
587
588 ### Placeholder system
589
590 In order to make plugins work with every custom themes, you need to add variable placeholder in your templates.
591
592 It's a RainTPL loop like this:
593
594 {loop="$plugin_variable"}
595 {$value}
596 {/loop}
597
598 You should enable `demo_plugin` for testing purpose, since it uses every placeholder available.
599
600 ### List of placeholders
601
602 **page.header.html**
603
604 At the end of the menu:
605
606 {loop="$plugins_header.buttons_toolbar"}
607 {$value}
608 {/loop}
609
610 At the end of file, before clearing floating blocks:
611
612 {if="!empty($plugin_errors) && $is_logged_in"}
613 <ul class="errors">
614 {loop="plugin_errors"}
615 <li>{$value}</li>
616 {/loop}
617 </ul>
618 {/if}
619
620 **includes.html**
621
622 At the end of the file:
623
624 ```html
625 {loop="$plugins_includes.css_files"}
626 <link type="text/css" rel="stylesheet" href="{$value}#"/>
627 {/loop}
628 ```
629
630 **page.footer.html**
631
632 At the end of your footer notes:
633
634 ```html
635 {loop="$plugins_footer.text"}
636 {$value}
637 {/loop}
638 ```
639
640 At the end of file:
641
642 ```html
643 {loop="$plugins_footer.js_files"}
644 <script src="{$value}#"></script>
645 {/loop}
646 ```
647
648 **linklist.html**
649
650 After search fields:
651
652 ```html
653 {loop="$plugins_header.fields_toolbar"}
654 {$value}
655 {/loop}
656 ```
657
658 Before displaying the link list (after paging):
659
660 ```html
661 {loop="$plugin_start_zone"}
662 {$value}
663 {/loop}
664 ```
665
666 For every links (icons):
667
668 ```html
669 {loop="$value.link_plugin"}
670 <span>{$value}</span>
671 {/loop}
672 ```
673
674 Before end paging:
675
676 ```html
677 {loop="$plugin_end_zone"}
678 {$value}
679 {/loop}
680 ```
681
682 **linklist.paging.html**
683
684 After the "private only" icon:
685
686 ```html
687 {loop="$action_plugin"}
688 {$value}
689 {/loop}
690 ```
691
692 **editlink.html**
693
694 After tags field:
695
696 ```html
697 {loop="$edit_link_plugin"}
698 {$value}
699 {/loop}
700 ```
701
702 **tools.html**
703
704 After the last tool:
705
706 ```html
707 {loop="$tools_plugin"}
708 {$value}
709 {/loop}
710 ```
711
712 **picwall.html**
713
714 Top:
715
716 ```html
717 <div id="plugin_zone_start_picwall" class="plugin_zone">
718 {loop="$plugin_start_zone"}
719 {$value}
720 {/loop}
721 </div>
722 ```
723
724 Bottom:
725
726 ```html
727 <div id="plugin_zone_end_picwall" class="plugin_zone">
728 {loop="$plugin_end_zone"}
729 {$value}
730 {/loop}
731 </div>
732 ```
733
734 **tagcloud.html**
735
736 Top:
737
738 ```html
739 <div id="plugin_zone_start_tagcloud" class="plugin_zone">
740 {loop="$plugin_start_zone"}
741 {$value}
742 {/loop}
743 </div>
744 ```
745
746 Bottom:
747
748 ```html
749 <div id="plugin_zone_end_tagcloud" class="plugin_zone">
750 {loop="$plugin_end_zone"}
751 {$value}
752 {/loop}
753 </div>
754 ```
755
756 **daily.html**
757
758 Top:
759
760 ```html
761 <div id="plugin_zone_start_picwall" class="plugin_zone">
762 {loop="$plugin_start_zone"}
763 {$value}
764 {/loop}
765 </div>
766 ```
767
768 After every link:
769
770 ```html
771 <div class="dailyEntryFooter">
772 {loop="$link.link_plugin"}
773 {$value}
774 {/loop}
775 </div>
776 ```
777
778 Bottom:
779
780 ```html
781 <div id="plugin_zone_end_picwall" class="plugin_zone">
782 {loop="$plugin_end_zone"}
783 {$value}
784 {/loop}
785 </div>
786 ```
787
788 **feed.atom.xml** and **feed.rss.xml**:
789
790 In headers tags section:
791 ```xml
792 {loop="$feed_plugins_header"}
793 {$value}
794 {/loop}
795 ```
796
797 After each entry:
798 ```xml
799 {loop="$value.feed_plugins"}
800 {$value}
801 {/loop}
802 ```