====== Tips for programming plugins ====== Please write down tips you've discovered making it easier for others to [[plugins|make plugins]]. I actually had to sit down and fgrep myself to this info, and I hope that it will help others ;-) On this page are some tips (see ToC right). Useful resources elsewhere in the wiki are: * [[common plugin functions#Configuration]] * [[common plugin functions#Localization]] * Plugin [[plugin file structure|file structure]] and [[plugin file structure|name conventions]] * [[devel:badextensions|Conflicting Versions and Basenames]] =====Customized Section editing===== When you like to change only small pieces of the wiki text via your plugin, it is recommended to look for section editing. This let you provide your interface elements that can edit a specific marked piece of wiki text. Please refer to [[section editor]] for details on the implementation in your plugin. ===== User lists and info ===== You can access user-lists/info and more for internal use by declaring the following in a function that needs it: global $auth; if ($auth->canDo('getUsers')) { // is this feature available? $auth->retrieveUsers(0, 0, $filter); } Where ''$filter'' is an array with one or more of the following keys ''user'', ''name'', ''mail'', or ''grps''. Several values in each using ''|'' as a separator. For example, to retrieve all users in the group 'admin', one would use: $filter['grps'] = 'admin'; $array_of_matches = $auth->retrieveUsers(0, 0, $filter); Be aware that the authentication plugin needs to implement this function. Otherwise it returns always an empty array. See also [[devel:auth_plugins#retrieveusers|authentication plugins]] mention of ''retrieveUsers()'' and other functions. ===== DokuWiki Global Variables ===== DokuWiki uses a number of [[devel:environment#global variables]] to hold information about the current page, current user and the actions being performed.\\ Details of these can be found on the [[devel:environment]] page. ===== Making your plugins more secure ===== ===Clean all input and output=== Be aware that every one can do 'requests' on your forms and pages with all data they think off. People can send the same kind of data as a brave one using your form, without using your form itself. So everything can be included. So check always whether the given data is that what __you allowed__. And when you output data, especially in the HTML of your interface, your plugin should ensure any data output has all HTML special characters converted to HTML entities using the [[phpfn>htmlspecialchars|htmlspecialchars()]] function. DokuWiki provides a convenient shortcut called [[xref>hsc()]] for the function. URLs values should be escaped using [[phpfn>rawurlencode|rawurlencode()]]. This makes sure input is always outputted as text and not as executable code. Malicious users could otherwise introduce what ever JavaScript and HTML code they want on your site and can manipulate and change everything. ===Protect forms and action urls=== If you use forms in your plugins or urls that initiate actions, you should include a hidden form field with the session-based security token. In the current version of DokuWiki you can generate this field by calling the function [[xref>formSecurityToken()]]. Before you process the form input, call [[xref>checkSecurityToken()]]. This function checks if the sent security token is correct. //Scenario// If you wonder, why this will make your plugins more secure, consider the following scenario: >You have written a plugin that displays a form to delete several pages at once.\\ An attacker knows you regularly log in to your wiki and you use a site that is under his control.\\ He places an images tag on his page that links to your doku.php and has all the form parameters for deleting pages in the URL.\\ Each time you see the page form the attacker, your browser requests the image from your DokuWiki installation, thereby deleting pages. This attack is called [[http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html|Cross Site Request forgery]]. Other security tips are listed and explained [[devel:security|on the dedicated page.]] ===== Use correct regular expressions ===== Use correct regular expressions for syntax search patterns. If the search pattern is incorrect, it can produce unwanted effects in combination with other plugins. Use reasonable tag names to avoid conflicts with other plugins. For example don't use a name like ''test'' but ''pluginname_test'' instead. Maybe check existing search patterns here [[devel:plugin_survey:syntax]], but don't use them as example because many of them are incorrect regular expressions. ==== Correct regular expression ==== .*? \r\n]*?>.*? \r\n]*?>[^\r\n]*? (?:.*?) \r\n]*")*\s*>(?:.*?) ~~tag\b.*?~~ ~~tag>.+?~~ ~~tag>[^\r\n]+?~~ ==== False regular expression ==== Example 1: .*? .*? .*? Start tag is not a word, the end-of-word marker ''\b'' is missing, any pattern for example tagmore or taged is found too. This produces a wrong result in this case: Text Text Example 2: .*? .* The search pattern is "greedy", the not-greedy marker ''?'' is missing, to long pieces are included in one search match. This produces a wrong result in this case: Text Text ===== Spam prevention ===== When you offer a form in your plugin and this can be also used by public users of a wiki, it's recommended to use CAPTCHA to defeat spambots. There is already a [[plugin:CAPTCHA]] plugin available, which provides different formats, visible and invisible CAPTCHAs. See the plugin page for description of the [[plugin:captcha#helper_methods|integration]] and the [[plugin:captcha#configuration]] options. ===Example implementation=== The [[plugin:Bureaucracy]] plugin supports this plugin. The CAPTCHA is integrated in the submit button, see [[https://github.com/splitbrain/dokuwiki-plugin-bureaucracy/blob/master/fields/submit.php|submit.php]]. ===== Adding JavaScript ===== ===Modify your wiki via local JavaScripts=== If you need to enhance DokuWiki's capabilities, you can consider JavaScript beside creating a new plugin.\\ Just put the JavaScript code into ''conf/userscript.js'' //(create this file if it doesn't exists)//. Examples JavaScripts additions: [[tips:wordcounter]] or [[tips:copy_section_link]] ===Distribute JavaScript and CSS files by pseudo plugins=== If you want to add some JavaScript and CSS at the same time and make it easier to distribute, you can create a //'pseudo'// plugin. Create a new folder and add a ''script.js'' and/or a ''style.css'' file to it. Add this folder to ''lib/plugins/'' and complete it by adding the ''[[plugin_info|plugin.info.txt]]''. Examples pseudo plugins: [[plugin:searchjump]] or [[plugin:ipa]] More about [[plugin file structure#JavaScript]] and [[plugin file structure#CSS styles]] in plugins. =====Using cookies===== When you like to store some preferences, you can add to and retrieve of DokuWikis preferences cookie by: * in PHP: * set a value ''[[xref>set_doku_pref|set_doku_pref($pref, $value)]]'' * delete an entry ''[[xref>set_doku_pref|set_doku_pref($pref, false)]]'' * and retrieve its value with ''[[xref>get_doku_pref|get_doku_pref($pref, $default)]]'' * and in javascript: * set a value ''[[xref>lib/scripts/cookie.js|DokuCookie.setValue(pref, value)]]'' * and retrieve value ''[[xref>lib/scripts/cookie.js|DokuCookie.getValue(pref)]]'' For other usages you use a separated cookie. Next snippet shows how you set cookies with the correct path in DokuWiki. In PHP: global $conf; $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; setCookie("yourCookieName", $value, $expire, $cookieDir, '', ($conf['securecookie'] && is_ssl())); and in javascript: jQuery.cookie("yourCookieName", value, { expires: 7, //days path: DOKU_COOKIE_PARAM.path, secure: DOKU_COOKIE_PARAM.secure }); More general info about cookies: [[faq:cookies|DokuWiki's cookies]], [[config:cookiedir]] config, [[config:securecookie]] config. ===== Handle JSON ajax request ===== An action plugin that register the [[devel:event:ajax_call_unknown|AJAX_CALL_UNKNOWN]] event, you can handle your own ajax requests. Here a sample how you can return JSON to your javascript. Plugin name is ''example''. Create an [[Action Plugin]] which should contain: use dokuwiki\Extension\ActionPlugin; use dokuwiki\Extension\EventHandler; use dokuwiki\Extension\Event; class action_plugin_example extends ActionPlugin { /** * plugin should use this method to register its handlers * with the dokuwiki's event controller */ public function register(EventHandler $controller) { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'ajaxCall'); } /** * handle ajax requests */ public function ajaxCall(Event $event) { if ($event->data !== 'plugin_example') { return; } //no other ajax call handlers needed $event->stopPropagation(); $event->preventDefault(); //e.g. access additional request variables global $INPUT; $name = $INPUT->str('name'); //data $data = []; //set content type header('Content-Type: application/json'); echo json_encode($data); } } You can request the data in javascript, for example: jQuery.post( DOKU_BASE + 'lib/exe/ajax.php', { call: 'plugin_example', name: 'local' }, function(data) { alert('Received response'); // data is array you returned with action.php }, 'json' ); ===== Checking user-input text against the DokuWiki blacklist ===== You have some user input you want to validate using the internal blacklist (e.g. as an anti-spam measure for comments or anything like that). Make a temporary backup of the current content of the internal ''$TEXT'' variable (which usually contains the wiki text of the displayed page), then put your text into ''$TEXT''. A call to ''checkwordblock()'' should then yield a result of ''true'' if the processed text contains any blocked words, and ''false'' otherwise. Don't forget to restore the original content of ''$TEXT'' after you are done checking your user input by copying it back from the backup you made. Example: // backup and reset the current $TEXT variable $backupText = $TEXT; $TEXT = $userinput; if (checkwordblock()) { // text contains blocked words ... } // restore $TEXT $TEXT = $backupText; ===== Disabling syntax plugins in user comments ===== You have written a syntax plugin which will be used in a restricted environment where anonymous users are not allowed to edit pages but allowed to comment using the [[plugin:discussion|discussion plugin]]. You do not want to disable wiki syntax in comments completely, but want to disable the syntax provided by your plugin as it can become a risk in the wrong hands. In your plugin's ''handle'' method, check whether ''$_REQUEST['comment']'' is set -- this indicates that the parsing process is working on a discussion comment. Example: global $INPUT; // we are parsing a submitted comment... if ($INPUT->has('comment')) { return false; } ===== Sending popularity data ===== Since release 2015-08-10 "Detritus" :!: As a plugin developer, beware: since popularity data is public, you must not send sensitive information with this feature. The [[plugin:popularity]] plugin already gather the number of time a plugin is installed on an instance of Dokuwiki. It also let the possibility to developers, to send more data about usage. It can be used by plugins developers to know if a given obsolete feature is still used. To do it, you need to subscribe to the [[devel:event:PLUGIN_POPULARITY_DATA_SETUP]] event. This event contains a key-value array. You should add a key which is the name of your plugin. The value should be either a string, or a key-value array itself (in this latter case, the data will be sent with the key ''_'') Example: use dokuwiki\Extension\ActionPlugin; use dokuwiki\Extension\EventHandler; use dokuwiki\Extension\Event; class action_plugin_example extends ActionPlugin { public function register(EventHandler $controller) { $controller->register_hook('PLUGIN_POPULARITY_DATA_SETUP', 'AFTER', $this, 'usageData'); } public function usageData(Event $event){ $event->data['my_plugin_name'] = 'my usage data'; /* or: $event->data['my_plugin_name'] = [ 'k1' => 'v1', 'k2' => 'v2' ]; */ } } A plugin which uses this feature is the [[plugin:nspages]] Plugin.