]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/action.php
Code to handle PEAR_Errors raised by DB_DataObject that are bubbling
[quix0rs-gnu-social.git] / lib / action.php
1 <?php
2 /**
3  * Laconica, 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   Laconica
24  * @author    Evan Prodromou <evan@controlyourself.ca>
25  * @author    Sarven Capadisli <csarven@controlyourself.ca>
26  * @copyright 2008 Control Yourself, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28  * @link      http://laconi.ca/
29  */
30
31 if (!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  Laconica
49  * @author   Evan Prodromou <evan@controlyourself.ca>
50  * @author   Sarven Capadisli <csarven@controlyourself.ca>
51  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
52  * @link     http://laconi.ca/
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=true)
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 is for checking PEAR_Errors raised by DB_DataObject.
86         // Setting this to PEAR_ERROR_CALLBACK because setting
87         // to PEAR_ERROR_EXCEPTION does't work to allow PEAR_Errors
88         // to be handled as PHP5 exceptions, and PEAR_ERROR_RETURN
89         // does not cause DB_DataObject to actually return PEAR_Errors
90         // that can be checked with PEAR::isError() -- instead
91         // they just disappear into the ether, and can only be checked for
92         // after the fact. -- Zach    
93         PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 
94                array($this, "checkDB_DataObjectError"));
95         
96         $this->args =& common_copy_args($argarray);
97         return true;
98     }
99
100     /**
101      * Show page, a template method.
102      *
103      * @return nothing
104      */
105     function showPage()
106     {
107         $this->startHTML();
108         $this->showHead();
109         $this->showBody();
110         $this->endHTML();
111     }
112
113     /**
114      * Show head, a template method.
115      *
116      * @return nothing
117      */
118     function showHead()
119     {
120         // XXX: attributes (profile?)
121         $this->elementStart('head');
122         $this->showTitle();
123         $this->showStylesheets();
124         $this->showScripts();
125         $this->showOpenSearch();
126         $this->showFeeds();
127         $this->showDescription();
128         $this->extraHead();
129         $this->elementEnd('head');
130     }
131
132     /**
133      * Show title, a template method.
134      *
135      * @return nothing
136      */
137     function showTitle()
138     {
139         $this->element('title', null,
140                        sprintf(_("%s - %s"),
141                                $this->title(),
142                                common_config('site', 'name')));
143     }
144
145     /**
146      * Returns the page title
147      *
148      * SHOULD overload
149      *
150      * @return string page title
151      */
152
153     function title()
154     {
155         return _("Untitled page");
156     }
157
158     /**
159      * Show stylesheets
160      *
161      * @return nothing
162      */
163     function showStylesheets()
164     {
165         $this->element('link', array('rel' => 'stylesheet',
166                                      'type' => 'text/css',
167                                      'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION,
168                                      'media' => 'screen, projection, tv'));
169         $this->element('link', array('rel' => 'stylesheet',
170                                      'type' => 'text/css',
171                                      'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
172                                      'media' => 'screen, projection, tv'));
173         $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
174                        'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
175         foreach (array(6,7) as $ver) {
176             if (file_exists(theme_file('css/ie'.$ver.'.css', 'base'))) {
177                 // Yes, IE people should be put in jail.
178                 $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
179                                'href="'.theme_path('css/ie'.$ver.'.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
180             }
181         }
182         $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
183                        'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]');
184     }
185
186     /**
187      * Show javascript headers
188      *
189      * @return nothing
190      */
191     function showScripts()
192     {
193         if (Event::handle('StartShowScripts', array($this))) {
194             if (Event::handle('StartShowJQueryScripts', array($this))) {
195                 $this->element('script', array('type' => 'text/javascript',
196                                                'src' => common_path('js/jquery.min.js')),
197                                ' ');
198                 $this->element('script', array('type' => 'text/javascript',
199                                                'src' => common_path('js/jquery.form.js')),
200                                ' ');
201                 Event::handle('EndShowJQueryScripts', array($this));
202             }
203             if (Event::handle('StartShowLaconicaScripts', array($this))) {
204                 $this->element('script', array('type' => 'text/javascript',
205                                                'src' => common_path('js/xbImportNode.js')),
206                                ' ');
207                 $this->element('script', array('type' => 'text/javascript',
208                                                'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
209                                ' ');
210                 Event::handle('EndShowLaconicaScripts', array($this));
211             }
212             Event::handle('EndShowScripts', array($this));
213         }
214     }
215
216     /**
217      * Show OpenSearch headers
218      *
219      * @return nothing
220      */
221     function showOpenSearch()
222     {
223         $this->element('link', array('rel' => 'search',
224                                      'type' => 'application/opensearchdescription+xml',
225                                      'href' =>  common_local_url('opensearch', array('type' => 'people')),
226                                      'title' => common_config('site', 'name').' People Search'));
227         $this->element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
228                                      'href' =>  common_local_url('opensearch', array('type' => 'notice')),
229                                      'title' => common_config('site', 'name').' Notice Search'));
230     }
231
232     /**
233      * Show feed headers
234      *
235      * MAY overload
236      *
237      * @return nothing
238      */
239
240     function showFeeds()
241     {
242         $feeds = $this->getFeeds();
243
244         if ($feeds) {
245             foreach ($feeds as $feed) {
246                 $this->element('link', array('rel' => $feed->rel(),
247                                              'href' => $feed->url,
248                                              'type' => $feed->mimeType(),
249                                              'title' => $feed->title));
250             }
251         }
252     }
253
254     /**
255      * Show description.
256      *
257      * SHOULD overload
258      *
259      * @return nothing
260      */
261     function showDescription()
262     {
263         // does nothing by default
264     }
265
266     /**
267      * Show extra stuff in <head>.
268      *
269      * MAY overload
270      *
271      * @return nothing
272      */
273     function extraHead()
274     {
275         // does nothing by default
276     }
277
278     /**
279      * Show body.
280      *
281      * Calls template methods
282      *
283      * @return nothing
284      */
285     function showBody()
286     {
287         $this->elementStart('body', array('id' => $this->trimmed('action')));
288         $this->elementStart('div', array('id' => 'wrap'));
289         $this->showHeader();
290         $this->showCore();
291         $this->showFooter();
292         $this->elementEnd('div');
293         $this->elementEnd('body');
294     }
295
296     /**
297      * Show header of the page.
298      *
299      * Calls template methods
300      *
301      * @return nothing
302      */
303     function showHeader()
304     {
305         $this->elementStart('div', array('id' => 'header'));
306         $this->showLogo();
307         $this->showPrimaryNav();
308         $this->showSiteNotice();
309         if (common_logged_in()) {
310             $this->showNoticeForm();
311         } else {
312             $this->showAnonymousMessage();
313         }
314         $this->elementEnd('div');
315     }
316
317     /**
318      * Show configured logo.
319      *
320      * @return nothing
321      */
322     function showLogo()
323     {
324         $this->elementStart('address', array('id' => 'site_contact',
325                                              'class' => 'vcard'));
326         $this->elementStart('a', array('class' => 'url home bookmark',
327                                        'href' => common_local_url('public')));
328         if (common_config('site', 'logo') || file_exists(theme_file('logo.png'))) {
329             $this->element('img', array('class' => 'logo photo',
330                                         'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : theme_path('logo.png'),
331                                         'alt' => common_config('site', 'name')));
332         }
333         $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
334         $this->elementEnd('a');
335         $this->elementEnd('address');
336     }
337
338     /**
339      * Show primary navigation.
340      *
341      * @return nothing
342      */
343     function showPrimaryNav()
344     {
345         $user = common_current_user();
346
347         $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
348         $this->element('dt', null, _('Primary site navigation'));
349         $this->elementStart('dd');
350         $this->elementStart('ul', array('class' => 'nav'));
351         if (Event::handle('StartPrimaryNav', array($this))) {
352             if ($user) {
353                 $this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
354                                 _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
355             }
356             $this->menuItem(common_local_url('peoplesearch'),
357                             _('Search'), _('Search for people or text'), false, 'nav_search');
358             if ($user) {
359                 $this->menuItem(common_local_url('profilesettings'),
360                                 _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
361
362                 if (common_config('xmpp', 'enabled')) {
363                     $this->menuItem(common_local_url('imsettings'),
364                                     _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
365                 } else {
366                     $this->menuItem(common_local_url('smssettings'),
367                                     _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
368                 }
369                 $this->menuItem(common_local_url('logout'),
370                                 _('Logout'), _('Logout from the site'), false, 'nav_logout');
371             } else {
372                 $this->menuItem(common_local_url('login'),
373                                 _('Login'), _('Login to the site'), false, 'nav_login');
374                 if (!common_config('site', 'closed')) {
375                     $this->menuItem(common_local_url('register'),
376                                     _('Register'), _('Create an account'), false, 'nav_register');
377                 }
378                 $this->menuItem(common_local_url('openidlogin'),
379                                 _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
380             }
381             $this->menuItem(common_local_url('doc', array('title' => 'help')),
382                             _('Help'), _('Help me!'), false, 'nav_help');
383             Event::handle('EndPrimaryNav', array($this));
384         }
385         $this->elementEnd('ul');
386         $this->elementEnd('dd');
387         $this->elementEnd('dl');
388     }
389
390     /**
391      * Show site notice.
392      *
393      * @return nothing
394      */
395     function showSiteNotice()
396     {
397         // Revist. Should probably do an hAtom pattern here
398         $text = common_config('site', 'notice');
399         if ($text) {
400             $this->elementStart('dl', array('id' => 'site_notice',
401                                             'class' => 'system_notice'));
402             $this->element('dt', null, _('Site notice'));
403             $this->elementStart('dd', null);
404             $this->raw($text);
405             $this->elementEnd('dd');
406             $this->elementEnd('dl');
407         }
408     }
409
410     /**
411      * Show notice form.
412      *
413      * MAY overload if no notice form needed... or direct message box????
414      *
415      * @return nothing
416      */
417     function showNoticeForm()
418     {
419         $notice_form = new NoticeForm($this);
420         $notice_form->show();
421     }
422
423     /**
424      * Show anonymous message.
425      *
426      * SHOULD overload
427      *
428      * @return nothing
429      */
430     function showAnonymousMessage()
431     {
432         // needs to be defined by the class
433     }
434
435     /**
436      * Show core.
437      *
438      * Shows local navigation, content block and aside.
439      *
440      * @return nothing
441      */
442     function showCore()
443     {
444         $this->elementStart('div', array('id' => 'core'));
445         $this->showLocalNavBlock();
446         $this->showContentBlock();
447         $this->showAside();
448         $this->elementEnd('div');
449     }
450
451     /**
452      * Show local navigation block.
453      *
454      * @return nothing
455      */
456     function showLocalNavBlock()
457     {
458         $this->elementStart('dl', array('id' => 'site_nav_local_views'));
459         $this->element('dt', null, _('Local views'));
460         $this->elementStart('dd');
461         $this->showLocalNav();
462         $this->elementEnd('dd');
463         $this->elementEnd('dl');
464     }
465
466     /**
467      * Show local navigation.
468      *
469      * SHOULD overload
470      *
471      * @return nothing
472      */
473     function showLocalNav()
474     {
475         // does nothing by default
476     }
477
478     /**
479      * Show content block.
480      *
481      * @return nothing
482      */
483     function showContentBlock()
484     {
485         $this->elementStart('div', array('id' => 'content'));
486         $this->showPageTitle();
487         $this->showPageNoticeBlock();
488         $this->elementStart('div', array('id' => 'content_inner'));
489         // show the actual content (forms, lists, whatever)
490         $this->showContent();
491         $this->elementEnd('div');
492         $this->elementEnd('div');
493     }
494
495     /**
496      * Show page title.
497      *
498      * @return nothing
499      */
500     function showPageTitle()
501     {
502         $this->element('h1', null, $this->title());
503     }
504
505     /**
506      * Show page notice block.
507      *
508      * @return nothing
509      */
510     function showPageNoticeBlock()
511     {
512         $this->elementStart('dl', array('id' => 'page_notice',
513                                         'class' => 'system_notice'));
514         $this->element('dt', null, _('Page notice'));
515         $this->elementStart('dd');
516         $this->showPageNotice();
517         $this->elementEnd('dd');
518         $this->elementEnd('dl');
519     }
520
521     /**
522      * Show page notice.
523      *
524      * SHOULD overload (unless there's not a notice)
525      *
526      * @return nothing
527      */
528     function showPageNotice()
529     {
530     }
531
532     /**
533      * Show content.
534      *
535      * MUST overload (unless there's not a notice)
536      *
537      * @return nothing
538      */
539     function showContent()
540     {
541     }
542
543     /**
544      * Show Aside.
545      *
546      * @return nothing
547      */
548
549     function showAside()
550     {
551         $this->elementStart('div', array('id' => 'aside_primary',
552                                          'class' => 'aside'));
553         $this->showExportData();
554         if (Event::handle('StartShowSections', array($this))) {
555             $this->showSections();
556             Event::handle('EndShowSections', array($this));
557         }
558         $this->elementEnd('div');
559     }
560
561     /**
562      * Show export data feeds.
563      *
564      * @return void
565      */
566
567     function showExportData()
568     {
569         $feeds = $this->getFeeds();
570         if ($feeds) {
571             $fl = new FeedList($this);
572             $fl->show($feeds);
573         }
574     }
575
576     /**
577      * Show sections.
578      *
579      * SHOULD overload
580      *
581      * @return nothing
582      */
583     function showSections()
584     {
585         // for each section, show it
586     }
587
588     /**
589      * Show footer.
590      *
591      * @return nothing
592      */
593     function showFooter()
594     {
595         $this->elementStart('div', array('id' => 'footer'));
596         $this->showSecondaryNav();
597         $this->showLicenses();
598         $this->elementEnd('div');
599     }
600
601     /**
602      * Show secondary navigation.
603      *
604      * @return nothing
605      */
606     function showSecondaryNav()
607     {
608         $this->elementStart('dl', array('id' => 'site_nav_global_secondary'));
609         $this->element('dt', null, _('Secondary site navigation'));
610         $this->elementStart('dd', null);
611         $this->elementStart('ul', array('class' => 'nav'));
612         if (Event::handle('StartSecondaryNav', array($this))) {
613             $this->menuItem(common_local_url('doc', array('title' => 'help')),
614                             _('Help'));
615             $this->menuItem(common_local_url('doc', array('title' => 'about')),
616                             _('About'));
617             $this->menuItem(common_local_url('doc', array('title' => 'faq')),
618                             _('FAQ'));
619             $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
620                             _('Privacy'));
621             $this->menuItem(common_local_url('doc', array('title' => 'source')),
622                             _('Source'));
623             $this->menuItem(common_local_url('doc', array('title' => 'contact')),
624                             _('Contact'));
625             Event::handle('EndSecondaryNav', array($this));
626         }
627         $this->elementEnd('ul');
628         $this->elementEnd('dd');
629         $this->elementEnd('dl');
630     }
631
632     /**
633      * Show licenses.
634      *
635      * @return nothing
636      */
637     function showLicenses()
638     {
639         $this->elementStart('dl', array('id' => 'licenses'));
640         $this->showLaconicaLicense();
641         $this->showContentLicense();
642         $this->elementEnd('dl');
643     }
644
645     /**
646      * Show Laconica license.
647      *
648      * @return nothing
649      */
650     function showLaconicaLicense()
651     {
652         $this->element('dt', array('id' => 'site_laconica_license'), _('Laconica software license'));
653         $this->elementStart('dd', null);
654         if (common_config('site', 'broughtby')) {
655             $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). ');
656         } else {
657             $instr = _('**%%site.name%%** is a microblogging service. ');
658         }
659         $instr .= sprintf(_('It runs the [Laconica](http://laconi.ca/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), LACONICA_VERSION);
660         $output = common_markup_to_html($instr);
661         $this->raw($output);
662         $this->elementEnd('dd');
663         // do it
664     }
665
666     /**
667      * Show content license.
668      *
669      * @return nothing
670      */
671     function showContentLicense()
672     {
673         $this->element('dt', array('id' => 'site_content_license'), _('Laconica software license'));
674         $this->elementStart('dd', array('id' => 'site_content_license_cc'));
675         $this->elementStart('p');
676         $this->element('img', array('id' => 'license_cc',
677                                     'src' => common_config('license', 'image'),
678                                     'alt' => common_config('license', 'title')));
679         //TODO: This is dirty: i18n
680         $this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
681         $this->element('a', array('class' => 'license',
682                                   'rel' => 'external license',
683                                   'href' => common_config('license', 'url')),
684                        common_config('license', 'title'));
685         $this->text(_('license.'));
686         $this->elementEnd('p');
687         $this->elementEnd('dd');
688     }
689
690     /**
691      * Return last modified, if applicable.
692      *
693      * MAY override
694      *
695      * @return string last modified http header
696      */
697     function lastModified()
698     {
699         // For comparison with If-Last-Modified
700         // If not applicable, return null
701         return null;
702     }
703
704     /**
705      * Return etag, if applicable.
706      *
707      * MAY override
708      *
709      * @return string etag http header
710      */
711     function etag()
712     {
713         return null;
714     }
715
716     /**
717      * Return true if read only.
718      *
719      * MAY override
720      *
721      * @return boolean is read only action?
722      */
723     function isReadOnly()
724     {
725         return false;
726     }
727
728     /**
729      * Returns query argument or default value if not found
730      *
731      * @param string $key requested argument
732      * @param string $def default value to return if $key is not provided
733      *
734      * @return boolean is read only action?
735      */
736     function arg($key, $def=null)
737     {
738         if (array_key_exists($key, $this->args)) {
739             return $this->args[$key];
740         } else {
741             return $def;
742         }
743     }
744
745     /**
746      * Returns trimmed query argument or default value if not found
747      *
748      * @param string $key requested argument
749      * @param string $def default value to return if $key is not provided
750      *
751      * @return boolean is read only action?
752      */
753     function trimmed($key, $def=null)
754     {
755         $arg = $this->arg($key, $def);
756         return is_string($arg) ? trim($arg) : $arg;
757     }
758
759     /**
760      * Handler method
761      *
762      * @param array $argarray is ignored since it's now passed in in prepare()
763      *
764      * @return boolean is read only action?
765      */
766     function handle($argarray=null)
767     {
768         $lm   = $this->lastModified();
769         $etag = $this->etag();
770         if ($etag) {
771             header('ETag: ' . $etag);
772         }
773         if ($lm) {
774             header('Last-Modified: ' . date(DATE_RFC1123, $lm));
775             $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
776             if ($if_modified_since) {
777                 $ims = strtotime($if_modified_since);
778                 if ($lm <= $ims) {
779                     if (!$etag ||
780                         $this->_hasEtag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) {
781                         header('HTTP/1.1 304 Not Modified');
782                         // Better way to do this?
783                         exit(0);
784                     }
785                 }
786             }
787         }
788     }
789
790     /**
791      * HasĀ etag? (private)
792      *
793      * @param string $etag          etag http header
794      * @param string $if_none_match ifNoneMatch http header
795      *
796      * @return boolean
797      */
798     function _hasEtag($etag, $if_none_match)
799     {
800         return ($if_none_match) && in_array($etag, explode(',', $if_none_match));
801     }
802
803     /**
804      * Boolean understands english (yes, no, true, false)
805      *
806      * @param string $key query key we're interested in
807      * @param string $def default value
808      *
809      * @return boolean interprets yes/no strings as boolean
810      */
811     function boolean($key, $def=false)
812     {
813         $arg = strtolower($this->trimmed($key));
814
815         if (is_null($arg)) {
816             return $def;
817         } else if (in_array($arg, array('true', 'yes', '1'))) {
818             return true;
819         } else if (in_array($arg, array('false', 'no', '0'))) {
820             return false;
821         } else {
822             return $def;
823         }
824     }
825
826     /**
827      * Server error
828      *
829      * @param string  $msg  error message to display
830      * @param integer $code http error code, 500 by default
831      *
832      * @return nothing
833      */
834
835     function serverError($msg, $code=500)
836     {
837         $action = $this->trimmed('action');
838         common_debug("Server error '$code' on '$action': $msg", __FILE__);
839         throw new ServerException($msg, $code);
840     }
841
842     /**
843      * Client error
844      *
845      * @param string  $msg  error message to display
846      * @param integer $code http error code, 400 by default
847      *
848      * @return nothing
849      */
850
851     function clientError($msg, $code=400)
852     {
853         $action = $this->trimmed('action');
854         common_debug("User error '$code' on '$action': $msg", __FILE__);
855         throw new ClientException($msg, $code);
856     }
857
858     /**
859      * Check old fashioned PEAR_Error msgs coming from DB_DataObject
860      *
861      * Logs the DB_DataObject error. Override to do something else.
862      * 
863      * @param PEAR_Error 
864      *
865      * @return nothing
866      */
867      
868     function checkDB_DataObjectError($error) {
869         common_log(LOG_ERR, $error->getMessage());
870             // XXX: throw an exception here? --Zach
871     }
872     
873     /**
874      * Returns the current URL
875      *
876      * @return string current URL
877      */
878     function selfUrl()
879     {
880         $action = $this->trimmed('action');
881         $args   = $this->args;
882         unset($args['action']);
883         foreach (array_keys($_COOKIE) as $cookie) {
884             unset($args[$cookie]);
885         }
886         return common_local_url($action, $args);
887     }
888
889     /**
890      * Generate a menu item
891      *
892      * @param string  $url         menu URL
893      * @param string  $text        menu name
894      * @param string  $title       title attribute, null by default
895      * @param boolean $is_selected current menu item, false by default
896      * @param string  $id          element id, null by default
897      *
898      * @return nothing
899      */
900     function menuItem($url, $text, $title=null, $is_selected=false, $id=null)
901     {
902         // Added @id to li for some control.
903         // XXX: We might want to move this to htmloutputter.php
904         $lattrs = array();
905         if ($is_selected) {
906             $lattrs['class'] = 'current';
907         }
908
909         (is_null($id)) ? $lattrs : $lattrs['id'] = $id;
910
911         $this->elementStart('li', $lattrs);
912         $attrs['href'] = $url;
913         if ($title) {
914             $attrs['title'] = $title;
915         }
916         $this->element('a', $attrs, $text);
917         $this->elementEnd('li');
918     }
919
920     /**
921      * Generate pagination links
922      *
923      * @param boolean $have_before is there something before?
924      * @param boolean $have_after  is there something after?
925      * @param integer $page        current page
926      * @param string  $action      current action
927      * @param array   $args        rest of query arguments
928      *
929      * @return nothing
930      */
931     function pagination($have_before, $have_after, $page, $action, $args=null)
932     {
933         // Does a little before-after block for next/prev page
934         if ($have_before || $have_after) {
935             $this->elementStart('div', array('class' => 'pagination'));
936             $this->elementStart('dl', null);
937             $this->element('dt', null, _('Pagination'));
938             $this->elementStart('dd', null);
939             $this->elementStart('ul', array('class' => 'nav'));
940         }
941         if ($have_before) {
942             $pargs   = array('page' => $page-1);
943             $newargs = $args ? array_merge($args, $pargs) : $pargs;
944             $this->elementStart('li', array('class' => 'nav_prev'));
945             $this->element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'prev'),
946                            _('After'));
947             $this->elementEnd('li');
948         }
949         if ($have_after) {
950             $pargs   = array('page' => $page+1);
951             $newargs = $args ? array_merge($args, $pargs) : $pargs;
952             $this->elementStart('li', array('class' => 'nav_next'));
953             $this->element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'next'),
954                            _('Before'));
955             $this->elementEnd('li');
956         }
957         if ($have_before || $have_after) {
958             $this->elementEnd('ul');
959             $this->elementEnd('dd');
960             $this->elementEnd('dl');
961             $this->elementEnd('div');
962         }
963     }
964
965     /**
966      * An array of feeds for this action.
967      *
968      * Returns an array of potential feeds for this action.
969      *
970      * @return array Feed object to show in head and links
971      */
972
973     function getFeeds()
974     {
975         return null;
976     }
977 }