]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/action.php
Cron plugin added and now default queue handler
[quix0rs-gnu-social.git] / lib / action.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Base class for all actions (~views)
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Action
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @author    Sarven Capadisli <csarven@status.net>
26  * @copyright 2008 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('GNUSOCIAL')) { exit(1); }
32
33 /**
34  * Base class for all actions
35  *
36  * This is the base class for all actions in the package. An action is
37  * more or less a "view" in an MVC framework.
38  *
39  * Actions are responsible for extracting and validating parameters; using
40  * model classes to read and write to the database; and doing ouput.
41  *
42  * @category Output
43  * @package  StatusNet
44  * @author   Evan Prodromou <evan@status.net>
45  * @author   Sarven Capadisli <csarven@status.net>
46  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
47  * @link     http://status.net/
48  *
49  * @see      HTMLOutputter
50  */
51 class Action extends HTMLOutputter // lawsuit
52 {
53     // This should be protected/private in the future
54     public $args = array();
55
56     // Action properties, set per-class
57     protected $action = false;
58     protected $ajax   = false;
59     protected $menus  = true;
60     protected $needLogin = false;
61     protected $needPost = false;
62
63     // The currently scoped profile (normally Profile::current; from $this->auth_user for API)
64     protected $scoped = null;
65
66     // Related to front-end user representation
67     protected $format = null;
68     protected $error  = null;
69     protected $msg    = null;
70
71     /**
72      * Constructor
73      *
74      * Just wraps the HTMLOutputter constructor.
75      *
76      * @param string  $output URI to output to, default = stdout
77      * @param boolean $indent Whether to indent output, default true
78      *
79      * @see XMLOutputter::__construct
80      * @see HTMLOutputter::__construct
81      */
82     function __construct($output='php://output', $indent=null)
83     {
84         parent::__construct($output, $indent);
85     }
86
87     function getError()
88     {
89         return $this->error;
90     }
91
92     function getInfo()
93     {
94         return $this->msg;
95     }
96
97     static public function run(array $args=array(), $output='php://output', $indent=null) {
98         $class = get_called_class();
99         $action = new $class($output, $indent);
100         $action->execute($args);
101         return $action;
102     }
103
104     public function execute(array $args=array()) {
105         // checkMirror stuff
106         if (common_config('db', 'mirror') && $this->isReadOnly($args)) {
107             if (is_array(common_config('db', 'mirror'))) {
108                 // "load balancing", ha ha
109                 $arr = common_config('db', 'mirror');
110                 $k = array_rand($arr);
111                 $mirror = $arr[$k];
112             } else {
113                 $mirror = common_config('db', 'mirror');
114             }
115
116             // everyone else uses the mirror
117             common_config_set('db', 'database', $mirror);
118         }
119
120         $status = $this->prepare($args);
121         if ($status) {
122             $this->handle($args);
123         } else {
124             common_debug('Prepare failed for Action.');
125         }
126
127         $this->flush();
128
129         Event::handle('EndActionExecute', array($status, $this));
130     }
131
132     /**
133      * For initializing members of the class.
134      *
135      * @param array $argarray misc. arguments
136      *
137      * @return boolean true
138      */
139     protected function prepare(array $args=array())
140     {
141         if ($this->needPost && !$this->isPost()) {
142             // TRANS: Client error. POST is a HTTP command. It should not be translated.
143             $this->clientError(_('This method requires a POST.'), 405);
144         }
145
146         $this->args = common_copy_args($args);
147
148         $this->action = $this->trimmed('action');
149
150         if ($this->ajax || $this->boolean('ajax')) {
151             // check with StatusNet::isAjax()
152             StatusNet::setAjax(true);
153         }
154
155         if ($this->needLogin) {
156             $this->checkLogin(); // if not logged in, this redirs/excepts
157         }
158
159         $this->updateScopedProfile();
160
161         return true;
162     }
163
164     function updateScopedProfile() {
165         $this->scoped = Profile::current();
166         return $this->scoped;
167     }
168
169     /**
170      * Show page, a template method.
171      *
172      * @return nothing
173      */
174     function showPage()
175     {
176         if (Event::handle('StartShowHTML', array($this))) {
177             $this->startHTML();
178             $this->flush();
179             Event::handle('EndShowHTML', array($this));
180         }
181         if (Event::handle('StartShowHead', array($this))) {
182             $this->showHead();
183             $this->flush();
184             Event::handle('EndShowHead', array($this));
185         }
186         if (Event::handle('StartShowBody', array($this))) {
187             $this->showBody();
188             Event::handle('EndShowBody', array($this));
189         }
190         if (Event::handle('StartEndHTML', array($this))) {
191             $this->endHTML();
192             Event::handle('EndEndHTML', array($this));
193         }
194     }
195
196     function endHTML()
197     {
198         global $_startTime;
199
200         if (isset($_startTime)) {
201             $endTime = microtime(true);
202             $diff = round(($endTime - $_startTime) * 1000);
203             $this->raw("<!-- ${diff}ms -->");
204         }
205
206         return parent::endHTML();
207     }
208
209     /**
210      * Show head, a template method.
211      *
212      * @return nothing
213      */
214     function showHead()
215     {
216         // XXX: attributes (profile?)
217         $this->elementStart('head');
218         if (Event::handle('StartShowHeadElements', array($this))) {
219             if (Event::handle('StartShowHeadTitle', array($this))) {
220                 $this->showTitle();
221                 Event::handle('EndShowHeadTitle', array($this));
222             }
223             $this->showShortcutIcon();
224             $this->showStylesheets();
225             $this->showOpenSearch();
226             $this->showFeeds();
227             $this->showDescription();
228             $this->extraHead();
229             Event::handle('EndShowHeadElements', array($this));
230         }
231         $this->elementEnd('head');
232     }
233
234     /**
235      * Show title, a template method.
236      *
237      * @return nothing
238      */
239     function showTitle()
240     {
241         $this->element('title', null,
242                        // TRANS: Page title. %1$s is the title, %2$s is the site name.
243                        sprintf(_('%1$s - %2$s'),
244                                $this->title(),
245                                common_config('site', 'name')));
246     }
247
248     /**
249      * Returns the page title
250      *
251      * SHOULD overload
252      *
253      * @return string page title
254      */
255
256     function title()
257     {
258         // TRANS: Page title for a page without a title set.
259         return _('Untitled page');
260     }
261
262     /**
263      * Show themed shortcut icon
264      *
265      * @return nothing
266      */
267     function showShortcutIcon()
268     {
269         if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/favicon.ico')) {
270             $this->element('link', array('rel' => 'shortcut icon',
271                                          'href' => Theme::path('favicon.ico')));
272         } else {
273             // favicon.ico should be HTTPS if the rest of the page is
274             $this->element('link', array('rel' => 'shortcut icon',
275                                          'href' => common_path('favicon.ico', StatusNet::isHTTPS())));
276         }
277
278         if (common_config('site', 'mobile')) {
279             if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/apple-touch-icon.png')) {
280                 $this->element('link', array('rel' => 'apple-touch-icon',
281                                              'href' => Theme::path('apple-touch-icon.png')));
282             } else {
283                 $this->element('link', array('rel' => 'apple-touch-icon',
284                                              'href' => common_path('apple-touch-icon.png')));
285             }
286         }
287     }
288
289     /**
290      * Show stylesheets
291      *
292      * @return nothing
293      */
294     function showStylesheets()
295     {
296         if (Event::handle('StartShowStyles', array($this))) {
297
298             // Use old name for StatusNet for compatibility on events
299
300             if (Event::handle('StartShowStylesheets', array($this))) {
301                 $this->primaryCssLink(null, 'screen, projection, tv, print');
302                 Event::handle('EndShowStylesheets', array($this));
303             }
304
305             $this->cssLink('js/extlib/jquery-ui/css/smoothness/jquery-ui.css');
306
307             if (Event::handle('StartShowUAStyles', array($this))) {
308                 $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
309                                'href="'.Theme::path('css/ie.css', 'base').'?version='.GNUSOCIAL_VERSION.'" /><![endif]');
310                 foreach (array(6,7) as $ver) {
311                     if (file_exists(Theme::file('css/ie'.$ver.'.css', 'base'))) {
312                         // Yes, IE people should be put in jail.
313                         $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
314                                        'href="'.Theme::path('css/ie'.$ver.'.css', 'base').'?version='.GNUSOCIAL_VERSION.'" /><![endif]');
315                     }
316                 }
317                 if (file_exists(Theme::file('css/ie.css'))) {
318                     $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
319                                'href="'.Theme::path('css/ie.css', null).'?version='.GNUSOCIAL_VERSION.'" /><![endif]');
320                 }
321                 Event::handle('EndShowUAStyles', array($this));
322             }
323
324             Event::handle('EndShowStyles', array($this));
325
326             if (common_config('custom_css', 'enabled')) {
327                 $css = common_config('custom_css', 'css');
328                 if (Event::handle('StartShowCustomCss', array($this, &$css))) {
329                     if (trim($css) != '') {
330                         $this->style($css);
331                     }
332                     Event::handle('EndShowCustomCss', array($this));
333                 }
334             }
335         }
336     }
337
338     function primaryCssLink($mainTheme=null, $media=null)
339     {
340         $theme = new Theme($mainTheme);
341
342         // Some themes may have external stylesheets, such as using the
343         // Google Font APIs to load webfonts.
344         foreach ($theme->getExternals() as $url) {
345             $this->cssLink($url, $mainTheme, $media);
346         }
347
348         // If the currently-selected theme has dependencies on other themes,
349         // we'll need to load their display.css files as well in order.
350         $baseThemes = $theme->getDeps();
351         foreach ($baseThemes as $baseTheme) {
352             $this->cssLink('css/display.css', $baseTheme, $media);
353         }
354         $this->cssLink('css/display.css', $mainTheme, $media);
355
356         // Additional styles for RTL languages
357         if (is_rtl(common_language())) {
358             if (file_exists(Theme::file('css/rtl.css'))) {
359                 $this->cssLink('css/rtl.css', $mainTheme, $media);
360             }
361         }
362     }
363
364     /**
365      * Show javascript headers
366      *
367      * @return nothing
368      */
369     function showScripts()
370     {
371         if (Event::handle('StartShowScripts', array($this))) {
372             if (Event::handle('StartShowJQueryScripts', array($this))) {
373                 if (common_config('site', 'minify')) {
374                     $this->script('extlib/jquery.min.js');
375                     $this->script('extlib/jquery.form.min.js');
376                     $this->script('extlib/jquery-ui/jquery-ui.min.js');
377                     $this->script('extlib/jquery.cookie.min.js');
378                     $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.min.js', StatusNet::isHTTPS()).'"); }');
379                     $this->script('extlib/jquery.infieldlabel.min.js');
380                 } else {
381                     $this->script('extlib/jquery.js');
382                     $this->script('extlib/jquery.form.js');
383                     $this->script('extlib/jquery-ui/jquery-ui.js');
384                     $this->script('extlib/jquery.cookie.js');
385                     $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.js', StatusNet::isHTTPS()).'"); }');
386                     $this->script('extlib/jquery.infieldlabel.js');
387                 }
388
389                 Event::handle('EndShowJQueryScripts', array($this));
390             }
391             if (Event::handle('StartShowStatusNetScripts', array($this))) {
392                 if (common_config('site', 'minify')) {
393                     $this->script('util.min.js');
394                 } else {
395                     $this->script('util.js');
396                     $this->script('xbImportNode.js');
397                     $this->script('geometa.js');
398                 }
399                 // This route isn't available in single-user mode.
400                 // Not sure why, but it causes errors here.
401                 $this->inlineScript('var _peopletagAC = "' .
402                                     common_local_url('peopletagautocomplete') . '";');
403                 $this->showScriptMessages();
404                 // Anti-framing code to avoid clickjacking attacks in older browsers.
405                 // This will show a blank page if the page is being framed, which is
406                 // consistent with the behavior of the 'X-Frame-Options: SAMEORIGIN'
407                 // header, which prevents framing in newer browser.
408                 if (common_config('javascript', 'bustframes')) {
409                     $this->inlineScript('if (window.top !== window.self) { document.write = ""; window.top.location = window.self.location; setTimeout(function () { document.body.innerHTML = ""; }, 1); window.self.onload = function () { document.body.innerHTML = ""; }; }');
410                 }
411                 Event::handle('EndShowStatusNetScripts', array($this));
412             }
413             Event::handle('EndShowScripts', array($this));
414         }
415     }
416
417     /**
418      * Exports a map of localized text strings to JavaScript code.
419      *
420      * Plugins can add to what's exported by hooking the StartScriptMessages or EndScriptMessages
421      * events and appending to the array. Try to avoid adding strings that won't be used, as
422      * they'll be added to HTML output.
423      */
424     function showScriptMessages()
425     {
426         $messages = array();
427
428         if (Event::handle('StartScriptMessages', array($this, &$messages))) {
429             // Common messages needed for timeline views etc...
430
431             // TRANS: Localized tooltip for '...' expansion button on overlong remote messages.
432             $messages['showmore_tooltip'] = _m('TOOLTIP', 'Show more');
433
434             // TRANS: Inline reply form submit button: submits a reply comment.
435             $messages['reply_submit'] = _m('BUTTON', 'Reply');
436
437             // TRANS: Placeholder text for inline reply form. Clicking in this box will turn it into a mini notice form.
438             $messages['reply_placeholder'] = _m('Write a reply...');
439
440             $messages = array_merge($messages, $this->getScriptMessages());
441
442             Event::handle('EndScriptMessages', array($this, &$messages));
443         }
444
445         if (!empty($messages)) {
446             $this->inlineScript('SN.messages=' . json_encode($messages));
447         }
448
449         return $messages;
450     }
451
452     /**
453      * If the action will need localizable text strings, export them here like so:
454      *
455      * return array('pool_deepend' => _('Deep end'),
456      *              'pool_shallow' => _('Shallow end'));
457      *
458      * The exported map will be available via SN.msg() to JS code:
459      *
460      *   $('#pool').html('<div class="deepend"></div><div class="shallow"></div>');
461      *   $('#pool .deepend').text(SN.msg('pool_deepend'));
462      *   $('#pool .shallow').text(SN.msg('pool_shallow'));
463      *
464      * Exports a map of localized text strings to JavaScript code.
465      *
466      * Plugins can add to what's exported on any action by hooking the StartScriptMessages or
467      * EndScriptMessages events and appending to the array. Try to avoid adding strings that won't
468      * be used, as they'll be added to HTML output.
469      */
470     function getScriptMessages()
471     {
472         return array();
473     }
474
475     /**
476      * Show OpenSearch headers
477      *
478      * @return nothing
479      */
480     function showOpenSearch()
481     {
482         $this->element('link', array('rel' => 'search',
483                                      'type' => 'application/opensearchdescription+xml',
484                                      'href' =>  common_local_url('opensearch', array('type' => 'people')),
485                                      'title' => common_config('site', 'name').' People Search'));
486         $this->element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
487                                      'href' =>  common_local_url('opensearch', array('type' => 'notice')),
488                                      'title' => common_config('site', 'name').' Notice Search'));
489     }
490
491     /**
492      * Show feed headers
493      *
494      * MAY overload
495      *
496      * @return nothing
497      */
498     function showFeeds()
499     {
500         $feeds = $this->getFeeds();
501
502         if ($feeds) {
503             foreach ($feeds as $feed) {
504                 $this->element('link', array('rel' => $feed->rel(),
505                                              'href' => $feed->url,
506                                              'type' => $feed->mimeType(),
507                                              'title' => $feed->title));
508             }
509         }
510     }
511
512     /**
513      * Show description.
514      *
515      * SHOULD overload
516      *
517      * @return nothing
518      */
519     function showDescription()
520     {
521         // does nothing by default
522     }
523
524     /**
525      * Show extra stuff in <head>.
526      *
527      * MAY overload
528      *
529      * @return nothing
530      */
531     function extraHead()
532     {
533         // does nothing by default
534     }
535
536     /**
537      * Show body.
538      *
539      * Calls template methods
540      *
541      * @return nothing
542      */
543     function showBody()
544     {
545         $this->elementStart('body', (common_current_user()) ? array('id' => strtolower($this->trimmed('action')),
546                                                                     'class' => 'user_in')
547                             : array('id' => strtolower($this->trimmed('action'))));
548         $this->elementStart('div', array('id' => 'wrap'));
549         if (Event::handle('StartShowHeader', array($this))) {
550             $this->showHeader();
551             $this->flush();
552             Event::handle('EndShowHeader', array($this));
553         }
554         $this->showCore();
555         $this->flush();
556         if (Event::handle('StartShowFooter', array($this))) {
557             $this->showFooter();
558             $this->flush();
559             Event::handle('EndShowFooter', array($this));
560         }
561         $this->elementEnd('div');
562         $this->showScripts();
563         $this->elementEnd('body');
564     }
565
566     /**
567      * Show header of the page.
568      *
569      * Calls template methods
570      *
571      * @return nothing
572      */
573     function showHeader()
574     {
575         $this->elementStart('div', array('id' => 'header'));
576         $this->showLogo();
577         $this->showPrimaryNav();
578         if (Event::handle('StartShowSiteNotice', array($this))) {
579             $this->showSiteNotice();
580
581             Event::handle('EndShowSiteNotice', array($this));
582         }
583
584         $this->elementEnd('div');
585     }
586
587     /**
588      * Show configured logo.
589      *
590      * @return nothing
591      */
592     function showLogo()
593     {
594         $this->elementStart('address', array('id' => 'site_contact',
595                                              'class' => 'vcard'));
596         if (Event::handle('StartAddressData', array($this))) {
597             if (common_config('singleuser', 'enabled')) {
598                 $user = User::singleUser();
599                 $url = common_local_url('showstream',
600                                         array('nickname' => $user->nickname));
601             } else if (common_logged_in()) {
602                 $cur = common_current_user();
603                 $url = common_local_url('all', array('nickname' => $cur->nickname));
604             } else {
605                 $url = common_local_url('public');
606             }
607
608             $this->elementStart('a', array('class' => 'url home bookmark',
609                                            'href' => $url));
610
611             if (StatusNet::isHTTPS()) {
612                 $logoUrl = common_config('site', 'ssllogo');
613                 if (empty($logoUrl)) {
614                     // if logo is an uploaded file, try to fall back to HTTPS file URL
615                     $httpUrl = common_config('site', 'logo');
616                     if (!empty($httpUrl)) {
617                         $f = File::getKV('url', $httpUrl);
618                         if (!empty($f) && !empty($f->filename)) {
619                             // this will handle the HTTPS case
620                             $logoUrl = File::url($f->filename);
621                         }
622                     }
623                 }
624             } else {
625                 $logoUrl = common_config('site', 'logo');
626             }
627
628             if (empty($logoUrl) && file_exists(Theme::file('logo.png'))) {
629                 // This should handle the HTTPS case internally
630                 $logoUrl = Theme::path('logo.png');
631             }
632
633             if (!empty($logoUrl)) {
634                 $this->element('img', array('class' => 'logo photo',
635                                             'src' => $logoUrl,
636                                             'alt' => common_config('site', 'name')));
637             }
638
639             $this->text(' ');
640             $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
641             $this->elementEnd('a');
642
643             Event::handle('EndAddressData', array($this));
644         }
645         $this->elementEnd('address');
646     }
647
648     /**
649      * Show primary navigation.
650      *
651      * @return nothing
652      */
653     function showPrimaryNav()
654     {
655         $this->elementStart('div', array('id' => 'site_nav_global_primary'));
656
657         $user = common_current_user();
658
659         if (!empty($user) || !common_config('site', 'private')) {
660             $form = new SearchForm($this);
661             $form->show();
662         }
663
664         $pn = new PrimaryNav($this);
665         $pn->show();
666         $this->elementEnd('div');
667     }
668
669     /**
670      * Show site notice.
671      *
672      * @return nothing
673      */
674     function showSiteNotice()
675     {
676         // Revist. Should probably do an hAtom pattern here
677         $text = common_config('site', 'notice');
678         if ($text) {
679             $this->elementStart('div', array('id' => 'site_notice',
680                                             'class' => 'system_notice'));
681             $this->raw($text);
682             $this->elementEnd('div');
683         }
684     }
685
686     /**
687      * Show notice form.
688      *
689      * MAY overload if no notice form needed... or direct message box????
690      *
691      * @return nothing
692      */
693     function showNoticeForm()
694     {
695         // TRANS: Tab on the notice form.
696         $tabs = array('status' => _m('TAB','Status'));
697
698         $this->elementStart('div', 'input_forms');
699
700         if (Event::handle('StartShowEntryForms', array(&$tabs))) {
701             $this->elementStart('ul', array('class' => 'nav',
702                                             'id' => 'input_form_nav'));
703
704             foreach ($tabs as $tag => $title) {
705                 $attrs = array('id' => 'input_form_nav_'.$tag,
706                                'class' => 'input_form_nav_tab');
707
708                 if ($tag == 'status') {
709                     // We're actually showing the placeholder form,
710                     // but we special-case the 'Status' tab as if
711                     // it were a small version of it.
712                     $attrs['class'] .= ' current';
713                 }
714                 $this->elementStart('li', $attrs);
715
716                 $this->element('a',
717                                array('href' => 'javascript:SN.U.switchInputFormTab("'.$tag.'")'),
718                                $title);
719                 $this->elementEnd('li');
720             }
721
722             $this->elementEnd('ul');
723
724             $attrs = array('class' => 'input_form current',
725                            'id' => 'input_form_placeholder');
726             $this->elementStart('div', $attrs);
727             $form = new NoticePlaceholderForm($this);
728             $form->show();
729             $this->elementEnd('div');
730
731             foreach ($tabs as $tag => $title) {
732                 $attrs = array('class' => 'input_form',
733                                'id' => 'input_form_'.$tag);
734
735                 $this->elementStart('div', $attrs);
736
737                 $form = null;
738
739                 if (Event::handle('StartMakeEntryForm', array($tag, $this, &$form))) {
740                     if ($tag == 'status') {
741                         $options = $this->noticeFormOptions();
742                         $form = new NoticeForm($this, $options);
743                     }
744                     Event::handle('EndMakeEntryForm', array($tag, $this, $form));
745                 }
746
747                 if (!empty($form)) {
748                     $form->show();
749                 }
750
751                 $this->elementEnd('div');
752             }
753         }
754
755         $this->elementEnd('div');
756     }
757
758     function noticeFormOptions()
759     {
760         return array();
761     }
762
763     /**
764      * Show anonymous message.
765      *
766      * SHOULD overload
767      *
768      * @return nothing
769      */
770     function showAnonymousMessage()
771     {
772         // needs to be defined by the class
773     }
774
775     /**
776      * Show core.
777      *
778      * Shows local navigation, content block and aside.
779      *
780      * @return nothing
781      */
782     function showCore()
783     {
784         $this->elementStart('div', array('id' => 'core'));
785         $this->elementStart('div', array('id' => 'aside_primary_wrapper'));
786         $this->elementStart('div', array('id' => 'content_wrapper'));
787         $this->elementStart('div', array('id' => 'site_nav_local_views_wrapper'));
788         if (Event::handle('StartShowLocalNavBlock', array($this))) {
789             $this->showLocalNavBlock();
790             $this->flush();
791             Event::handle('EndShowLocalNavBlock', array($this));
792         }
793         if (Event::handle('StartShowContentBlock', array($this))) {
794             $this->showContentBlock();
795             $this->flush();
796             Event::handle('EndShowContentBlock', array($this));
797         }
798         if (Event::handle('StartShowAside', array($this))) {
799             $this->showAside();
800             $this->flush();
801             Event::handle('EndShowAside', array($this));
802         }
803         $this->elementEnd('div');
804         $this->elementEnd('div');
805         $this->elementEnd('div');
806         $this->elementEnd('div');
807     }
808
809     /**
810      * Show local navigation block.
811      *
812      * @return nothing
813      */
814     function showLocalNavBlock()
815     {
816         // Need to have this ID for CSS; I'm too lazy to add it to
817         // all menus
818         $this->elementStart('div', array('id' => 'site_nav_local_views'));
819         // Cheat cheat cheat!
820         $this->showLocalNav();
821         $this->elementEnd('div');
822     }
823
824     /**
825      * If there's a logged-in user, show a bit of login context
826      *
827      * @return nothing
828      */
829     function showProfileBlock()
830     {
831         if (common_logged_in()) {
832             $block = new DefaultProfileBlock($this);
833             $block->show();
834         }
835     }
836
837     /**
838      * Show local navigation.
839      *
840      * SHOULD overload
841      *
842      * @return nothing
843      */
844     function showLocalNav()
845     {
846         $nav = new DefaultLocalNav($this);
847         $nav->show();
848     }
849
850     /**
851      * Show menu for an object (group, profile)
852      *
853      * This block will only show if a subclass has overridden
854      * the showObjectNav() method.
855      *
856      * @return nothing
857      */
858     function showObjectNavBlock()
859     {
860         $rmethod = new ReflectionMethod($this, 'showObjectNav');
861         $dclass = $rmethod->getDeclaringClass()->getName();
862
863         if ($dclass != 'Action') {
864             // Need to have this ID for CSS; I'm too lazy to add it to
865             // all menus
866             $this->elementStart('div', array('id' => 'site_nav_object',
867                                              'class' => 'section'));
868             $this->showObjectNav();
869             $this->elementEnd('div');
870         }
871     }
872
873     /**
874      * Show object navigation.
875      *
876      * If there are things to do with this object, show it here.
877      *
878      * @return nothing
879      */
880     function showObjectNav()
881     {
882         /* Nothing here. */
883     }
884
885     /**
886      * Show content block.
887      *
888      * @return nothing
889      */
890     function showContentBlock()
891     {
892         $this->elementStart('div', array('id' => 'content'));
893         if (common_logged_in()) {
894             if (Event::handle('StartShowNoticeForm', array($this))) {
895                 $this->showNoticeForm();
896                 Event::handle('EndShowNoticeForm', array($this));
897             }
898         }
899         if (Event::handle('StartShowPageTitle', array($this))) {
900             $this->showPageTitle();
901             Event::handle('EndShowPageTitle', array($this));
902         }
903         $this->showPageNoticeBlock();
904         $this->elementStart('div', array('id' => 'content_inner'));
905         // show the actual content (forms, lists, whatever)
906         $this->showContent();
907         $this->elementEnd('div');
908         $this->elementEnd('div');
909     }
910
911     /**
912      * Show page title.
913      *
914      * @return nothing
915      */
916     function showPageTitle()
917     {
918         $this->element('h1', null, $this->title());
919     }
920
921     /**
922      * Show page notice block.
923      *
924      * Only show the block if a subclassed action has overrided
925      * Action::showPageNotice(), or an event handler is registered for
926      * the StartShowPageNotice event, in which case we assume the
927      * 'page_notice' definition list is desired.  This is to prevent
928      * empty 'page_notice' definition lists from being output everywhere.
929      *
930      * @return nothing
931      */
932     function showPageNoticeBlock()
933     {
934         $rmethod = new ReflectionMethod($this, 'showPageNotice');
935         $dclass = $rmethod->getDeclaringClass()->getName();
936
937         if ($dclass != 'Action' || Event::hasHandler('StartShowPageNotice')) {
938
939             $this->elementStart('div', array('id' => 'page_notice',
940                                             'class' => 'system_notice'));
941             if (Event::handle('StartShowPageNotice', array($this))) {
942                 $this->showPageNotice();
943                 Event::handle('EndShowPageNotice', array($this));
944             }
945             $this->elementEnd('div');
946         }
947     }
948
949     /**
950      * Show page notice.
951      *
952      * SHOULD overload (unless there's not a notice)
953      *
954      * @return nothing
955      */
956     function showPageNotice()
957     {
958     }
959
960     /**
961      * Show content.
962      *
963      * MUST overload (unless there's not a notice)
964      *
965      * @return nothing
966      */
967     function showContent()
968     {
969     }
970
971     /**
972      * Show Aside.
973      *
974      * @return nothing
975      */
976     function showAside()
977     {
978         $this->elementStart('div', array('id' => 'aside_primary',
979                                          'class' => 'aside'));
980         $this->showProfileBlock();
981         if (Event::handle('StartShowObjectNavBlock', array($this))) {
982             $this->showObjectNavBlock();
983             Event::handle('EndShowObjectNavBlock', array($this));
984         }
985         if (Event::handle('StartShowSections', array($this))) {
986             $this->showSections();
987             Event::handle('EndShowSections', array($this));
988         }
989         if (Event::handle('StartShowExportData', array($this))) {
990             $this->showExportData();
991             Event::handle('EndShowExportData', array($this));
992         }
993         $this->elementEnd('div');
994     }
995
996     /**
997      * Show export data feeds.
998      *
999      * @return void
1000      */
1001     function showExportData()
1002     {
1003         $feeds = $this->getFeeds();
1004         if ($feeds) {
1005             $fl = new FeedList($this);
1006             $fl->show($feeds);
1007         }
1008     }
1009
1010     /**
1011      * Show sections.
1012      *
1013      * SHOULD overload
1014      *
1015      * @return nothing
1016      */
1017     function showSections()
1018     {
1019         // for each section, show it
1020     }
1021
1022     /**
1023      * Show footer.
1024      *
1025      * @return nothing
1026      */
1027     function showFooter()
1028     {
1029         $this->elementStart('div', array('id' => 'footer'));
1030         if (Event::handle('StartShowInsideFooter', array($this))) {
1031             $this->showSecondaryNav();
1032             $this->showLicenses();
1033             Event::handle('EndShowInsideFooter', array($this));
1034         }
1035         $this->elementEnd('div');
1036     }
1037
1038     /**
1039      * Show secondary navigation.
1040      *
1041      * @return nothing
1042      */
1043     function showSecondaryNav()
1044     {
1045         $sn = new SecondaryNav($this);
1046         $sn->show();
1047     }
1048
1049     /**
1050      * Show licenses.
1051      *
1052      * @return nothing
1053      */
1054     function showLicenses()
1055     {
1056         $this->showGNUsocialLicense();
1057         $this->showContentLicense();
1058     }
1059
1060     /**
1061      * Show GNU social license.
1062      *
1063      * @return nothing
1064      */
1065     function showGNUsocialLicense()
1066     {
1067         if (common_config('site', 'broughtby')) {
1068             // TRANS: First sentence of the GNU social site license. Used if 'broughtby' is set.
1069             // TRANS: Text between [] is a link description, text between () is the link itself.
1070             // TRANS: Make sure there is no whitespace between "]" and "(".
1071             // TRANS: "%%site.broughtby%%" is the value of the variable site.broughtby
1072             $instr = _('**%%site.name%%** is a social network, courtesy of [%%site.broughtby%%](%%site.broughtbyurl%%).');
1073         } else {
1074             // TRANS: First sentence of the GNU social site license. Used if 'broughtby' is not set.
1075             $instr = _('**%%site.name%%** is a social network.');
1076         }
1077         $instr .= ' ';
1078         // TRANS: Second sentence of the GNU social site license. Mentions the GNU social source code license.
1079         // TRANS: Make sure there is no whitespace between "]" and "(".
1080         // TRANS: [%1$s](%2$s) is a link description followed by the link itself
1081         // TRANS: %3$s is the version of GNU social that is being used.
1082         $instr .= sprintf(_('It runs on [%1$s](%2$s), version %3$s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), GNUSOCIAL_ENGINE, GNUSOCIAL_ENGINE_URL, GNUSOCIAL_VERSION);
1083         $output = common_markup_to_html($instr);
1084         $this->raw($output);
1085         // do it
1086     }
1087
1088     /**
1089      * Show content license.
1090      *
1091      * @return nothing
1092      */
1093     function showContentLicense()
1094     {
1095         if (Event::handle('StartShowContentLicense', array($this))) {
1096             switch (common_config('license', 'type')) {
1097             case 'private':
1098                 // TRANS: Content license displayed when license is set to 'private'.
1099                 // TRANS: %1$s is the site name.
1100                 $this->element('p', null, sprintf(_('Content and data of %1$s are private and confidential.'),
1101                                                   common_config('site', 'name')));
1102                 // fall through
1103             case 'allrightsreserved':
1104                 if (common_config('license', 'owner')) {
1105                     // TRANS: Content license displayed when license is set to 'allrightsreserved'.
1106                     // TRANS: %1$s is the copyright owner.
1107                     $this->element('p', null, sprintf(_('Content and data copyright by %1$s. All rights reserved.'),
1108                                                       common_config('license', 'owner')));
1109                 } else {
1110                     // TRANS: Content license displayed when license is set to 'allrightsreserved' and no owner is set.
1111                     $this->element('p', null, _('Content and data copyright by contributors. All rights reserved.'));
1112                 }
1113                 break;
1114             case 'cc': // fall through
1115             default:
1116                 $this->elementStart('p');
1117
1118                 $image    = common_config('license', 'image');
1119                 $sslimage = common_config('license', 'sslimage');
1120
1121                 if (StatusNet::isHTTPS()) {
1122                     if (!empty($sslimage)) {
1123                         $url = $sslimage;
1124                     } else if (preg_match('#^http://i.creativecommons.org/#', $image)) {
1125                         // CC support HTTPS on their images
1126                         $url = preg_replace('/^http/', 'https', $image);
1127                     } else {
1128                         // Better to show mixed content than no content
1129                         $url = $image;
1130                     }
1131                 } else {
1132                     $url = $image;
1133                 }
1134
1135                 $this->element('img', array('id' => 'license_cc',
1136                                             'src' => $url,
1137                                             'alt' => common_config('license', 'title'),
1138                                             'width' => '80',
1139                                             'height' => '15'));
1140                 $this->text(' ');
1141                 // TRANS: license message in footer.
1142                 // TRANS: %1$s is the site name, %2$s is a link to the license URL, with a licence name set in configuration.
1143                 $notice = _('All %1$s content and data are available under the %2$s license.');
1144                 $link = "<a class=\"license\" rel=\"external license\" href=\"" .
1145                         htmlspecialchars(common_config('license', 'url')) .
1146                         "\">" .
1147                         htmlspecialchars(common_config('license', 'title')) .
1148                         "</a>";
1149                 $this->raw(sprintf(htmlspecialchars($notice),
1150                                    htmlspecialchars(common_config('site', 'name')),
1151                                    $link));
1152                 $this->elementEnd('p');
1153                 break;
1154             }
1155
1156             Event::handle('EndShowContentLicense', array($this));
1157         }
1158     }
1159
1160     /**
1161      * Return last modified, if applicable.
1162      *
1163      * MAY override
1164      *
1165      * @return string last modified http header
1166      */
1167     function lastModified()
1168     {
1169         // For comparison with If-Last-Modified
1170         // If not applicable, return null
1171         return null;
1172     }
1173
1174     /**
1175      * Return etag, if applicable.
1176      *
1177      * MAY override
1178      *
1179      * @return string etag http header
1180      */
1181     function etag()
1182     {
1183         return null;
1184     }
1185
1186     /**
1187      * Return true if read only.
1188      *
1189      * MAY override
1190      *
1191      * @param array $args other arguments
1192      *
1193      * @return boolean is read only action?
1194      */
1195     function isReadOnly($args)
1196     {
1197         return false;
1198     }
1199
1200     /**
1201      * Returns query argument or default value if not found
1202      *
1203      * @param string $key requested argument
1204      * @param string $def default value to return if $key is not provided
1205      *
1206      * @return boolean is read only action?
1207      */
1208     function arg($key, $def=null)
1209     {
1210         if (array_key_exists($key, $this->args)) {
1211             return $this->args[$key];
1212         } else {
1213             return $def;
1214         }
1215     }
1216
1217     /**
1218      * Returns trimmed query argument or default value if not found
1219      *
1220      * @param string $key requested argument
1221      * @param string $def default value to return if $key is not provided
1222      *
1223      * @return boolean is read only action?
1224      */
1225     function trimmed($key, $def=null)
1226     {
1227         $arg = $this->arg($key, $def);
1228         return is_string($arg) ? trim($arg) : $arg;
1229     }
1230
1231     /**
1232      * Handler method
1233      *
1234      * @return boolean is read only action?
1235      */
1236     protected function handle()
1237     {
1238         header('Vary: Accept-Encoding,Cookie');
1239
1240         $lm   = $this->lastModified();
1241         $etag = $this->etag();
1242
1243         if ($etag) {
1244             header('ETag: ' . $etag);
1245         }
1246
1247         if ($lm) {
1248             header('Last-Modified: ' . date(DATE_RFC1123, $lm));
1249             if ($this->isCacheable()) {
1250                 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
1251                 header( "Cache-Control: private, must-revalidate, max-age=0" );
1252                 header( "Pragma:");
1253             }
1254         }
1255
1256         $checked = false;
1257         if ($etag) {
1258             $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
1259               $_SERVER['HTTP_IF_NONE_MATCH'] : null;
1260             if ($if_none_match) {
1261                 // If this check fails, ignore the if-modified-since below.
1262                 $checked = true;
1263                 if ($this->_hasEtag($etag, $if_none_match)) {
1264                     header('HTTP/1.1 304 Not Modified');
1265                     // Better way to do this?
1266                     exit(0);
1267                 }
1268             }
1269         }
1270
1271         if (!$checked && $lm && array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
1272             $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
1273             $ims = strtotime($if_modified_since);
1274             if ($lm <= $ims) {
1275                 header('HTTP/1.1 304 Not Modified');
1276                 // Better way to do this?
1277                 exit(0);
1278             }
1279         }
1280     }
1281
1282     /**
1283      * Is this action cacheable?
1284      *
1285      * If the action returns a last-modified
1286      *
1287      * @param array $argarray is ignored since it's now passed in in prepare()
1288      *
1289      * @return boolean is read only action?
1290      */
1291     function isCacheable()
1292     {
1293         return true;
1294     }
1295
1296     /**
1297      * Has etag? (private)
1298      *
1299      * @param string $etag          etag http header
1300      * @param string $if_none_match ifNoneMatch http header
1301      *
1302      * @return boolean
1303      */
1304     function _hasEtag($etag, $if_none_match)
1305     {
1306         $etags = explode(',', $if_none_match);
1307         return in_array($etag, $etags) || in_array('*', $etags);
1308     }
1309
1310     /**
1311      * Boolean understands english (yes, no, true, false)
1312      *
1313      * @param string $key query key we're interested in
1314      * @param string $def default value
1315      *
1316      * @return boolean interprets yes/no strings as boolean
1317      */
1318     function boolean($key, $def=false)
1319     {
1320         $arg = strtolower($this->trimmed($key));
1321
1322         if (is_null($arg)) {
1323             return $def;
1324         } else if (in_array($arg, array('true', 'yes', '1', 'on'))) {
1325             return true;
1326         } else if (in_array($arg, array('false', 'no', '0'))) {
1327             return false;
1328         } else {
1329             return $def;
1330         }
1331     }
1332
1333     /**
1334      * Integer value of an argument
1335      *
1336      * @param string $key      query key we're interested in
1337      * @param string $defValue optional default value (default null)
1338      * @param string $maxValue optional max value (default null)
1339      * @param string $minValue optional min value (default null)
1340      *
1341      * @return integer integer value
1342      */
1343     function int($key, $defValue=null, $maxValue=null, $minValue=null)
1344     {
1345         $arg = strtolower($this->trimmed($key));
1346
1347         if (is_null($arg) || !is_integer($arg)) {
1348             return $defValue;
1349         }
1350
1351         if (!is_null($maxValue)) {
1352             $arg = min($arg, $maxValue);
1353         }
1354
1355         if (!is_null($minValue)) {
1356             $arg = max($arg, $minValue);
1357         }
1358
1359         return $arg;
1360     }
1361
1362     /**
1363      * Server error
1364      *
1365      * @param string  $msg  error message to display
1366      * @param integer $code http error code, 500 by default
1367      *
1368      * @return nothing
1369      */
1370     function serverError($msg, $code=500, $format=null)
1371     {
1372         if ($format === null) {
1373             $format = $this->format;
1374         }
1375
1376         common_debug("Server error '{$code}' on '{$this->action}': {$msg}", __FILE__);
1377
1378         if (!array_key_exists($code, ServerErrorAction::$status)) {
1379             $code = 500;
1380         }
1381
1382         $status_string = ServerErrorAction::$status[$code];
1383
1384         switch ($format) {
1385         case 'xml':
1386             header("HTTP/1.1 {$code} {$status_string}");
1387             $this->initDocument('xml');
1388             $this->elementStart('hash');
1389             $this->element('error', null, $msg);
1390             $this->element('request', null, $_SERVER['REQUEST_URI']);
1391             $this->elementEnd('hash');
1392             $this->endDocument('xml');
1393             break;
1394         case 'json':
1395             if (!isset($this->callback)) {
1396                 header("HTTP/1.1 {$code} {$status_string}");
1397             }
1398             $this->initDocument('json');
1399             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
1400             print(json_encode($error_array));
1401             $this->endDocument('json');
1402             break;
1403         default:
1404             throw new ServerException($msg, $code);
1405         }
1406
1407         exit((int)$code);
1408     }
1409
1410     /**
1411      * Client error
1412      *
1413      * @param string  $msg    error message to display
1414      * @param integer $code   http error code, 400 by default
1415      * @param string  $format error format (json, xml, text) for ApiAction
1416      *
1417      * @return nothing
1418      * @throws ClientException always
1419      */
1420     function clientError($msg, $code=400, $format=null)
1421     {
1422         // $format is currently only relevant for an ApiAction anyway
1423         if ($format === null) {
1424             $format = $this->format;
1425         }
1426
1427         common_debug("User error '{$code}' on '{$this->action}': {$msg}", __FILE__);
1428
1429         if (!array_key_exists($code, ClientErrorAction::$status)) {
1430             $code = 400;
1431         }
1432         
1433         $status_string = ClientErrorAction::$status[$code];
1434
1435         switch ($format) {
1436         case 'xml':
1437             header("HTTP/1.1 {$code} {$status_string}");
1438             $this->initDocument('xml');
1439             $this->elementStart('hash');
1440             $this->element('error', null, $msg);
1441             $this->element('request', null, $_SERVER['REQUEST_URI']);
1442             $this->elementEnd('hash');
1443             $this->endDocument('xml');
1444             break;
1445         case 'json':
1446             if (!isset($this->callback)) {
1447                 header("HTTP/1.1 {$code} {$status_string}");
1448             }
1449             $this->initDocument('json');
1450             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
1451             $this->text(json_encode($error_array));
1452             $this->endDocument('json');
1453             break;
1454         case 'text':
1455             header("HTTP/1.1 {$code} {$status_string}");
1456             header('Content-Type: text/plain; charset=utf-8');
1457             echo $msg;
1458             break;
1459         default:
1460             throw new ClientException($msg, $code);
1461         }
1462         exit((int)$code);
1463     }
1464
1465     /**
1466      * If not logged in, take appropriate action (redir or exception)
1467      *
1468      * @param boolean $redir Redirect to login if not logged in
1469      *
1470      * @return boolean true if logged in (never returns if not)
1471      */
1472     public function checkLogin($redir=true)
1473     {
1474         if (common_logged_in()) {
1475             return true;
1476         }
1477
1478         if ($redir==true) {
1479             common_set_returnto($_SERVER['REQUEST_URI']);
1480             common_redirect(common_local_url('login'));
1481         }
1482
1483         // TRANS: Error message displayed when trying to perform an action that requires a logged in user.
1484         $this->clientError(_('Not logged in.'), 403);
1485     }
1486
1487     /**
1488      * Returns the current URL
1489      *
1490      * @return string current URL
1491      */
1492     function selfUrl()
1493     {
1494         list($action, $args) = $this->returnToArgs();
1495         return common_local_url($action, $args);
1496     }
1497
1498     /**
1499      * Returns arguments sufficient for re-constructing URL
1500      *
1501      * @return array two elements: action, other args
1502      */
1503     function returnToArgs()
1504     {
1505         $action = $this->trimmed('action');
1506         $args   = $this->args;
1507         unset($args['action']);
1508         if (common_config('site', 'fancy')) {
1509             unset($args['p']);
1510         }
1511         if (array_key_exists('submit', $args)) {
1512             unset($args['submit']);
1513         }
1514         foreach (array_keys($_COOKIE) as $cookie) {
1515             unset($args[$cookie]);
1516         }
1517         return array($action, $args);
1518     }
1519
1520     /**
1521      * Generate a menu item
1522      *
1523      * @param string  $url         menu URL
1524      * @param string  $text        menu name
1525      * @param string  $title       title attribute, null by default
1526      * @param boolean $is_selected current menu item, false by default
1527      * @param string  $id          element id, null by default
1528      *
1529      * @return nothing
1530      */
1531     function menuItem($url, $text, $title=null, $is_selected=false, $id=null, $class=null)
1532     {
1533         // Added @id to li for some control.
1534         // XXX: We might want to move this to htmloutputter.php
1535         $lattrs  = array();
1536         $classes = array();
1537         if ($class !== null) {
1538             $classes[] = trim($class);
1539         }
1540         if ($is_selected) {
1541             $classes[] = 'current';
1542         }
1543
1544         if (!empty($classes)) {
1545             $lattrs['class'] = implode(' ', $classes);
1546         }
1547
1548         if (!is_null($id)) {
1549             $lattrs['id'] = $id;
1550         }
1551
1552         $this->elementStart('li', $lattrs);
1553         $attrs['href'] = $url;
1554         if ($title) {
1555             $attrs['title'] = $title;
1556         }
1557         $this->element('a', $attrs, $text);
1558         $this->elementEnd('li');
1559     }
1560
1561     /**
1562      * Generate pagination links
1563      *
1564      * @param boolean $have_before is there something before?
1565      * @param boolean $have_after  is there something after?
1566      * @param integer $page        current page
1567      * @param string  $action      current action
1568      * @param array   $args        rest of query arguments
1569      *
1570      * @return nothing
1571      */
1572     // XXX: The messages in this pagination method only tailor to navigating
1573     //      notices. In other lists, "Previous"/"Next" type navigation is
1574     //      desirable, but not available.
1575     function pagination($have_before, $have_after, $page, $action, $args=null)
1576     {
1577         // Does a little before-after block for next/prev page
1578         if ($have_before || $have_after) {
1579             $this->elementStart('ul', array('class' => 'nav',
1580                                             'id' => 'pagination'));
1581         }
1582         if ($have_before) {
1583             $pargs   = array('page' => $page-1);
1584             $this->elementStart('li', array('class' => 'nav_prev'));
1585             $this->element('a', array('href' => common_local_url($action, $args, $pargs),
1586                                       'rel' => 'prev'),
1587                            // TRANS: Pagination message to go to a page displaying information more in the
1588                            // TRANS: present than the currently displayed information.
1589                            _('After'));
1590             $this->elementEnd('li');
1591         }
1592         if ($have_after) {
1593             $pargs   = array('page' => $page+1);
1594             $this->elementStart('li', array('class' => 'nav_next'));
1595             $this->element('a', array('href' => common_local_url($action, $args, $pargs),
1596                                       'rel' => 'next'),
1597                            // TRANS: Pagination message to go to a page displaying information more in the
1598                            // TRANS: past than the currently displayed information.
1599                            _('Before'));
1600             $this->elementEnd('li');
1601         }
1602         if ($have_before || $have_after) {
1603             $this->elementEnd('ul');
1604         }
1605     }
1606
1607     /**
1608      * An array of feeds for this action.
1609      *
1610      * Returns an array of potential feeds for this action.
1611      *
1612      * @return array Feed object to show in head and links
1613      */
1614     function getFeeds()
1615     {
1616         return null;
1617     }
1618
1619     /**
1620      * Check the session token.
1621      *
1622      * Checks that the current form has the correct session token,
1623      * and throw an exception if it does not.
1624      *
1625      * @return void
1626      */
1627     // XXX: Finding this type of check with the same message about 50 times.
1628     //      Possible to refactor?
1629     function checkSessionToken()
1630     {
1631         // CSRF protection
1632         $token = $this->trimmed('token');
1633         if (empty($token) || $token != common_session_token()) {
1634             // TRANS: Client error text when there is a problem with the session token.
1635             $this->clientError(_('There was a problem with your session token.'));
1636         }
1637     }
1638
1639     /**
1640      * Check if the current request is a POST
1641      *
1642      * @return boolean true if POST; otherwise false.
1643      */
1644
1645     function isPost()
1646     {
1647         return ($_SERVER['REQUEST_METHOD'] == 'POST');
1648     }
1649 }