X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/041923a89ece8b1ed195cb7b528843c15770ea6f..18695056917a2f34a36e5e89df7f01deff9ab640:/doc/plugins/write.mdwn?ds=sidebyside diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 77210d35c..cccfb9bba 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -19,7 +19,7 @@ that can be fleshed out to make a useful plugin. `IkiWiki::Plugin::pagecount` is another simple example. All perl plugins should `use IkiWiki` to import the ikiwiki plugin interface. It's a good idea to include the version number of the plugin interface that your plugin -expects: `use IkiWiki 2.00`. +expects: `use IkiWiki 3.00`. An external plugin is an executable program. It can be written in any language. Its interface to ikiwiki is via XML RPC, which it reads from @@ -55,8 +55,8 @@ plugin, and a "call" parameter, which tells what function to call for the hook. An optional "last" parameter, if set to a true value, makes the hook run -after all other hooks of its type. Useful if the hook depends on some other -hook being run first. +after all other hooks of its type, and an optional "first" parameter makes +it run first. Useful if the hook depends on some other hook being run first. ## Types of hooks @@ -107,7 +107,7 @@ adding or removing files from it. This hook is called early in the process of building the wiki, and is used as a first pass scan of the page, to collect metadata about the page. It's -mostly used to scan the page for WikiLinks, and add them to `%links`. +mostly used to scan the page for [[WikiLinks|ikiwiki/WikiLink]], and add them to `%links`. Present in IkiWiki 2.40 and later. The function is passed named parameters "page" and "content". Its return @@ -123,7 +123,7 @@ make arbitrary changes. The function is passed named parameters "page", ### preprocess -Adding a [[ikiwiki/PreProcessorDirective]] is probably the most common use +Adding a preprocessor [[ikiwiki/directive]] is probably the most common use of a plugin. hook(type => "preprocess", id => "foo", call => \&preprocess); @@ -158,7 +158,7 @@ can assume it's being run in scan mode, and avoid doing expensive things at that point.) Note that if the [[htmlscrubber]] is enabled, html in -[[ikiwiki/PreProcessorDirective]] output is sanitised, which may limit what +preprocessor [[ikiwiki/directive]] output is sanitised, which may limit what your plugin can do. Also, the rest of the page content is not in html format at preprocessor time. Text output by a preprocessor directive will be linkified and passed through markdown (or whatever engine is used to @@ -168,7 +168,7 @@ htmlize the page) along with the rest of the page. hook(type => "linkify", id => "foo", call => \&linkify); -This hook is called to convert [[WikiLinks|WikiLink]] on the page into html +This hook is called to convert [[WikiLinks|ikiwiki/WikiLink]] on the page into html links. The function is passed named parameters "page", "destpage", and "content". It should return the linkified content. Present in IkiWiki 2.40 and later. @@ -188,6 +188,15 @@ languages to ikiwiki. The function is passed named parameters: "page" and "content" and should return the htmlized content. +If `hook` is passed an optional "keepextension" parameter, set to a true +value, then the extension will not be stripped from the source filename when +generating the page. + +If `hook` is passed an optional "noextension" parameter, set to a true +value, then the id parameter specifies not a filename extension, but +a whole filename that can be htmlized. This is useful for files +like `Makefile` that have no extension. + ### pagetemplate hook(type => "pagetemplate", id => "foo", call => \&pagetemplate); @@ -299,7 +308,7 @@ can check if the session object has a "name" parameter set. ### canedit - hook(type => "canedit", id => "foo", call => \&pagelocked); + hook(type => "canedit", id => "foo", call => \&canedit); This hook can be used to implement arbitrary access methods to control when a page can be edited using the web interface (commits from revision control @@ -317,6 +326,47 @@ This hook should avoid directly redirecting the user to a signin page, since it's sometimes used to test to see which pages in a set of pages a user can edit. +### canremove + + hook(type => "canremove", id => "foo", call => \&canremove); + +This hook can be used to implement arbitrary access methods to control +when a page can be removed using the web interface (commits from +revision control bypass it). It works exactly like the `canedit` hook, +but is passed the named parameters `cgi` (a CGI object), `session` +(a session object) and `page` (the page subject to deletion). + +### canrename + + hook(type => "canrename", id => "foo", call => \&canrename); + +This hook can be used to implement arbitrary access methods to control when +a page can be renamed using the web interface (commits from revision control +bypass it). It works exactly like the `canedit` hook, +but is passed the named parameters `cgi` (a CGI object), `session` (a +session object), `src`, `srcfile`, `dest` and `destfile`. + +### checkcontent + + hook(type => "checkcontent", id => "foo", call => \&checkcontent); + +This hook is called to check the content a user has entered on a page, +before it is saved, and decide if it should be allowed. + +It is passed named parameters: `content`, `page`, `cgi`, and `session`. If +the content the user has entered is a comment, it may also be passed some +additional parameters: `author`, `url`, and `subject`. The `subject` +parameter may also be filled with the user's comment about the change. + +Note: When the user edits an existing wiki page, this hook is also +passed a `diff` named parameter, which will include only the lines +that they added to the page, or modified. + +The hook should return `undef` on success. If the content is disallowed, it +should return a message stating what the problem is, or a function +that can be run to perform whatever action is necessary to allow the user +to post the content. + ### editcontent hook(type => "editcontent", id => "foo", call => \&editcontent); @@ -356,14 +406,33 @@ This hook is called whenever ikiwiki normally saves its state, just before the state is saved. The function can save other state, modify values before they're saved, etc. -## renamepage +### renamepage hook(type => "renamepage", id => "foo", call => \&renamepage); This hook is called by the [[plugins/rename]] plugin when it renames -something. The hook is passed named parameters: `page`, `oldpage`, -`newpage`, and `content`, and should try to modify the content to reflect -the name change. For example, by converting links to point to the new page. +something, once per page linking to the renamed page's old location. +The hook is passed named parameters: `page`, `oldpage`, `newpage`, and +`content`, and should try to modify the content of `page` to reflect +the name change. For example, by converting links to point to the +new page. + +### rename + + hook(type => "rename", id => "foo", call => \&rename); + +When a page or set of pages is renamed, the referenced function is +called for every page, and is passed named parameters: + +* `torename`: a reference to a hash with keys: `src`, `srcfile`, + `dest`, `destfile`, `required`. +* `cgi`: a CGI object +* `session`: a session object. + +Such a hook function returns any additional rename hashes it wants to +add. This hook is applied recursively to returned additional rename +hashes, so that it handles the case where two plugins use the hook: +plugin A would see when plugin B adds a new file to be renamed. ### getsetup @@ -379,38 +448,55 @@ die if not, which will cause the plugin to not be offered in the configuration interface. The data returned is a list of `%config` options, followed by a hash -describing the option. For example: +describing the option. There can also be an item named "plugin", which +describes the plugin as a whole. For example: return option_foo => { type => "boolean", - description => "enable foo", + description => "enable foo?", + advanced => 1, safe => 1, rebuild => 1, }, option_bar => { type => "string", example => "hello", - description => "what to say", + description => "option bar", safe => 1, rebuild => 0, }, + plugin => { + description => "description of this plugin", + safe => 1, + rebuild => 1, + }, -* `type` can be "boolean", "string", "integer", "pagespec", or "internal" - (used for values that are not user-visible). The type is the type of - the leaf values; the `%config` option may be an array or hash of these. +* `type` can be "boolean", "string", "integer", "pagespec", + or "internal" (used for values that are not user-visible). The type is + the type of the leaf values; the `%config` option may be an array or + hash of these. * `example` can be set to an example value. * `description` is a short description of the option. +* `link` is a link to further information about the option. This can either + be a [[ikiwiki/WikiLink]], or an url. +* `advanced` can be set to true if the option is more suitable for advanced + users. * `safe` should be false if the option should not be displayed in unsafe configuration methods, such as the web interface. Anything that specifies a command to run, a path on disk, or a regexp should be marked as unsafe. -* `rebuild` should be true if changing the option will require a wiki rebuild. + If a plugin is marked as unsafe, that prevents it from being + enabled/disabled. +* `rebuild` should be true if changing the option (or enabling/disabling + the plugin) will require a wiki rebuild, false if no rebuild is needed, + and undef if a rebuild could be needed in some circumstances, but is not + strictly required. ## Plugin interface To import the ikiwiki plugin interface: - use IkiWiki '2.00'; + use IkiWiki '3.00'; This will import several variables and functions into your plugin's namespace. These variables and functions are the ones most plugins need, @@ -431,8 +517,8 @@ your ikiwiki setup file, which sets the hash content to configure the wiki. The `%pagestate` hash can be used by plugins to save state that they will need next time ikiwiki is run. The hash holds per-page state, so to set a value, -use `%pagestate{$page}{$id}{$key}=$value`, and to retrieve the value, -use `%pagestate{$page}{$id}{$key}`. +use `$pagestate{$page}{$id}{$key}=$value`, and to retrieve the value, +use `$pagestate{$page}{$id}{$key}`. The `$value` can be anything that perl's Storable module is capable of serializing. `$key` can be any string you like, but `$id` must be the same @@ -445,6 +531,15 @@ When pages are deleted, ikiwiki automatically deletes their pagestate too. Note that page state does not persist across wiki rebuilds, only across wiki updates. +### %wikistate + +The `%wikistate` hash can be used by a plugin to store persistant state +that is not bound to any one page. To set a value, use +`$wikistate{$id}{$key}=$value, where `$value` is anything Storable can +serialize, `$key` is any string you like, and `$id` must be the same as the +"id" parameter passed to `hook()` when registering the plugin, so that the +state can be dropped if the plugin is no longer used. + ### Other variables If your plugin needs to access data about other pages in the wiki. It can @@ -456,7 +551,7 @@ use the following hashes, using a page name as the key: destination file. * `%pagesources` contains the name of the source file for each page. -Also, the %IkiWiki::version variable contains the version number for the +Also, the `%IkiWiki::version` variable contains the version number for the ikiwiki program. ### Library functions @@ -482,7 +577,7 @@ function that is called after the error message is printed, to do any final cleanup. If called inside a preprocess hook, error() does not abort the entire -wiki build, but instead replaces the [[ikiwiki/PreProcessorDirective]] with +wiki build, but instead replaces the preprocessor [[ikiwiki/directive]] with a version containing the error message. In other hooks, error() is a fatal error, so use with care. Try to avoid @@ -500,6 +595,9 @@ parameters are passed to `HTML::Template->new`. Passed a page name, returns the base name that will be used for a the html page created from it. (Ie, it appends ".html".) +Use this when constructing the filename of a html file. Use `urlto` when +generating a link to a page. + #### `add_depends($$)` Makes the specified page depend on the specified [[ikiwiki/PageSpec]]. @@ -576,6 +674,16 @@ A failure to write the file will result in it dying with an error. If the destination directory doesn't exist, it will first be created. +The filename and directory are separate parameters because of +some security checks done to avoid symlink attacks. Before writing a file, +it checks to make sure there's not a symlink with its name, to avoid +following the symlink. If the filename parameter includes a subdirectory +to put the file in, it also checks if that subdirectory is a symlink, etc. +The directory parameter, however, is not checked for symlinks. So, +generally the directory parameter is a trusted toplevel directory like +the srcdir or destdir, and any subdirectories of this are included in the +filename parameter. + #### `will_render($$)` Given a page name and a destination file name (not including the base @@ -602,6 +710,23 @@ a type that ikiwiki knowns how to htmlize. Otherwise, returns undef. Given the name of a source file, returns the name of the wiki page that corresponds to that file. +#### `pagetitle($)` + +Give the name of a wiki page, returns a version suitable to be displayed as +the page's title. This is accomplished by de-escaping escaped characters in +the page name. "_" is replaced with a space, and '__NN__' is replaced by +the UTF character with code NN. + +#### `titlepage($)` + +This performs the inverse of `pagetitle`, ie, it converts a page title into +a wiki page name. + +#### `linkpage($)` + +This converts text that could have been entered by the user as a +[[ikiwiki/WikiLink]] into a wiki page name. + #### `srcfile($;$)` Given the name of a source file in the wiki, searches for the file in @@ -640,11 +765,21 @@ destination file, as registered by `will_render`. If the third parameter is passed and is true, an absolute url will be constructed instead of the default relative url. -#### `targetpage($$)` +#### `newpagefile($$)` + +This can be called when creating a new page, to determine what filename +to save the page to. It's passed a page name, and its type, and returns +the name of the file to create, relative to the srcdir. + +#### `targetpage($$;$)` Passed a page and an extension, returns the filename that page will be rendered to. +Optionally, a third parameter can be passed, to specify the preferred +filename of the page. For example, `targetpage("foo", "rss", "feed")` +will yield something like `foo/feed.rss`. + ## Miscellaneous ### Internal use pages @@ -655,7 +790,7 @@ are collected together to form the RecentChanges page, for example. To make an internal use page, register a filename extension that starts with "_". Internal use pages cannot be edited with the web interface, -generally shouldn't contain wikilinks or preprocessor directives (use +generally shouldn't contain [[WikiLinks|ikiwiki/WikiLink]] or preprocessor directives (use either on them with extreme caution), and are not matched by regular PageSpecs glob patterns, but instead only by a special `internal()` [[ikiwiki/PageSpec]]. @@ -701,8 +836,8 @@ Changes can be staged by calls to `rcs_add, `rcs_remove`, and Adds the passed file to the archive. The filename is relative to the root of the srcdir. -Note that this should not check the new file in, it should only -prepare for it to be checked in when rcs_commit (or `rcs_commit_staged`) is +Note that this should not commit the new file, it should only +prepare for it to be committed when rcs_commit (or `rcs_commit_staged`) is called. Note that the file may be in a new subdir that is not yet in to version control; the subdir can be added if so. @@ -710,9 +845,9 @@ to version control; the subdir can be added if so. Remove a file. The filename is relative to the root of the srcdir. -Note that this should not check the removal in, it should only prepare for it -to be checked in when `rcs_commit` (or `rcs_commit_staged`) is called. Note -that the new file may be in a new subdir that is not yet inversion +Note that this should not commit the removal, it should only prepare for it +to be committed when `rcs_commit` (or `rcs_commit_staged`) is called. Note +that the new file may be in a new subdir that is not yet in version control; the subdir can be added if so. #### `rcs_rename($$)` @@ -764,6 +899,30 @@ it up in the history. It's ok if this is not implemented, and throws an error. +#### `rcs_receive()` + +This is called when ikiwiki is running as a pre-receive hook (or +equivalent), and is testing if changes pushed into the RCS from an +untrusted user should be accepted. This is optional, and doesn't make +sense to implement for all RCSs. + +It should examine the incoming changes, and do any sanity +checks that are appropriate for the RCS to limit changes to safe file adds, +removes, and changes. If something bad is found, it should exit +nonzero, to abort the push. Otherwise, it should return a list of +files that were changed, in the form: + + { + file => # name of file that was changed + action => # either "add", "change", or "remove" + path => # temp file containing the new file content, only + # needed for "add"/"change", and only if the file + # is an attachment, not a page + } + +The list will then be checked to make sure that each change is one that +is allowed to be made via the web interface. + ### PageSpec plugins It's also possible to write plugins that add new functions to @@ -787,6 +946,85 @@ when imported, populate `$IkiWiki::Setup::raw_setup` with a reference to a hash containing all the config items. They should also implement a `gendump` function. -By the way, to parse a ikiwiki setup file, a program just needs to -do something like: -`use IkiWiki::Setup; my %setup=IkiWiki::Setup::load($filename)` +By the way, to parse a ikiwiki setup file and populate `%config`, a +program just needs to do something like: +`use IkiWiki::Setup; IkiWiki::Setup::load($filename)` + +### Function overriding + +Sometimes using ikiwiki's pre-defined hooks is not enough. Your plugin +may need to replace one of ikiwiki's own functions with a modified version, +or wrap one of the functions. + +For example, your plugin might want to override `displaytime`, to change +the html markup used when displaying a date. Or it might want to override +`IkiWiki::formattime`, to change how a date is formatted. Or perhaps you +want to override `bestlink` and change how ikiwiki deals with [[WikiLinks|ikiwiki/WikiLink]]. + +By venturing into this territory, your plugin is becoming tightly tied to +ikiwiki's internals. And it might break if those internals change. But +don't let that stop you, if you're brave. + +Ikiwiki provides an `inject()` function, that is a powerful way to replace +any function with one of your own. This even allows you to inject a +replacement for an exported function, like `bestlink`. Everything that +imports that function will get your version instead. Pass it the name of +the function to replace, and a new function to call. + +For example, here's how to replace `displaytime` with a version using HTML 5 +markup: + + inject(name => 'IkiWiki::displaytime', call => sub { + return ""; + }); + +Here's how to wrap `bestlink` with a version that tries to handle +plural words: + + my $origbestlink=\&bestlink; + inject(name => 'IkiWiki::bestlink', call => \&mybestlink); + + sub deplural ($) { + my $word=shift; + $word =~ s/e?s$//; # just an example :-) + return $word; + } + + sub mybestlink ($$) { + my $page=shift; + my $link=shift; + my $ret=$origbestlink->($page, $link); + if (! length $ret) { + $ret=$origbestlink->($page, deplural($link)); + } + return $ret; + } + +### Javascript + +Some plugins use javascript to make ikiwiki look a bit more web-2.0-ish. + +All javascript code should be put in `.js` files in the `javascript` +underlay, and plugins using those files can enable use of the underlay by +calling `add_underlay("javascript");` in their `import` function. + +You'll have to arrange for `