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