]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/action.php
Merge branch 'testing' into 0.9.x
[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('STATUSNET') && !defined('LACONICA')) {
32     exit(1);
33 }
34
35 require_once INSTALLDIR.'/lib/noticeform.php';
36 require_once INSTALLDIR.'/lib/htmloutputter.php';
37
38 /**
39  * Base class for all actions
40  *
41  * This is the base class for all actions in the package. An action is
42  * more or less a "view" in an MVC framework.
43  *
44  * Actions are responsible for extracting and validating parameters; using
45  * model classes to read and write to the database; and doing ouput.
46  *
47  * @category Output
48  * @package  StatusNet
49  * @author   Evan Prodromou <evan@status.net>
50  * @author   Sarven Capadisli <csarven@status.net>
51  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
52  * @link     http://status.net/
53  *
54  * @see      HTMLOutputter
55  */
56 class Action extends HTMLOutputter // lawsuit
57 {
58     var $args;
59
60     /**
61      * Constructor
62      *
63      * Just wraps the HTMLOutputter constructor.
64      *
65      * @param string  $output URI to output to, default = stdout
66      * @param boolean $indent Whether to indent output, default true
67      *
68      * @see XMLOutputter::__construct
69      * @see HTMLOutputter::__construct
70      */
71     function __construct($output='php://output', $indent=null)
72     {
73         parent::__construct($output, $indent);
74     }
75
76     /**
77      * For initializing members of the class.
78      *
79      * @param array $argarray misc. arguments
80      *
81      * @return boolean true
82      */
83     function prepare($argarray)
84     {
85         $this->args =& common_copy_args($argarray);
86         return true;
87     }
88
89     /**
90      * Show page, a template method.
91      *
92      * @return nothing
93      */
94     function showPage()
95     {
96         if (Event::handle('StartShowHTML', array($this))) {
97             $this->startHTML();
98             Event::handle('EndShowHTML', array($this));
99         }
100         if (Event::handle('StartShowHead', array($this))) {
101             $this->showHead();
102             Event::handle('EndShowHead', array($this));
103         }
104         if (Event::handle('StartShowBody', array($this))) {
105             $this->showBody();
106             Event::handle('EndShowBody', array($this));
107         }
108         if (Event::handle('StartEndHTML', array($this))) {
109             $this->endHTML();
110             Event::handle('EndEndHTML', array($this));
111         }
112     }
113
114     /**
115      * Show head, a template method.
116      *
117      * @return nothing
118      */
119     function showHead()
120     {
121         // XXX: attributes (profile?)
122         $this->elementStart('head');
123         if (Event::handle('StartShowHeadElements', array($this))) {
124             $this->showTitle();
125             $this->showShortcutIcon();
126             $this->showStylesheets();
127             $this->showOpenSearch();
128             $this->showFeeds();
129             $this->showDescription();
130             $this->extraHead();
131             Event::handle('EndShowHeadElements', array($this));
132         }
133         $this->elementEnd('head');
134     }
135
136     /**
137      * Show title, a template method.
138      *
139      * @return nothing
140      */
141     function showTitle()
142     {
143         $this->element('title', null,
144                        // TRANS: Page title. %1$s is the title, %2$s is the site name.
145                        sprintf(_("%1\$s - %2\$s"),
146                                $this->title(),
147                                common_config('site', 'name')));
148     }
149
150     /**
151      * Returns the page title
152      *
153      * SHOULD overload
154      *
155      * @return string page title
156      */
157
158     function title()
159     {
160         // TRANS: Page title for a page without a title set.
161         return _("Untitled page");
162     }
163
164     /**
165      * Show themed shortcut icon
166      *
167      * @return nothing
168      */
169     function showShortcutIcon()
170     {
171         if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/favicon.ico')) {
172             $this->element('link', array('rel' => 'shortcut icon',
173                                          'href' => Theme::path('favicon.ico')));
174         } else {
175             $this->element('link', array('rel' => 'shortcut icon',
176                                          'href' => common_path('favicon.ico')));
177         }
178
179         if (common_config('site', 'mobile')) {
180             if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/apple-touch-icon.png')) {
181                 $this->element('link', array('rel' => 'apple-touch-icon',
182                                              'href' => Theme::path('apple-touch-icon.png')));
183             } else {
184                 $this->element('link', array('rel' => 'apple-touch-icon',
185                                              'href' => common_path('apple-touch-icon.png')));
186             }
187         }
188     }
189
190     /**
191      * Show stylesheets
192      *
193      * @return nothing
194      */
195     function showStylesheets()
196     {
197         if (Event::handle('StartShowStyles', array($this))) {
198
199             // Use old name for StatusNet for compatibility on events
200
201             if (Event::handle('StartShowStatusNetStyles', array($this)) &&
202                 Event::handle('StartShowLaconicaStyles', array($this))) {
203                 $this->cssLink('css/display.css',null, 'screen, projection, tv, print');
204                 Event::handle('EndShowStatusNetStyles', array($this));
205                 Event::handle('EndShowLaconicaStyles', array($this));
206             }
207
208             if (Event::handle('StartShowUAStyles', array($this))) {
209                 $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
210                                'href="'.Theme::path('css/ie.css', 'base').'?version='.STATUSNET_VERSION.'" /><![endif]');
211                 foreach (array(6,7) as $ver) {
212                     if (file_exists(Theme::file('css/ie'.$ver.'.css', 'base'))) {
213                         // Yes, IE people should be put in jail.
214                         $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
215                                        'href="'.Theme::path('css/ie'.$ver.'.css', 'base').'?version='.STATUSNET_VERSION.'" /><![endif]');
216                     }
217                 }
218                 $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
219                                'href="'.Theme::path('css/ie.css', null).'?version='.STATUSNET_VERSION.'" /><![endif]');
220                 Event::handle('EndShowUAStyles', array($this));
221             }
222
223             if (Event::handle('StartShowDesign', array($this))) {
224
225                 $user = common_current_user();
226
227                 if (empty($user) || $user->viewdesigns) {
228                     $design = $this->getDesign();
229
230                     if (!empty($design)) {
231                         $design->showCSS($this);
232                     }
233                 }
234
235                 Event::handle('EndShowDesign', array($this));
236             }
237             Event::handle('EndShowStyles', array($this));
238         }
239     }
240
241     /**
242      * Show javascript headers
243      *
244      * @return nothing
245      */
246     function showScripts()
247     {
248         if (Event::handle('StartShowScripts', array($this))) {
249             if (Event::handle('StartShowJQueryScripts', array($this))) {
250                 $this->script('jquery.min.js');
251                 $this->script('jquery.form.js');
252                 $this->script('jquery.cookie.js');
253                 $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.js').'"); }');
254                 $this->script('jquery.joverlay.min.js');
255                 Event::handle('EndShowJQueryScripts', array($this));
256             }
257             if (Event::handle('StartShowStatusNetScripts', array($this)) &&
258                 Event::handle('StartShowLaconicaScripts', array($this))) {
259                 $this->script('xbImportNode.js');
260                 $this->script('util.js');
261                 $this->script('geometa.js');
262                 // Frame-busting code to avoid clickjacking attacks.
263                 $this->inlineScript('if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
264                 Event::handle('EndShowStatusNetScripts', array($this));
265                 Event::handle('EndShowLaconicaScripts', array($this));
266             }
267             Event::handle('EndShowScripts', array($this));
268         }
269     }
270
271     /**
272      * Show OpenSearch headers
273      *
274      * @return nothing
275      */
276     function showOpenSearch()
277     {
278         $this->element('link', array('rel' => 'search',
279                                      'type' => 'application/opensearchdescription+xml',
280                                      'href' =>  common_local_url('opensearch', array('type' => 'people')),
281                                      'title' => common_config('site', 'name').' People Search'));
282         $this->element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
283                                      'href' =>  common_local_url('opensearch', array('type' => 'notice')),
284                                      'title' => common_config('site', 'name').' Notice Search'));
285     }
286
287     /**
288      * Show feed headers
289      *
290      * MAY overload
291      *
292      * @return nothing
293      */
294
295     function showFeeds()
296     {
297         $feeds = $this->getFeeds();
298
299         if ($feeds) {
300             foreach ($feeds as $feed) {
301                 $this->element('link', array('rel' => $feed->rel(),
302                                              'href' => $feed->url,
303                                              'type' => $feed->mimeType(),
304                                              'title' => $feed->title));
305             }
306         }
307     }
308
309     /**
310      * Show description.
311      *
312      * SHOULD overload
313      *
314      * @return nothing
315      */
316     function showDescription()
317     {
318         // does nothing by default
319     }
320
321     /**
322      * Show extra stuff in <head>.
323      *
324      * MAY overload
325      *
326      * @return nothing
327      */
328     function extraHead()
329     {
330         // does nothing by default
331     }
332
333     /**
334      * Show body.
335      *
336      * Calls template methods
337      *
338      * @return nothing
339      */
340     function showBody()
341     {
342         $this->elementStart('body', (common_current_user()) ? array('id' => $this->trimmed('action'),
343                                                                     'class' => 'user_in')
344                             : array('id' => $this->trimmed('action')));
345         $this->elementStart('div', array('id' => 'wrap'));
346         if (Event::handle('StartShowHeader', array($this))) {
347             $this->showHeader();
348             Event::handle('EndShowHeader', array($this));
349         }
350         $this->showCore();
351         if (Event::handle('StartShowFooter', array($this))) {
352             $this->showFooter();
353             Event::handle('EndShowFooter', array($this));
354         }
355         $this->elementEnd('div');
356         $this->showScripts();
357         $this->elementEnd('body');
358     }
359
360     /**
361      * Show header of the page.
362      *
363      * Calls template methods
364      *
365      * @return nothing
366      */
367     function showHeader()
368     {
369         $this->elementStart('div', array('id' => 'header'));
370         $this->showLogo();
371         $this->showPrimaryNav();
372         if (Event::handle('StartShowSiteNotice', array($this))) {
373             $this->showSiteNotice();
374
375             Event::handle('EndShowSiteNotice', array($this));
376         }
377         if (common_logged_in()) {
378             $this->showNoticeForm();
379         } else {
380             $this->showAnonymousMessage();
381         }
382         $this->elementEnd('div');
383     }
384
385     /**
386      * Show configured logo.
387      *
388      * @return nothing
389      */
390     function showLogo()
391     {
392         $this->elementStart('address', array('id' => 'site_contact',
393                                              'class' => 'vcard'));
394         if (Event::handle('StartAddressData', array($this))) {
395             if (common_config('singleuser', 'enabled')) {
396                 $url = common_local_url('showstream',
397                                         array('nickname' => common_config('singleuser', 'nickname')));
398             } else {
399                 $url = common_local_url('public');
400             }
401             $this->elementStart('a', array('class' => 'url home bookmark',
402                                            'href' => $url));
403             if (common_config('site', 'logo') || file_exists(Theme::file('logo.png'))) {
404                 $this->element('img', array('class' => 'logo photo',
405                                             'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'),
406                                             'alt' => common_config('site', 'name')));
407             }
408             $this->text(' ');
409             $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
410             $this->elementEnd('a');
411             Event::handle('EndAddressData', array($this));
412         }
413         $this->elementEnd('address');
414     }
415
416     /**
417      * Show primary navigation.
418      *
419      * @return nothing
420      */
421     function showPrimaryNav()
422     {
423         $user = common_current_user();
424         $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
425         // TRANS: DT element for primary navigation menu. String is hidden in default CSS.
426         $this->element('dt', null, _('Primary site navigation'));
427         $this->elementStart('dd');
428         $this->elementStart('ul', array('class' => 'nav'));
429         if (Event::handle('StartPrimaryNav', array($this))) {
430             if ($user) {
431                 // TRANS: Tooltip for main menu option "Personal"
432                 $tooltip = _m('TOOLTIP', 'Personal profile and friends timeline');
433                 $this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
434                                 // TRANS: Main menu option when logged in for access to personal profile and friends timeline
435                                 _m('MENU', 'Personal'), $tooltip, false, 'nav_home');
436                 // TRANS: Tooltip for main menu option "Account"
437                 $tooltip = _m('TOOLTIP', 'Change your email, avatar, password, profile');
438                 $this->menuItem(common_local_url('profilesettings'),
439                                 // TRANS: Main menu option when logged in for access to user settings
440                                 _('Account'), $tooltip, false, 'nav_account');
441                 // TRANS: Tooltip for main menu option "Services"
442                 $tooltip = _m('TOOLTIP', 'Connect to services');
443                 $this->menuItem(common_local_url('oauthconnectionssettings'),
444                                 // TRANS: Main menu option when logged in and connection are possible for access to options to connect to other services
445                                 _('Connect'), $tooltip, false, 'nav_connect');
446                 if ($user->hasRight(Right::CONFIGURESITE)) {
447                     // TRANS: Tooltip for menu option "Admin"
448                     $tooltip = _m('TOOLTIP', 'Change site configuration');
449                     $this->menuItem(common_local_url('siteadminpanel'),
450                                     // TRANS: Main menu option when logged in and site admin for access to site configuration
451                                     _m('MENU', 'Admin'), $tooltip, false, 'nav_admin');
452                 }
453                 if (common_config('invite', 'enabled')) {
454                     // TRANS: Tooltip for main menu option "Invite"
455                     $tooltip = _m('TOOLTIP', 'Invite friends and colleagues to join you on %s');
456                     $this->menuItem(common_local_url('invite'),
457                                     // TRANS: Main menu option when logged in and invitations are allowed for inviting new users
458                                     _m('MENU', 'Invite'),
459                                     sprintf($tooltip,
460                                             common_config('site', 'name')),
461                                     false, 'nav_invitecontact');
462                 }
463                 // TRANS: Tooltip for main menu option "Logout"
464                 $tooltip = _m('TOOLTIP', 'Logout from the site');
465                 $this->menuItem(common_local_url('logout'),
466                                 // TRANS: Main menu option when logged in to log out the current user
467                                 _m('MENU', 'Logout'), $tooltip, false, 'nav_logout');
468             }
469             else {
470                 if (!common_config('site', 'closed') && !common_config('site', 'inviteonly')) {
471                     // TRANS: Tooltip for main menu option "Register"
472                     $tooltip = _m('TOOLTIP', 'Create an account');
473                     $this->menuItem(common_local_url('register'),
474                                     // TRANS: Main menu option when not logged in to register a new account
475                                     _m('MENU', 'Register'), $tooltip, false, 'nav_register');
476                 }
477                 // TRANS: Tooltip for main menu option "Login"
478                 $tooltip = _m('TOOLTIP', 'Login to the site');
479                 // TRANS: Main menu option when not logged in to log in
480                 $this->menuItem(common_local_url('login'),
481                                 _m('MENU', 'Login'), $tooltip, false, 'nav_login');
482             }
483             // TRANS: Tooltip for main menu option "Help"
484             $tooltip = _m('TOOLTIP', 'Help me!');
485             // TRANS: Main menu option for help on the StatusNet site
486             $this->menuItem(common_local_url('doc', array('title' => 'help')),
487                             _m('MENU', 'Help'), $tooltip, false, 'nav_help');
488             if ($user || !common_config('site', 'private')) {
489                 // TRANS: Tooltip for main menu option "Search"
490                 $tooltip = _m('TOOLTIP', 'Search for people or text');
491                 // TRANS: Main menu option when logged in or when the StatusNet instance is not private
492                 $this->menuItem(common_local_url('peoplesearch'),
493                                 _m('MENU', 'Search'), $tooltip, false, 'nav_search');
494             }
495             Event::handle('EndPrimaryNav', array($this));
496         }
497         $this->elementEnd('ul');
498         $this->elementEnd('dd');
499         $this->elementEnd('dl');
500     }
501
502     /**
503      * Show site notice.
504      *
505      * @return nothing
506      */
507     function showSiteNotice()
508     {
509         // Revist. Should probably do an hAtom pattern here
510         $text = common_config('site', 'notice');
511         if ($text) {
512             $this->elementStart('dl', array('id' => 'site_notice',
513                                             'class' => 'system_notice'));
514             // TRANS: DT element for site notice. String is hidden in default CSS.
515             $this->element('dt', null, _('Site notice'));
516             $this->elementStart('dd', null);
517             $this->raw($text);
518             $this->elementEnd('dd');
519             $this->elementEnd('dl');
520         }
521     }
522
523     /**
524      * Show notice form.
525      *
526      * MAY overload if no notice form needed... or direct message box????
527      *
528      * @return nothing
529      */
530     function showNoticeForm()
531     {
532         $notice_form = new NoticeForm($this);
533         $notice_form->show();
534     }
535
536     /**
537      * Show anonymous message.
538      *
539      * SHOULD overload
540      *
541      * @return nothing
542      */
543     function showAnonymousMessage()
544     {
545         // needs to be defined by the class
546     }
547
548     /**
549      * Show core.
550      *
551      * Shows local navigation, content block and aside.
552      *
553      * @return nothing
554      */
555     function showCore()
556     {
557         $this->elementStart('div', array('id' => 'core'));
558         if (Event::handle('StartShowLocalNavBlock', array($this))) {
559             $this->showLocalNavBlock();
560             Event::handle('EndShowLocalNavBlock', array($this));
561         }
562         if (Event::handle('StartShowContentBlock', array($this))) {
563             $this->showContentBlock();
564             Event::handle('EndShowContentBlock', array($this));
565         }
566         if (Event::handle('StartShowAside', array($this))) {
567             $this->showAside();
568             Event::handle('EndShowAside', array($this));
569         }
570         $this->elementEnd('div');
571     }
572
573     /**
574      * Show local navigation block.
575      *
576      * @return nothing
577      */
578     function showLocalNavBlock()
579     {
580         $this->elementStart('dl', array('id' => 'site_nav_local_views'));
581         // TRANS: DT element for local views block. String is hidden in default CSS.
582         $this->element('dt', null, _('Local views'));
583         $this->elementStart('dd');
584         $this->showLocalNav();
585         $this->elementEnd('dd');
586         $this->elementEnd('dl');
587     }
588
589     /**
590      * Show local navigation.
591      *
592      * SHOULD overload
593      *
594      * @return nothing
595      */
596     function showLocalNav()
597     {
598         // does nothing by default
599     }
600
601     /**
602      * Show content block.
603      *
604      * @return nothing
605      */
606     function showContentBlock()
607     {
608         $this->elementStart('div', array('id' => 'content'));
609         $this->showPageTitle();
610         $this->showPageNoticeBlock();
611         $this->elementStart('div', array('id' => 'content_inner'));
612         // show the actual content (forms, lists, whatever)
613         $this->showContent();
614         $this->elementEnd('div');
615         $this->elementEnd('div');
616     }
617
618     /**
619      * Show page title.
620      *
621      * @return nothing
622      */
623     function showPageTitle()
624     {
625         $this->element('h1', null, $this->title());
626     }
627
628     /**
629      * Show page notice block.
630      *
631      * Only show the block if a subclassed action has overrided
632      * Action::showPageNotice(), or an event handler is registered for
633      * the StartShowPageNotice event, in which case we assume the
634      * 'page_notice' definition list is desired.  This is to prevent
635      * empty 'page_notice' definition lists from being output everywhere.
636      *
637      * @return nothing
638      */
639     function showPageNoticeBlock()
640     {
641         $rmethod = new ReflectionMethod($this, 'showPageNotice');
642         $dclass = $rmethod->getDeclaringClass()->getName();
643
644         if ($dclass != 'Action' || Event::hasHandler('StartShowPageNotice')) {
645
646             $this->elementStart('dl', array('id' => 'page_notice',
647                                             'class' => 'system_notice'));
648             // TRANS: DT element for page notice. String is hidden in default CSS.
649             $this->element('dt', null, _('Page notice'));
650             $this->elementStart('dd');
651             if (Event::handle('StartShowPageNotice', array($this))) {
652                 $this->showPageNotice();
653                 Event::handle('EndShowPageNotice', array($this));
654             }
655             $this->elementEnd('dd');
656             $this->elementEnd('dl');
657         }
658     }
659
660     /**
661      * Show page notice.
662      *
663      * SHOULD overload (unless there's not a notice)
664      *
665      * @return nothing
666      */
667     function showPageNotice()
668     {
669     }
670
671     /**
672      * Show content.
673      *
674      * MUST overload (unless there's not a notice)
675      *
676      * @return nothing
677      */
678     function showContent()
679     {
680     }
681
682     /**
683      * Show Aside.
684      *
685      * @return nothing
686      */
687
688     function showAside()
689     {
690         $this->elementStart('div', array('id' => 'aside_primary',
691                                          'class' => 'aside'));
692         if (Event::handle('StartShowExportData', array($this))) {
693             $this->showExportData();
694             Event::handle('EndShowExportData', array($this));
695         }
696         if (Event::handle('StartShowSections', array($this))) {
697             $this->showSections();
698             Event::handle('EndShowSections', array($this));
699         }
700         $this->elementEnd('div');
701     }
702
703     /**
704      * Show export data feeds.
705      *
706      * @return void
707      */
708
709     function showExportData()
710     {
711         $feeds = $this->getFeeds();
712         if ($feeds) {
713             $fl = new FeedList($this);
714             $fl->show($feeds);
715         }
716     }
717
718     /**
719      * Show sections.
720      *
721      * SHOULD overload
722      *
723      * @return nothing
724      */
725     function showSections()
726     {
727         // for each section, show it
728     }
729
730     /**
731      * Show footer.
732      *
733      * @return nothing
734      */
735     function showFooter()
736     {
737         $this->elementStart('div', array('id' => 'footer'));
738         $this->showSecondaryNav();
739         $this->showLicenses();
740         $this->elementEnd('div');
741     }
742
743     /**
744      * Show secondary navigation.
745      *
746      * @return nothing
747      */
748     function showSecondaryNav()
749     {
750         $this->elementStart('dl', array('id' => 'site_nav_global_secondary'));
751         // TRANS: DT element for secondary navigation menu. String is hidden in default CSS.
752         $this->element('dt', null, _('Secondary site navigation'));
753         $this->elementStart('dd', null);
754         $this->elementStart('ul', array('class' => 'nav'));
755         if (Event::handle('StartSecondaryNav', array($this))) {
756             $this->menuItem(common_local_url('doc', array('title' => 'help')),
757                             // TRANS: Secondary navigation menu option leading to help on StatusNet.
758                             _('Help'));
759             $this->menuItem(common_local_url('doc', array('title' => 'about')),
760                             // TRANS: Secondary navigation menu option leading to text about StatusNet site.
761                             _('About'));
762             $this->menuItem(common_local_url('doc', array('title' => 'faq')),
763                             // TRANS: Secondary navigation menu option leading to Frequently Asked Questions.
764                             _('FAQ'));
765             $bb = common_config('site', 'broughtby');
766             if (!empty($bb)) {
767                 $this->menuItem(common_local_url('doc', array('title' => 'tos')),
768                                 // TRANS: Secondary navigation menu option leading to Terms of Service.
769                                 _('TOS'));
770             }
771             $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
772                             // TRANS: Secondary navigation menu option leading to privacy policy.
773                             _('Privacy'));
774             $this->menuItem(common_local_url('doc', array('title' => 'source')),
775                             // TRANS: Secondary navigation menu option.
776                             _('Source'));
777             $this->menuItem(common_local_url('version'),
778                             // TRANS: Secondary navigation menu option leading to version information on the StatusNet site.
779                             _('Version'));
780             $this->menuItem(common_local_url('doc', array('title' => 'contact')),
781                             // TRANS: Secondary navigation menu option leading to contact information on the StatusNet site.
782                             _('Contact'));
783             $this->menuItem(common_local_url('doc', array('title' => 'badge')),
784                             _('Badge'));
785             Event::handle('EndSecondaryNav', array($this));
786         }
787         $this->elementEnd('ul');
788         $this->elementEnd('dd');
789         $this->elementEnd('dl');
790     }
791
792     /**
793      * Show licenses.
794      *
795      * @return nothing
796      */
797     function showLicenses()
798     {
799         $this->elementStart('dl', array('id' => 'licenses'));
800         $this->showStatusNetLicense();
801         $this->showContentLicense();
802         $this->elementEnd('dl');
803     }
804
805     /**
806      * Show StatusNet license.
807      *
808      * @return nothing
809      */
810     function showStatusNetLicense()
811     {
812         // TRANS: DT element for StatusNet software license.
813         $this->element('dt', array('id' => 'site_statusnet_license'), _('StatusNet software license'));
814         $this->elementStart('dd', null);
815         if (common_config('site', 'broughtby')) {
816             // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is set.
817             $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%).');
818         } else {
819             // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is not set.
820             $instr = _('**%%site.name%%** is a microblogging service.');
821         }
822         $instr .= ' ';
823         // TRANS: Second sentence of the StatusNet site license. Mentions the StatusNet source code license.
824         $instr .= sprintf(_('It runs the [StatusNet](http://status.net/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), STATUSNET_VERSION);
825         $output = common_markup_to_html($instr);
826         $this->raw($output);
827         $this->elementEnd('dd');
828         // do it
829     }
830
831     /**
832      * Show content license.
833      *
834      * @return nothing
835      */
836     function showContentLicense()
837     {
838         if (Event::handle('StartShowContentLicense', array($this))) {
839             // TRANS: DT element for StatusNet site content license.
840             $this->element('dt', array('id' => 'site_content_license'), _('Site content license'));
841             $this->elementStart('dd', array('id' => 'site_content_license_cc'));
842
843             switch (common_config('license', 'type')) {
844             case 'private':
845                 // TRANS: Content license displayed when license is set to 'private'.
846                 // TRANS: %1$s is the site name.
847                 $this->element('p', null, sprintf(_('Content and data of %1$s are private and confidential.'),
848                                                   common_config('site', 'name')));
849                 // fall through
850             case 'allrightsreserved':
851                 if (common_config('license', 'owner')) {
852                     // TRANS: Content license displayed when license is set to 'allrightsreserved'.
853                     // TRANS: %1$s is the copyright owner.
854                     $this->element('p', null, sprintf(_('Content and data copyright by %1$s. All rights reserved.'),
855                                                       common_config('license', 'owner')));
856                 } else {
857                     // TRANS: Content license displayed when license is set to 'allrightsreserved' and no owner is set.
858                     $this->element('p', null, _('Content and data copyright by contributors. All rights reserved.'));
859                 }
860                 break;
861             case 'cc': // fall through
862             default:
863                 $this->elementStart('p');
864                 $this->element('img', array('id' => 'license_cc',
865                                             'src' => common_config('license', 'image'),
866                                             'alt' => common_config('license', 'title'),
867                                             'width' => '80',
868                                             'height' => '15'));
869                 $this->text(' ');
870                 // TRANS: license message in footer. %1$s is the site name, %2$s is a link to the license URL, with a licence name set in configuration.
871                 $notice = _('All %1$s content and data are available under the %2$s license.');
872                 $link = "<a class=\"license\" rel=\"external license\" href=\"" .
873                         htmlspecialchars(common_config('license', 'url')) .
874                         "\">" .
875                         htmlspecialchars(common_config('license', 'title')) .
876                         "</a>";
877                 $this->raw(sprintf(htmlspecialchars($notice),
878                                    htmlspecialchars(common_config('site', 'name')),
879                                    $link));
880                 $this->elementEnd('p');
881                 break;
882             }
883
884             $this->elementEnd('dd');
885             Event::handle('EndShowContentLicense', array($this));
886         }
887     }
888
889     /**
890      * Return last modified, if applicable.
891      *
892      * MAY override
893      *
894      * @return string last modified http header
895      */
896     function lastModified()
897     {
898         // For comparison with If-Last-Modified
899         // If not applicable, return null
900         return null;
901     }
902
903     /**
904      * Return etag, if applicable.
905      *
906      * MAY override
907      *
908      * @return string etag http header
909      */
910     function etag()
911     {
912         return null;
913     }
914
915     /**
916      * Return true if read only.
917      *
918      * MAY override
919      *
920      * @param array $args other arguments
921      *
922      * @return boolean is read only action?
923      */
924
925     function isReadOnly($args)
926     {
927         return false;
928     }
929
930     /**
931      * Returns query argument or default value if not found
932      *
933      * @param string $key requested argument
934      * @param string $def default value to return if $key is not provided
935      *
936      * @return boolean is read only action?
937      */
938     function arg($key, $def=null)
939     {
940         if (array_key_exists($key, $this->args)) {
941             return $this->args[$key];
942         } else {
943             return $def;
944         }
945     }
946
947     /**
948      * Returns trimmed query argument or default value if not found
949      *
950      * @param string $key requested argument
951      * @param string $def default value to return if $key is not provided
952      *
953      * @return boolean is read only action?
954      */
955     function trimmed($key, $def=null)
956     {
957         $arg = $this->arg($key, $def);
958         return is_string($arg) ? trim($arg) : $arg;
959     }
960
961     /**
962      * Handler method
963      *
964      * @param array $argarray is ignored since it's now passed in in prepare()
965      *
966      * @return boolean is read only action?
967      */
968     function handle($argarray=null)
969     {
970         header('Vary: Accept-Encoding,Cookie');
971         $lm   = $this->lastModified();
972         $etag = $this->etag();
973         if ($etag) {
974             header('ETag: ' . $etag);
975         }
976         if ($lm) {
977             header('Last-Modified: ' . date(DATE_RFC1123, $lm));
978             if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
979                 $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
980                 $ims = strtotime($if_modified_since);
981                 if ($lm <= $ims) {
982                     $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
983                       $_SERVER['HTTP_IF_NONE_MATCH'] : null;
984                     if (!$if_none_match ||
985                         !$etag ||
986                         $this->_hasEtag($etag, $if_none_match)) {
987                         header('HTTP/1.1 304 Not Modified');
988                         // Better way to do this?
989                         exit(0);
990                     }
991                 }
992             }
993         }
994     }
995
996     /**
997      * HasĀ etag? (private)
998      *
999      * @param string $etag          etag http header
1000      * @param string $if_none_match ifNoneMatch http header
1001      *
1002      * @return boolean
1003      */
1004
1005     function _hasEtag($etag, $if_none_match)
1006     {
1007         $etags = explode(',', $if_none_match);
1008         return in_array($etag, $etags) || in_array('*', $etags);
1009     }
1010
1011     /**
1012      * Boolean understands english (yes, no, true, false)
1013      *
1014      * @param string $key query key we're interested in
1015      * @param string $def default value
1016      *
1017      * @return boolean interprets yes/no strings as boolean
1018      */
1019     function boolean($key, $def=false)
1020     {
1021         $arg = strtolower($this->trimmed($key));
1022
1023         if (is_null($arg)) {
1024             return $def;
1025         } else if (in_array($arg, array('true', 'yes', '1', 'on'))) {
1026             return true;
1027         } else if (in_array($arg, array('false', 'no', '0'))) {
1028             return false;
1029         } else {
1030             return $def;
1031         }
1032     }
1033
1034     /**
1035      * Integer value of an argument
1036      *
1037      * @param string $key      query key we're interested in
1038      * @param string $defValue optional default value (default null)
1039      * @param string $maxValue optional max value (default null)
1040      * @param string $minValue optional min value (default null)
1041      *
1042      * @return integer integer value
1043      */
1044
1045     function int($key, $defValue=null, $maxValue=null, $minValue=null)
1046     {
1047         $arg = strtolower($this->trimmed($key));
1048
1049         if (is_null($arg) || !is_integer($arg)) {
1050             return $defValue;
1051         }
1052
1053         if (!is_null($maxValue)) {
1054             $arg = min($arg, $maxValue);
1055         }
1056
1057         if (!is_null($minValue)) {
1058             $arg = max($arg, $minValue);
1059         }
1060
1061         return $arg;
1062     }
1063
1064     /**
1065      * Server error
1066      *
1067      * @param string  $msg  error message to display
1068      * @param integer $code http error code, 500 by default
1069      *
1070      * @return nothing
1071      */
1072
1073     function serverError($msg, $code=500)
1074     {
1075         $action = $this->trimmed('action');
1076         common_debug("Server error '$code' on '$action': $msg", __FILE__);
1077         throw new ServerException($msg, $code);
1078     }
1079
1080     /**
1081      * Client error
1082      *
1083      * @param string  $msg  error message to display
1084      * @param integer $code http error code, 400 by default
1085      *
1086      * @return nothing
1087      */
1088
1089     function clientError($msg, $code=400)
1090     {
1091         $action = $this->trimmed('action');
1092         common_debug("User error '$code' on '$action': $msg", __FILE__);
1093         throw new ClientException($msg, $code);
1094     }
1095
1096     /**
1097      * Returns the current URL
1098      *
1099      * @return string current URL
1100      */
1101
1102     function selfUrl()
1103     {
1104         list($action, $args) = $this->returnToArgs();
1105         return common_local_url($action, $args);
1106     }
1107
1108     /**
1109      * Returns arguments sufficient for re-constructing URL
1110      *
1111      * @return array two elements: action, other args
1112      */
1113
1114     function returnToArgs()
1115     {
1116         $action = $this->trimmed('action');
1117         $args   = $this->args;
1118         unset($args['action']);
1119         if (common_config('site', 'fancy')) {
1120             unset($args['p']);
1121         }
1122         if (array_key_exists('submit', $args)) {
1123             unset($args['submit']);
1124         }
1125         foreach (array_keys($_COOKIE) as $cookie) {
1126             unset($args[$cookie]);
1127         }
1128         return array($action, $args);
1129     }
1130
1131     /**
1132      * Generate a menu item
1133      *
1134      * @param string  $url         menu URL
1135      * @param string  $text        menu name
1136      * @param string  $title       title attribute, null by default
1137      * @param boolean $is_selected current menu item, false by default
1138      * @param string  $id          element id, null by default
1139      *
1140      * @return nothing
1141      */
1142     function menuItem($url, $text, $title=null, $is_selected=false, $id=null)
1143     {
1144         // Added @id to li for some control.
1145         // XXX: We might want to move this to htmloutputter.php
1146         $lattrs = array();
1147         if ($is_selected) {
1148             $lattrs['class'] = 'current';
1149         }
1150
1151         (is_null($id)) ? $lattrs : $lattrs['id'] = $id;
1152
1153         $this->elementStart('li', $lattrs);
1154         $attrs['href'] = $url;
1155         if ($title) {
1156             $attrs['title'] = $title;
1157         }
1158         $this->element('a', $attrs, $text);
1159         $this->elementEnd('li');
1160     }
1161
1162     /**
1163      * Generate pagination links
1164      *
1165      * @param boolean $have_before is there something before?
1166      * @param boolean $have_after  is there something after?
1167      * @param integer $page        current page
1168      * @param string  $action      current action
1169      * @param array   $args        rest of query arguments
1170      *
1171      * @return nothing
1172      */
1173     // XXX: The messages in this pagination method only tailor to navigating
1174     //      notices. In other lists, "Previous"/"Next" type navigation is
1175     //      desirable, but not available.
1176     function pagination($have_before, $have_after, $page, $action, $args=null)
1177     {
1178         // Does a little before-after block for next/prev page
1179         if ($have_before || $have_after) {
1180             $this->elementStart('dl', 'pagination');
1181             // TRANS: DT element for pagination (previous/next, etc.).
1182             $this->element('dt', null, _('Pagination'));
1183             $this->elementStart('dd', null);
1184             $this->elementStart('ul', array('class' => 'nav'));
1185         }
1186         if ($have_before) {
1187             $pargs   = array('page' => $page-1);
1188             $this->elementStart('li', array('class' => 'nav_prev'));
1189             $this->element('a', array('href' => common_local_url($action, $args, $pargs),
1190                                       'rel' => 'prev'),
1191                            // TRANS: Pagination message to go to a page displaying information more in the
1192                            // TRANS: present than the currently displayed information.
1193                            _('After'));
1194             $this->elementEnd('li');
1195         }
1196         if ($have_after) {
1197             $pargs   = array('page' => $page+1);
1198             $this->elementStart('li', array('class' => 'nav_next'));
1199             $this->element('a', array('href' => common_local_url($action, $args, $pargs),
1200                                       'rel' => 'next'),
1201                            // TRANS: Pagination message to go to a page displaying information more in the
1202                            // TRANS: past than the currently displayed information.
1203                            _('Before'));
1204             $this->elementEnd('li');
1205         }
1206         if ($have_before || $have_after) {
1207             $this->elementEnd('ul');
1208             $this->elementEnd('dd');
1209             $this->elementEnd('dl');
1210         }
1211     }
1212
1213     /**
1214      * An array of feeds for this action.
1215      *
1216      * Returns an array of potential feeds for this action.
1217      *
1218      * @return array Feed object to show in head and links
1219      */
1220
1221     function getFeeds()
1222     {
1223         return null;
1224     }
1225
1226     /**
1227      * A design for this action
1228      *
1229      * @return Design a design object to use
1230      */
1231
1232     function getDesign()
1233     {
1234         return Design::siteDesign();
1235     }
1236
1237     /**
1238      * Check the session token.
1239      *
1240      * Checks that the current form has the correct session token,
1241      * and throw an exception if it does not.
1242      *
1243      * @return void
1244      */
1245
1246     // XXX: Finding this type of check with the same message about 50 times.
1247     //      Possible to refactor?
1248     function checkSessionToken()
1249     {
1250         // CSRF protection
1251         $token = $this->trimmed('token');
1252         if (empty($token) || $token != common_session_token()) {
1253             $this->clientError(_('There was a problem with your session token.'));
1254         }
1255     }
1256 }