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