1<?php 2/** 3 * DokuWiki Actions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9if(!defined('DOKU_INC')) die('meh.'); 10 11/** 12 * Call the needed action handlers 13 * 14 * @author Andreas Gohr <andi@splitbrain.org> 15 * @triggers ACTION_ACT_PREPROCESS 16 * @triggers ACTION_HEADERS_SEND 17 */ 18function act_dispatch(){ 19 global $ACT; 20 global $ID; 21 global $INFO; 22 global $QUERY; 23 /* @var Input $INPUT */ 24 global $INPUT; 25 global $lang; 26 global $conf; 27 28 $preact = $ACT; 29 30 // give plugins an opportunity to process the action 31 $evt = new Doku_Event('ACTION_ACT_PREPROCESS',$ACT); 32 33 $headers = array(); 34 if ($evt->advise_before()) { 35 36 //sanitize $ACT 37 $ACT = act_validate($ACT); 38 39 //check if searchword was given - else just show 40 $s = cleanID($QUERY); 41 if($ACT == 'search' && empty($s)){ 42 $ACT = 'show'; 43 } 44 45 //login stuff 46 if(in_array($ACT,array('login','logout'))){ 47 $ACT = act_auth($ACT); 48 } 49 50 //check if user is asking to (un)subscribe a page 51 if($ACT == 'subscribe') { 52 try { 53 $ACT = act_subscription($ACT); 54 } catch (Exception $e) { 55 msg($e->getMessage(), -1); 56 } 57 } 58 59 //display some info 60 if($ACT == 'check'){ 61 check(); 62 $ACT = 'show'; 63 } 64 65 //check permissions 66 $ACT = act_permcheck($ACT); 67 68 //sitemap 69 if ($ACT == 'sitemap'){ 70 act_sitemap($ACT); 71 } 72 73 //recent changes 74 if ($ACT == 'recent'){ 75 $show_changes = $INPUT->str('show_changes'); 76 if (!empty($show_changes)) { 77 set_doku_pref('show_changes', $show_changes); 78 } 79 } 80 81 //diff 82 if ($ACT == 'diff'){ 83 $difftype = $INPUT->str('difftype'); 84 if (!empty($difftype)) { 85 set_doku_pref('difftype', $difftype); 86 } 87 } 88 89 //register 90 if($ACT == 'register' && $INPUT->post->bool('save') && register()){ 91 $ACT = 'login'; 92 } 93 94 if ($ACT == 'resendpwd' && act_resendpwd()) { 95 $ACT = 'login'; 96 } 97 98 // user profile changes 99 if (in_array($ACT, array('profile','profile_delete'))) { 100 if(!$INPUT->server->str('REMOTE_USER')) { 101 $ACT = 'login'; 102 } else { 103 switch ($ACT) { 104 case 'profile' : 105 if(updateprofile()) { 106 msg($lang['profchanged'],1); 107 $ACT = 'show'; 108 } 109 break; 110 case 'profile_delete' : 111 if(auth_deleteprofile()){ 112 msg($lang['profdeleted'],1); 113 $ACT = 'show'; 114 } else { 115 $ACT = 'profile'; 116 } 117 break; 118 } 119 } 120 } 121 122 //revert 123 if($ACT == 'revert'){ 124 if(checkSecurityToken()){ 125 $ACT = act_revert($ACT); 126 }else{ 127 $ACT = 'show'; 128 } 129 } 130 131 //save 132 if($ACT == 'save'){ 133 if(checkSecurityToken()){ 134 $ACT = act_save($ACT); 135 }else{ 136 $ACT = 'preview'; 137 } 138 } 139 140 //cancel conflicting edit 141 if($ACT == 'cancel') 142 $ACT = 'show'; 143 144 //draft deletion 145 if($ACT == 'draftdel') 146 $ACT = act_draftdel($ACT); 147 148 //draft saving on preview 149 if($ACT == 'preview') { 150 $headers[] = "X-XSS-Protection: 0"; 151 $ACT = act_draftsave($ACT); 152 } 153 154 //edit 155 if(in_array($ACT, array('edit', 'preview', 'recover'))) { 156 $ACT = act_edit($ACT); 157 }else{ 158 unlock($ID); //try to unlock 159 } 160 161 //handle export 162 if(substr($ACT,0,7) == 'export_') 163 $ACT = act_export($ACT); 164 165 //handle admin tasks 166 if($ACT == 'admin'){ 167 // retrieve admin plugin name from $_REQUEST['page'] 168 if (($page = $INPUT->str('page', '', true)) != '') { 169 /** @var $plugin DokuWiki_Admin_Plugin */ 170 if ($plugin = plugin_getRequestAdminPlugin()){ 171 $plugin->handle(); 172 } 173 } 174 } 175 176 // check permissions again - the action may have changed 177 $ACT = act_permcheck($ACT); 178 } // end event ACTION_ACT_PREPROCESS default action 179 $evt->advise_after(); 180 // Make sure plugs can handle 'denied' 181 if($conf['send404'] && $ACT == 'denied') { 182 http_status(403); 183 } 184 unset($evt); 185 186 // when action 'show', the intial not 'show' and POST, do a redirect 187 if($ACT == 'show' && $preact != 'show' && strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post'){ 188 act_redirect($ID,$preact); 189 } 190 191 global $INFO; 192 global $conf; 193 global $license; 194 195 //call template FIXME: all needed vars available? 196 $headers[] = 'Content-Type: text/html; charset=utf-8'; 197 trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders'); 198 199 include(template('main.php')); 200 // output for the commands is now handled in inc/templates.php 201 // in function tpl_content() 202} 203 204/** 205 * Send the given headers using header() 206 * 207 * @param array $headers The headers that shall be sent 208 */ 209function act_sendheaders($headers) { 210 foreach ($headers as $hdr) header($hdr); 211} 212 213/** 214 * Sanitize the action command 215 * 216 * @author Andreas Gohr <andi@splitbrain.org> 217 * 218 * @param array|string $act 219 * @return string 220 */ 221function act_clean($act){ 222 // check if the action was given as array key 223 if(is_array($act)){ 224 list($act) = array_keys($act); 225 } 226 227 //remove all bad chars 228 $act = strtolower($act); 229 $act = preg_replace('/[^1-9a-z_]+/','',$act); 230 231 if($act == 'export_html') $act = 'export_xhtml'; 232 if($act == 'export_htmlbody') $act = 'export_xhtmlbody'; 233 234 if($act === '') $act = 'show'; 235 return $act; 236} 237 238/** 239 * Sanitize and validate action commands. 240 * 241 * Add all allowed commands here. 242 * 243 * @author Andreas Gohr <andi@splitbrain.org> 244 * 245 * @param array|string $act 246 * @return string 247 */ 248function act_validate($act) { 249 global $conf; 250 global $INFO; 251 252 $act = act_clean($act); 253 254 // check if action is disabled 255 if(!actionOK($act)){ 256 msg('Command disabled: '.hsc($act),-1); 257 return 'show'; 258 } 259 260 //disable all acl related commands if ACL is disabled 261 if(!$conf['useacl'] && in_array($act,array('login','logout','register','admin', 262 'subscribe','unsubscribe','profile','revert', 263 'resendpwd','profile_delete'))){ 264 msg('Command unavailable: '.hsc($act),-1); 265 return 'show'; 266 } 267 268 //is there really a draft? 269 if($act == 'draft' && !file_exists($INFO['draft'])) return 'edit'; 270 271 if(!in_array($act,array('login','logout','register','save','cancel','edit','draft', 272 'preview','search','show','check','index','revisions', 273 'diff','recent','backlink','admin','subscribe','revert', 274 'unsubscribe','profile','profile_delete','resendpwd','recover', 275 'draftdel','sitemap','media')) && substr($act,0,7) != 'export_' ) { 276 msg('Command unknown: '.hsc($act),-1); 277 return 'show'; 278 } 279 return $act; 280} 281 282/** 283 * Run permissionchecks 284 * 285 * @author Andreas Gohr <andi@splitbrain.org> 286 * 287 * @param string $act action command 288 * @return string action command 289 */ 290function act_permcheck($act){ 291 global $INFO; 292 293 if(in_array($act,array('save','preview','edit','recover'))){ 294 if($INFO['exists']){ 295 if($act == 'edit'){ 296 //the edit function will check again and do a source show 297 //when no AUTH_EDIT available 298 $permneed = AUTH_READ; 299 }else{ 300 $permneed = AUTH_EDIT; 301 } 302 }else{ 303 $permneed = AUTH_CREATE; 304 } 305 }elseif(in_array($act,array('login','search','recent','profile','profile_delete','index', 'sitemap'))){ 306 $permneed = AUTH_NONE; 307 }elseif($act == 'revert'){ 308 $permneed = AUTH_ADMIN; 309 if($INFO['ismanager']) $permneed = AUTH_EDIT; 310 }elseif($act == 'register'){ 311 $permneed = AUTH_NONE; 312 }elseif($act == 'resendpwd'){ 313 $permneed = AUTH_NONE; 314 }elseif($act == 'admin'){ 315 if($INFO['ismanager']){ 316 // if the manager has the needed permissions for a certain admin 317 // action is checked later 318 $permneed = AUTH_READ; 319 }else{ 320 $permneed = AUTH_ADMIN; 321 } 322 }else{ 323 $permneed = AUTH_READ; 324 } 325 if($INFO['perm'] >= $permneed) return $act; 326 327 return 'denied'; 328} 329 330/** 331 * Handle 'draftdel' 332 * 333 * Deletes the draft for the current page and user 334 * 335 * @param string $act action command 336 * @return string action command 337 */ 338function act_draftdel($act){ 339 global $INFO; 340 @unlink($INFO['draft']); 341 $INFO['draft'] = null; 342 return 'show'; 343} 344 345/** 346 * Saves a draft on preview 347 * 348 * @todo this currently duplicates code from ajax.php :-/ 349 * 350 * @param string $act action command 351 * @return string action command 352 */ 353function act_draftsave($act){ 354 global $INFO; 355 global $ID; 356 global $INPUT; 357 global $conf; 358 if($conf['usedraft'] && $INPUT->post->has('wikitext')) { 359 $draft = array('id' => $ID, 360 'prefix' => substr($INPUT->post->str('prefix'), 0, -1), 361 'text' => $INPUT->post->str('wikitext'), 362 'suffix' => $INPUT->post->str('suffix'), 363 'date' => $INPUT->post->int('date'), 364 'client' => $INFO['client'], 365 ); 366 $cname = getCacheName($draft['client'].$ID,'.draft'); 367 if(io_saveFile($cname,serialize($draft))){ 368 $INFO['draft'] = $cname; 369 } 370 } 371 return $act; 372} 373 374/** 375 * Handle 'save' 376 * 377 * Checks for spam and conflicts and saves the page. 378 * Does a redirect to show the page afterwards or 379 * returns a new action. 380 * 381 * @author Andreas Gohr <andi@splitbrain.org> 382 * 383 * @param string $act action command 384 * @return string action command 385 */ 386function act_save($act){ 387 global $ID; 388 global $DATE; 389 global $PRE; 390 global $TEXT; 391 global $SUF; 392 global $SUM; 393 global $lang; 394 global $INFO; 395 global $INPUT; 396 397 //spam check 398 if(checkwordblock()) { 399 msg($lang['wordblock'], -1); 400 return 'edit'; 401 } 402 //conflict check 403 if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE ) 404 return 'conflict'; 405 406 //save it 407 saveWikiText($ID,con($PRE,$TEXT,$SUF,true),$SUM,$INPUT->bool('minor')); //use pretty mode for con 408 //unlock it 409 unlock($ID); 410 411 //delete draft 412 act_draftdel($act); 413 session_write_close(); 414 415 // when done, show page 416 return 'show'; 417} 418 419/** 420 * Revert to a certain revision 421 * 422 * @author Andreas Gohr <andi@splitbrain.org> 423 * 424 * @param string $act action command 425 * @return string action command 426 */ 427function act_revert($act){ 428 global $ID; 429 global $REV; 430 global $lang; 431 /* @var Input $INPUT */ 432 global $INPUT; 433 // FIXME $INFO['writable'] currently refers to the attic version 434 // global $INFO; 435 // if (!$INFO['writable']) { 436 // return 'show'; 437 // } 438 439 // when no revision is given, delete current one 440 // FIXME this feature is not exposed in the GUI currently 441 $text = ''; 442 $sum = $lang['deleted']; 443 if($REV){ 444 $text = rawWiki($ID,$REV); 445 if(!$text) return 'show'; //something went wrong 446 $sum = sprintf($lang['restored'], dformat($REV)); 447 } 448 449 // spam check 450 451 if (checkwordblock($text)) { 452 msg($lang['wordblock'], -1); 453 return 'edit'; 454 } 455 456 saveWikiText($ID,$text,$sum,false); 457 msg($sum,1); 458 459 //delete any draft 460 act_draftdel($act); 461 session_write_close(); 462 463 // when done, show current page 464 $INPUT->server->set('REQUEST_METHOD','post'); //should force a redirect 465 $REV = ''; 466 return 'show'; 467} 468 469/** 470 * Do a redirect after receiving post data 471 * 472 * Tries to add the section id as hash mark after section editing 473 * 474 * @param string $id page id 475 * @param string $preact action command before redirect 476 */ 477function act_redirect($id,$preact){ 478 global $PRE; 479 global $TEXT; 480 481 $opts = array( 482 'id' => $id, 483 'preact' => $preact 484 ); 485 //get section name when coming from section edit 486 if($PRE && preg_match('/^\s*==+([^=\n]+)/',$TEXT,$match)){ 487 $check = false; //Byref 488 $opts['fragment'] = sectionID($match[0], $check); 489 } 490 491 trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute'); 492} 493 494/** 495 * Execute the redirect 496 * 497 * @param array $opts id and fragment for the redirect and the preact 498 */ 499function act_redirect_execute($opts){ 500 $go = wl($opts['id'],'',true); 501 if(isset($opts['fragment'])) $go .= '#'.$opts['fragment']; 502 503 //show it 504 send_redirect($go); 505} 506 507/** 508 * Handle 'login', 'logout' 509 * 510 * @author Andreas Gohr <andi@splitbrain.org> 511 * 512 * @param string $act action command 513 * @return string action command 514 */ 515function act_auth($act){ 516 global $ID; 517 global $INFO; 518 /* @var Input $INPUT */ 519 global $INPUT; 520 521 //already logged in? 522 if($INPUT->server->has('REMOTE_USER') && $act=='login'){ 523 return 'show'; 524 } 525 526 //handle logout 527 if($act=='logout'){ 528 $lockedby = checklock($ID); //page still locked? 529 if($lockedby == $INPUT->server->str('REMOTE_USER')){ 530 unlock($ID); //try to unlock 531 } 532 533 // do the logout stuff 534 auth_logoff(); 535 536 // rebuild info array 537 $INFO = pageinfo(); 538 539 act_redirect($ID,'login'); 540 } 541 542 return $act; 543} 544 545/** 546 * Handle 'edit', 'preview', 'recover' 547 * 548 * @author Andreas Gohr <andi@splitbrain.org> 549 * 550 * @param string $act action command 551 * @return string action command 552 */ 553function act_edit($act){ 554 global $ID; 555 global $INFO; 556 557 global $TEXT; 558 global $RANGE; 559 global $PRE; 560 global $SUF; 561 global $REV; 562 global $SUM; 563 global $lang; 564 global $DATE; 565 566 if (!isset($TEXT)) { 567 if ($INFO['exists']) { 568 if ($RANGE) { 569 list($PRE,$TEXT,$SUF) = rawWikiSlices($RANGE,$ID,$REV); 570 } else { 571 $TEXT = rawWiki($ID,$REV); 572 } 573 } else { 574 $TEXT = pageTemplate($ID); 575 } 576 } 577 578 //set summary default 579 if(!$SUM){ 580 if($REV){ 581 $SUM = sprintf($lang['restored'], dformat($REV)); 582 }elseif(!$INFO['exists']){ 583 $SUM = $lang['created']; 584 } 585 } 586 587 // Use the date of the newest revision, not of the revision we edit 588 // This is used for conflict detection 589 if(!$DATE) $DATE = @filemtime(wikiFN($ID)); 590 591 //check if locked by anyone - if not lock for my self 592 //do not lock when the user can't edit anyway 593 if ($INFO['writable']) { 594 $lockedby = checklock($ID); 595 if($lockedby) return 'locked'; 596 597 lock($ID); 598 } 599 600 return $act; 601} 602 603/** 604 * Export a wiki page for various formats 605 * 606 * Triggers ACTION_EXPORT_POSTPROCESS 607 * 608 * Event data: 609 * data['id'] -- page id 610 * data['mode'] -- requested export mode 611 * data['headers'] -- export headers 612 * data['output'] -- export output 613 * 614 * @author Andreas Gohr <andi@splitbrain.org> 615 * @author Michael Klier <chi@chimeric.de> 616 * 617 * @param string $act action command 618 * @return string action command 619 */ 620function act_export($act){ 621 global $ID; 622 global $REV; 623 global $conf; 624 global $lang; 625 626 $pre = ''; 627 $post = ''; 628 $headers = array(); 629 630 // search engines: never cache exported docs! (Google only currently) 631 $headers['X-Robots-Tag'] = 'noindex'; 632 633 $mode = substr($act,7); 634 switch($mode) { 635 case 'raw': 636 $headers['Content-Type'] = 'text/plain; charset=utf-8'; 637 $headers['Content-Disposition'] = 'attachment; filename='.noNS($ID).'.txt'; 638 $output = rawWiki($ID,$REV); 639 break; 640 case 'xhtml': 641 $pre .= '<!DOCTYPE html>' . DOKU_LF; 642 $pre .= '<html lang="'.$conf['lang'].'" dir="'.$lang['direction'].'">' . DOKU_LF; 643 $pre .= '<head>' . DOKU_LF; 644 $pre .= ' <meta charset="utf-8" />' . DOKU_LF; 645 $pre .= ' <title>'.$ID.'</title>' . DOKU_LF; 646 647 // get metaheaders 648 ob_start(); 649 tpl_metaheaders(); 650 $pre .= ob_get_clean(); 651 652 $pre .= '</head>' . DOKU_LF; 653 $pre .= '<body>' . DOKU_LF; 654 $pre .= '<div class="dokuwiki export">' . DOKU_LF; 655 656 // get toc 657 $pre .= tpl_toc(true); 658 659 $headers['Content-Type'] = 'text/html; charset=utf-8'; 660 $output = p_wiki_xhtml($ID,$REV,false); 661 662 $post .= '</div>' . DOKU_LF; 663 $post .= '</body>' . DOKU_LF; 664 $post .= '</html>' . DOKU_LF; 665 break; 666 case 'xhtmlbody': 667 $headers['Content-Type'] = 'text/html; charset=utf-8'; 668 $output = p_wiki_xhtml($ID,$REV,false); 669 break; 670 default: 671 $output = p_cached_output(wikiFN($ID,$REV), $mode, $ID); 672 $headers = p_get_metadata($ID,"format $mode"); 673 break; 674 } 675 676 // prepare event data 677 $data = array(); 678 $data['id'] = $ID; 679 $data['mode'] = $mode; 680 $data['headers'] = $headers; 681 $data['output'] =& $output; 682 683 trigger_event('ACTION_EXPORT_POSTPROCESS', $data); 684 685 if(!empty($data['output'])){ 686 if(is_array($data['headers'])) foreach($data['headers'] as $key => $val){ 687 header("$key: $val"); 688 } 689 print $pre.$data['output'].$post; 690 exit; 691 } 692 return 'show'; 693} 694 695/** 696 * Handle sitemap delivery 697 * 698 * @author Michael Hamann <michael@content-space.de> 699 * 700 * @param string $act action command 701 */ 702function act_sitemap($act) { 703 global $conf; 704 705 if ($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) { 706 http_status(404); 707 print "Sitemap generation is disabled."; 708 exit; 709 } 710 711 $sitemap = Sitemapper::getFilePath(); 712 if (Sitemapper::sitemapIsCompressed()) { 713 $mime = 'application/x-gzip'; 714 }else{ 715 $mime = 'application/xml; charset=utf-8'; 716 } 717 718 // Check if sitemap file exists, otherwise create it 719 if (!is_readable($sitemap)) { 720 Sitemapper::generate(); 721 } 722 723 if (is_readable($sitemap)) { 724 // Send headers 725 header('Content-Type: '.$mime); 726 header('Content-Disposition: attachment; filename='.utf8_basename($sitemap)); 727 728 http_conditionalRequest(filemtime($sitemap)); 729 730 // Send file 731 //use x-sendfile header to pass the delivery to compatible webservers 732 http_sendfile($sitemap); 733 734 readfile($sitemap); 735 exit; 736 } 737 738 http_status(500); 739 print "Could not read the sitemap file - bad permissions?"; 740 exit; 741} 742 743/** 744 * Handle page 'subscribe' 745 * 746 * Throws exception on error. 747 * 748 * @author Adrian Lang <lang@cosmocode.de> 749 * 750 * @param string $act action command 751 * @return string action command 752 * @throws Exception if (un)subscribing fails 753 */ 754function act_subscription($act){ 755 global $lang; 756 global $INFO; 757 global $ID; 758 /* @var Input $INPUT */ 759 global $INPUT; 760 761 // subcriptions work for logged in users only 762 if(!$INPUT->server->str('REMOTE_USER')) return 'show'; 763 764 // get and preprocess data. 765 $params = array(); 766 foreach(array('target', 'style', 'action') as $param) { 767 if ($INPUT->has("sub_$param")) { 768 $params[$param] = $INPUT->str("sub_$param"); 769 } 770 } 771 772 // any action given? if not just return and show the subscription page 773 if(empty($params['action']) || !checkSecurityToken()) return $act; 774 775 // Handle POST data, may throw exception. 776 trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, 'subscription_handle_post'); 777 778 $target = $params['target']; 779 $style = $params['style']; 780 $action = $params['action']; 781 782 // Perform action. 783 $sub = new Subscription(); 784 if($action == 'unsubscribe'){ 785 $ok = $sub->remove($target, $INPUT->server->str('REMOTE_USER'), $style); 786 }else{ 787 $ok = $sub->add($target, $INPUT->server->str('REMOTE_USER'), $style); 788 } 789 790 if($ok) { 791 msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']), 792 prettyprint_id($target)), 1); 793 act_redirect($ID, $act); 794 } else { 795 throw new Exception(sprintf($lang["subscr_{$action}_error"], 796 hsc($INFO['userinfo']['name']), 797 prettyprint_id($target))); 798 } 799 800 // Assure that we have valid data if act_redirect somehow fails. 801 $INFO['subscribed'] = $sub->user_subscription(); 802 return 'show'; 803} 804 805/** 806 * Validate POST data 807 * 808 * Validates POST data for a subscribe or unsubscribe request. This is the 809 * default action for the event ACTION_HANDLE_SUBSCRIBE. 810 * 811 * @author Adrian Lang <lang@cosmocode.de> 812 * 813 * @param array &$params the parameters: target, style and action 814 * @throws Exception 815 */ 816function subscription_handle_post(&$params) { 817 global $INFO; 818 global $lang; 819 /* @var Input $INPUT */ 820 global $INPUT; 821 822 // Get and validate parameters. 823 if (!isset($params['target'])) { 824 throw new Exception('no subscription target given'); 825 } 826 $target = $params['target']; 827 $valid_styles = array('every', 'digest'); 828 if (substr($target, -1, 1) === ':') { 829 // Allow “list” subscribe style since the target is a namespace. 830 $valid_styles[] = 'list'; 831 } 832 $style = valid_input_set('style', $valid_styles, $params, 833 'invalid subscription style given'); 834 $action = valid_input_set('action', array('subscribe', 'unsubscribe'), 835 $params, 'invalid subscription action given'); 836 837 // Check other conditions. 838 if ($action === 'subscribe') { 839 if ($INFO['userinfo']['mail'] === '') { 840 throw new Exception($lang['subscr_subscribe_noaddress']); 841 } 842 } elseif ($action === 'unsubscribe') { 843 $is = false; 844 foreach($INFO['subscribed'] as $subscr) { 845 if ($subscr['target'] === $target) { 846 $is = true; 847 } 848 } 849 if ($is === false) { 850 throw new Exception(sprintf($lang['subscr_not_subscribed'], 851 $INPUT->server->str('REMOTE_USER'), 852 prettyprint_id($target))); 853 } 854 // subscription_set deletes a subscription if style = null. 855 $style = null; 856 } 857 858 $params = compact('target', 'style', 'action'); 859} 860 861//Setup VIM: ex: et ts=2 : 862