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