5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
9 if(!defined('DOKU_INC')) die('meh.');
12 * Call the needed action handlers
14 * @author Andreas Gohr <andi@splitbrain.org>
15 * @triggers ACTION_ACT_PREPROCESS
16 * @triggers ACTION_HEADERS_SEND
18 function act_dispatch(){
29 // give plugins an opportunity to process the action
30 $evt = new Doku_Event('ACTION_ACT_PREPROCESS',$ACT);
31 if ($evt->advise_before()) {
34 $ACT = act_validate($ACT);
36 //check if searchword was given - else just show
38 if($ACT == 'search' && empty($s)){
43 if(in_array($ACT,array('login','logout'))){
44 $ACT = act_auth($ACT);
47 //check if user is asking to (un)subscribe a page
48 if($ACT == 'subscribe') {
50 $ACT = act_subscription($ACT);
51 } catch (Exception $e) {
52 msg($e->getMessage(), -1);
63 $ACT = act_permcheck($ACT);
66 if ($ACT == 'sitemap'){
71 if($ACT == 'register' && $INPUT->post->bool('save') && register()){
75 if ($ACT == 'resendpwd' && act_resendpwd()) {
80 if ($ACT == 'profile') {
81 if(!$_SERVER['REMOTE_USER']) {
85 msg($lang['profchanged'],1);
93 if(checkSecurityToken()){
94 $ACT = act_revert($ACT);
102 if(checkSecurityToken()){
103 $ACT = act_save($ACT);
109 //cancel conflicting edit
114 if($ACT == 'draftdel')
115 $ACT = act_draftdel($ACT);
117 //draft saving on preview
118 if($ACT == 'preview')
119 $ACT = act_draftsave($ACT);
122 if(in_array($ACT, array('edit', 'preview', 'recover'))) {
123 $ACT = act_edit($ACT);
125 unlock($ID); //try to unlock
129 if(substr($ACT,0,7) == 'export_')
130 $ACT = act_export($ACT);
134 // retrieve admin plugin name from $_REQUEST['page']
135 if (($page = $INPUT->str('page', '', true)) != '') {
136 $pluginlist = plugin_list('admin');
137 if (in_array($page, $pluginlist)) {
138 // attempt to load the plugin
139 if ($plugin =& plugin_load('admin',$page) !== null){
140 /** @var DokuWiki_Admin_Plugin $plugin */
141 if($plugin->forAdminOnly() && !$INFO['isadmin']){
142 // a manager tried to load a plugin that's for admins only
143 $INPUT->remove('page');
144 msg('For admins only',-1);
153 // check permissions again - the action may have changed
154 $ACT = act_permcheck($ACT);
155 } // end event ACTION_ACT_PREPROCESS default action
156 $evt->advise_after();
157 // Make sure plugs can handle 'denied'
158 if($conf['send404'] && $ACT == 'denied') {
159 header('HTTP/1.0 403 Forbidden');
163 // when action 'show', the intial not 'show' and POST, do a redirect
164 if($ACT == 'show' && $preact != 'show' && strtolower($_SERVER['REQUEST_METHOD']) == 'post'){
165 act_redirect($ID,$preact);
172 //call template FIXME: all needed vars available?
173 $headers[] = 'Content-Type: text/html; charset=utf-8';
174 trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders');
176 include(template('main.php'));
177 // output for the commands is now handled in inc/templates.php
178 // in function tpl_content()
182 * Send the given headers using header()
184 * @param array $headers The headers that shall be sent
186 function act_sendheaders($headers) {
187 foreach ($headers as $hdr) header($hdr);
191 * Sanitize the action command
193 * @author Andreas Gohr <andi@splitbrain.org>
195 function act_clean($act){
196 // check if the action was given as array key
198 list($act) = array_keys($act);
201 //remove all bad chars
202 $act = strtolower($act);
203 $act = preg_replace('/[^1-9a-z_]+/','',$act);
205 if($act == 'export_html') $act = 'export_xhtml';
206 if($act == 'export_htmlbody') $act = 'export_xhtmlbody';
208 if($act === '') $act = 'show';
213 * Sanitize and validate action commands.
215 * Add all allowed commands here.
217 * @author Andreas Gohr <andi@splitbrain.org>
219 function act_validate($act) {
223 $act = act_clean($act);
225 // check if action is disabled
227 msg('Command disabled: '.htmlspecialchars($act),-1);
231 //disable all acl related commands if ACL is disabled
232 if(!$conf['useacl'] && in_array($act,array('login','logout','register','admin',
233 'subscribe','unsubscribe','profile','revert',
235 msg('Command unavailable: '.htmlspecialchars($act),-1);
239 //is there really a draft?
240 if($act == 'draft' && !file_exists($INFO['draft'])) return 'edit';
242 if(!in_array($act,array('login','logout','register','save','cancel','edit','draft',
243 'preview','search','show','check','index','revisions',
244 'diff','recent','backlink','admin','subscribe','revert',
245 'unsubscribe','profile','resendpwd','recover',
246 'draftdel','sitemap','media')) && substr($act,0,7) != 'export_' ) {
247 msg('Command unknown: '.htmlspecialchars($act),-1);
254 * Run permissionchecks
256 * @author Andreas Gohr <andi@splitbrain.org>
258 function act_permcheck($act){
262 if(in_array($act,array('save','preview','edit','recover'))){
265 //the edit function will check again and do a source show
266 //when no AUTH_EDIT available
267 $permneed = AUTH_READ;
269 $permneed = AUTH_EDIT;
272 $permneed = AUTH_CREATE;
274 }elseif(in_array($act,array('login','search','recent','profile','index', 'sitemap'))){
275 $permneed = AUTH_NONE;
276 }elseif($act == 'revert'){
277 $permneed = AUTH_ADMIN;
278 if($INFO['ismanager']) $permneed = AUTH_EDIT;
279 }elseif($act == 'register'){
280 $permneed = AUTH_NONE;
281 }elseif($act == 'resendpwd'){
282 $permneed = AUTH_NONE;
283 }elseif($act == 'admin'){
284 if($INFO['ismanager']){
285 // if the manager has the needed permissions for a certain admin
286 // action is checked later
287 $permneed = AUTH_READ;
289 $permneed = AUTH_ADMIN;
292 $permneed = AUTH_READ;
294 if($INFO['perm'] >= $permneed) return $act;
302 * Deletes the draft for the current page and user
304 function act_draftdel($act){
306 @unlink($INFO['draft']);
307 $INFO['draft'] = null;
312 * Saves a draft on preview
314 * @todo this currently duplicates code from ajax.php :-/
316 function act_draftsave($act){
321 if($conf['usedraft'] && $INPUT->post->has('wikitext')) {
322 $draft = array('id' => $ID,
323 'prefix' => substr($INPUT->post->str('prefix'), 0, -1),
324 'text' => $INPUT->post->str('wikitext'),
325 'suffix' => $INPUT->post->str('suffix'),
326 'date' => $INPUT->post->int('date'),
327 'client' => $INFO['client'],
329 $cname = getCacheName($draft['client'].$ID,'.draft');
330 if(io_saveFile($cname,serialize($draft))){
331 $INFO['draft'] = $cname;
340 * Checks for spam and conflicts and saves the page.
341 * Does a redirect to show the page afterwards or
342 * returns a new action.
344 * @author Andreas Gohr <andi@splitbrain.org>
346 function act_save($act){
358 if(checkwordblock()) {
359 msg($lang['wordblock'], -1);
363 if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE )
367 saveWikiText($ID,con($PRE,$TEXT,$SUF,1),$SUM,$INPUT->bool('minor')); //use pretty mode for con
373 session_write_close();
375 // when done, show page
380 * Revert to a certain revision
382 * @author Andreas Gohr <andi@splitbrain.org>
384 function act_revert($act){
388 // FIXME $INFO['writable'] currently refers to the attic version
390 // if (!$INFO['writable']) {
394 // when no revision is given, delete current one
395 // FIXME this feature is not exposed in the GUI currently
397 $sum = $lang['deleted'];
399 $text = rawWiki($ID,$REV);
400 if(!$text) return 'show'; //something went wrong
401 $sum = sprintf($lang['restored'], dformat($REV));
406 if (checkwordblock($text)) {
407 msg($lang['wordblock'], -1);
411 saveWikiText($ID,$text,$sum,false);
416 session_write_close();
418 // when done, show current page
419 $_SERVER['REQUEST_METHOD'] = 'post'; //should force a redirect
425 * Do a redirect after receiving post data
427 * Tries to add the section id as hash mark after section editing
429 function act_redirect($id,$preact){
437 //get section name when coming from section edit
438 if($PRE && preg_match('/^\s*==+([^=\n]+)/',$TEXT,$match)){
439 $check = false; //Byref
440 $opts['fragment'] = sectionID($match[0], $check);
443 trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute');
447 * Execute the redirect
449 * @param array $opts id and fragment for the redirect
451 function act_redirect_execute($opts){
452 $go = wl($opts['id'],'',true);
453 if(isset($opts['fragment'])) $go .= '#'.$opts['fragment'];
460 * Handle 'login', 'logout'
462 * @author Andreas Gohr <andi@splitbrain.org>
464 function act_auth($act){
469 if(isset($_SERVER['REMOTE_USER']) && $act=='login'){
475 $lockedby = checklock($ID); //page still locked?
476 if($lockedby == $_SERVER['REMOTE_USER'])
477 unlock($ID); //try to unlock
479 // do the logout stuff
482 // rebuild info array
485 act_redirect($ID,'login');
492 * Handle 'edit', 'preview', 'recover'
494 * @author Andreas Gohr <andi@splitbrain.org>
496 function act_edit($act){
510 if ($INFO['exists']) {
512 list($PRE,$TEXT,$SUF) = rawWikiSlices($RANGE,$ID,$REV);
514 $TEXT = rawWiki($ID,$REV);
517 $TEXT = pageTemplate($ID);
521 //set summary default
524 $SUM = sprintf($lang['restored'], dformat($REV));
525 }elseif(!$INFO['exists']){
526 $SUM = $lang['created'];
530 // Use the date of the newest revision, not of the revision we edit
531 // This is used for conflict detection
532 if(!$DATE) $DATE = @filemtime(wikiFN($ID));
534 //check if locked by anyone - if not lock for my self
535 //do not lock when the user can't edit anyway
536 if ($INFO['writable']) {
537 $lockedby = checklock($ID);
538 if($lockedby) return 'locked';
547 * Export a wiki page for various formats
549 * Triggers ACTION_EXPORT_POSTPROCESS
552 * data['id'] -- page id
553 * data['mode'] -- requested export mode
554 * data['headers'] -- export headers
555 * data['output'] -- export output
557 * @author Andreas Gohr <andi@splitbrain.org>
558 * @author Michael Klier <chi@chimeric.de>
560 function act_export($act){
571 // search engines: never cache exported docs! (Google only currently)
572 $headers['X-Robots-Tag'] = 'noindex';
574 $mode = substr($act,7);
577 $headers['Content-Type'] = 'text/plain; charset=utf-8';
578 $headers['Content-Disposition'] = 'attachment; filename='.noNS($ID).'.txt';
579 $output = rawWiki($ID,$REV);
582 $pre .= '<!DOCTYPE html>' . DOKU_LF;
583 $pre .= '<html lang="'.$conf['lang'].'" dir="'.$lang['direction'].'">' . DOKU_LF;
584 $pre .= '<head>' . DOKU_LF;
585 $pre .= ' <meta charset="utf-8" />' . DOKU_LF;
586 $pre .= ' <title>'.$ID.'</title>' . DOKU_LF;
591 $pre .= ob_get_clean();
593 $pre .= '</head>' . DOKU_LF;
594 $pre .= '<body>' . DOKU_LF;
595 $pre .= '<div class="dokuwiki export">' . DOKU_LF;
598 $pre .= tpl_toc(true);
600 $headers['Content-Type'] = 'text/html; charset=utf-8';
601 $output = p_wiki_xhtml($ID,$REV,false);
603 $post .= '</div>' . DOKU_LF;
604 $post .= '</body>' . DOKU_LF;
605 $post .= '</html>' . DOKU_LF;
608 $headers['Content-Type'] = 'text/html; charset=utf-8';
609 $output = p_wiki_xhtml($ID,$REV,false);
612 $output = p_cached_output(wikiFN($ID,$REV), $mode);
613 $headers = p_get_metadata($ID,"format $mode");
617 // prepare event data
620 $data['mode'] = $mode;
621 $data['headers'] = $headers;
622 $data['output'] =& $output;
624 trigger_event('ACTION_EXPORT_POSTPROCESS', $data);
626 if(!empty($data['output'])){
627 if(is_array($data['headers'])) foreach($data['headers'] as $key => $val){
628 header("$key: $val");
630 print $pre.$data['output'].$post;
637 * Handle sitemap delivery
639 * @author Michael Hamann <michael@content-space.de>
641 function act_sitemap($act) {
644 if ($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) {
645 header("HTTP/1.0 404 Not Found");
646 print "Sitemap generation is disabled.";
650 $sitemap = Sitemapper::getFilePath();
651 if (Sitemapper::sitemapIsCompressed()) {
652 $mime = 'application/x-gzip';
654 $mime = 'application/xml; charset=utf-8';
657 // Check if sitemap file exists, otherwise create it
658 if (!is_readable($sitemap)) {
659 Sitemapper::generate();
662 if (is_readable($sitemap)) {
664 header('Content-Type: '.$mime);
665 header('Content-Disposition: attachment; filename='.utf8_basename($sitemap));
667 http_conditionalRequest(filemtime($sitemap));
670 //use x-sendfile header to pass the delivery to compatible webservers
671 if (http_sendfile($sitemap)) exit;
677 header("HTTP/1.0 500 Internal Server Error");
678 print "Could not read the sitemap file - bad permissions?";
683 * Handle page 'subscribe'
685 * Throws exception on error.
687 * @author Adrian Lang <lang@cosmocode.de>
689 function act_subscription($act){
695 // subcriptions work for logged in users only
696 if(!$_SERVER['REMOTE_USER']) return 'show';
698 // get and preprocess data.
700 foreach(array('target', 'style', 'action') as $param) {
701 if ($INPUT->has("sub_$param")) {
702 $params[$param] = $INPUT->str("sub_$param");
706 // any action given? if not just return and show the subscription page
707 if(!$params['action'] || !checkSecurityToken()) return $act;
709 // Handle POST data, may throw exception.
710 trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, 'subscription_handle_post');
712 $target = $params['target'];
713 $style = $params['style'];
714 $data = $params['data'];
715 $action = $params['action'];
718 if (!subscription_set($_SERVER['REMOTE_USER'], $target, $style, $data)) {
719 throw new Exception(sprintf($lang["subscr_{$action}_error"],
720 hsc($INFO['userinfo']['name']),
721 prettyprint_id($target)));
723 msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
724 prettyprint_id($target)), 1);
725 act_redirect($ID, $act);
727 // Assure that we have valid data if act_redirect somehow fails.
728 $INFO['subscribed'] = get_info_subscribed();
735 * Validates POST data for a subscribe or unsubscribe request. This is the
736 * default action for the event ACTION_HANDLE_SUBSCRIBE.
738 * @author Adrian Lang <lang@cosmocode.de>
740 function subscription_handle_post(&$params) {
744 // Get and validate parameters.
745 if (!isset($params['target'])) {
746 throw new Exception('no subscription target given');
748 $target = $params['target'];
749 $valid_styles = array('every', 'digest');
750 if (substr($target, -1, 1) === ':') {
751 // Allow “list” subscribe style since the target is a namespace.
752 $valid_styles[] = 'list';
754 $style = valid_input_set('style', $valid_styles, $params,
755 'invalid subscription style given');
756 $action = valid_input_set('action', array('subscribe', 'unsubscribe'),
757 $params, 'invalid subscription action given');
759 // Check other conditions.
760 if ($action === 'subscribe') {
761 if ($INFO['userinfo']['mail'] === '') {
762 throw new Exception($lang['subscr_subscribe_noaddress']);
764 } elseif ($action === 'unsubscribe') {
766 foreach($INFO['subscribed'] as $subscr) {
767 if ($subscr['target'] === $target) {
772 throw new Exception(sprintf($lang['subscr_not_subscribed'],
773 $_SERVER['REMOTE_USER'],
774 prettyprint_id($target)));
776 // subscription_set deletes a subscription if style = null.
780 $data = in_array($style, array('list', 'digest')) ? time() : null;
781 $params = compact('target', 'style', 'data', 'action');
784 //Setup VIM: ex: et ts=2 :