]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/action.php
Don't trust local HTML either
[quix0rs-gnu-social.git] / lib / action.php
index 64661a46c1eeebd336ff49c56a99be5e709b19b7..c2d60e11e974bec1414fe2b9b143b4babdb4ce0d 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/noticeform.php';
-require_once INSTALLDIR.'/lib/htmloutputter.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * Base class for all actions
@@ -63,13 +58,16 @@ class Action extends HTMLOutputter // lawsuit
     protected $ajax   = false;
     protected $menus  = true;
     protected $needLogin = false;
+    protected $needPost = false;    // implies canPost if true
+    protected $canPost = false;     // can this action handle POST method?
 
-    // The currently scoped profile
+    // The currently scoped profile (normally Profile::current; from $this->auth_user for API)
     protected $scoped = null;
 
-    // Messages to the front-end user
-    protected $error = null;
-    protected $msg   = null;
+    // Related to front-end user representation
+    protected $format = null;
+    protected $error  = null;
+    protected $msg    = null;
 
     /**
      * Constructor
@@ -120,8 +118,16 @@ class Action extends HTMLOutputter // lawsuit
             common_config_set('db', 'database', $mirror);
         }
 
-        if ($this->prepare($args)) {
-            $this->handle($args);
+        if (Event::handle('StartActionExecute', array($this, &$args))) {
+            $prepared = $this->prepare($args);
+            if ($prepared) {
+                $this->handle($args);
+            } else {
+                common_debug('Prepare failed for Action.');
+            }
+
+            $this->flush();
+            Event::handle('EndActionExecute', array($this));
         }
     }
 
@@ -134,31 +140,77 @@ class Action extends HTMLOutputter // lawsuit
      */
     protected function prepare(array $args=array())
     {
+        if ($this->needPost && !$this->isPost()) {
+            // TRANS: Client error. POST is a HTTP command. It should not be translated.
+            $this->clientError(_('This method requires a POST.'), 405);
+        }
+
+        // needPost, of course, overrides canPost if true
+        if (!$this->canPost) {
+            $this->canPost = $this->needPost;
+        }
+
         $this->args = common_copy_args($args);
 
-        $this->action = $this->trimmed('action');
+        // This could be set with get_called_action and then
+        // chop off 'Action' from the class name. In lower case.
+        $this->action = strtolower($this->trimmed('action'));
 
         if ($this->ajax || $this->boolean('ajax')) {
-            // check with StatusNet::isAjax()
-            StatusNet::setAjax(true);
+            // check with GNUsocial::isAjax()
+            GNUsocial::setAjax(true);
         }
 
         if ($this->needLogin) {
             $this->checkLogin(); // if not logged in, this redirs/excepts
         }
 
-        $this->scoped = Profile::current();
+        $this->updateScopedProfile();
 
         return true;
     }
 
+    public function updateScopedProfile()
+    {
+        $this->scoped = Profile::current();
+        return $this->scoped;
+    }
+
+    public function getScoped()
+    {
+        return ($this->scoped instanceof Profile) ? $this->scoped : null;
+    }
+
+    // Must be run _after_ prepare
+    public function getActionName()
+    {
+        return $this->action;
+    }
+
+    public function isAction(array $names)
+    {
+        foreach ($names as $class) {
+            // PHP is case insensitive, and we have stuff like ApiUpperCaseAction,
+            // but we at least make a point out of wanting to do stuff case-sensitive.
+            $class = ucfirst($class) . 'Action';
+            if ($this instanceof $class) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Show page, a template method.
      *
      * @return nothing
      */
-    function showPage()
+    public function showPage()
     {
+        if (GNUsocial::isAjax()) {
+            self::showAjax();
+            return;
+        }
         if (Event::handle('StartShowHTML', array($this))) {
             $this->startHTML();
             $this->flush();
@@ -179,6 +231,23 @@ class Action extends HTMLOutputter // lawsuit
         }
     }
 
+    public function showAjax()
+    {
+        $this->startHTML('text/xml;charset=utf-8');
+        $this->elementStart('head');
+        // TRANS: Title for conversation page.
+        $this->element('title', null, $this->title());
+        $this->elementEnd('head');
+        $this->elementStart('body');
+        if ($this->getError()) {
+            $this->element('p', array('id'=>'error'), $this->getError());
+        } else {
+            $this->showContent();
+        }
+        $this->elementEnd('body');
+        $this->endHTML();
+    }
+
     function endHTML()
     {
         global $_startTime;
@@ -258,7 +327,7 @@ class Action extends HTMLOutputter // lawsuit
         } else {
             // favicon.ico should be HTTPS if the rest of the page is
             $this->element('link', array('rel' => 'shortcut icon',
-                                         'href' => common_path('favicon.ico', StatusNet::isHTTPS())));
+                                         'href' => common_path('favicon.ico', GNUsocial::isHTTPS())));
         }
 
         if (common_config('site', 'mobile')) {
@@ -283,29 +352,14 @@ class Action extends HTMLOutputter // lawsuit
 
             // Use old name for StatusNet for compatibility on events
 
-            if (Event::handle('StartShowStatusNetStyles', array($this)) &&
-                Event::handle('StartShowLaconicaStyles', array($this))) {
+            if (Event::handle('StartShowStylesheets', array($this))) {
                 $this->primaryCssLink(null, 'screen, projection, tv, print');
-                Event::handle('EndShowStatusNetStyles', array($this));
-                Event::handle('EndShowLaconicaStyles', array($this));
+                Event::handle('EndShowStylesheets', array($this));
             }
 
-            $this->cssLink(common_path('js/css/smoothness/jquery-ui.css', StatusNet::isHTTPS()));
+            $this->cssLink('js/extlib/jquery-ui/css/smoothness/jquery-ui.css');
 
             if (Event::handle('StartShowUAStyles', array($this))) {
-                $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
-                               'href="'.Theme::path('css/ie.css', 'base').'?version='.STATUSNET_VERSION.'" /><![endif]');
-                foreach (array(6,7) as $ver) {
-                    if (file_exists(Theme::file('css/ie'.$ver.'.css', 'base'))) {
-                        // Yes, IE people should be put in jail.
-                        $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
-                                       'href="'.Theme::path('css/ie'.$ver.'.css', 'base').'?version='.STATUSNET_VERSION.'" /><![endif]');
-                    }
-                }
-                if (file_exists(Theme::file('css/ie.css'))) {
-                    $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
-                               'href="'.Theme::path('css/ie.css', null).'?version='.STATUSNET_VERSION.'" /><![endif]');
-                }
                 Event::handle('EndShowUAStyles', array($this));
             }
 
@@ -358,40 +412,25 @@ class Action extends HTMLOutputter // lawsuit
     {
         if (Event::handle('StartShowScripts', array($this))) {
             if (Event::handle('StartShowJQueryScripts', array($this))) {
-                if (common_config('site', 'minify')) {
-                    $this->script('jquery.min.js');
-                    $this->script('jquery.form.min.js');
-                    $this->script('jquery-ui.min.js');
-                    $this->script('jquery.cookie.min.js');
-                    $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.min.js', StatusNet::isHTTPS()).'"); }');
-                    $this->script('jquery.joverlay.min.js');
-                    $this->script('jquery.infieldlabel.min.js');
-                } else {
-                    $this->script('jquery.js');
-                    $this->script('jquery.form.js');
-                    $this->script('jquery-ui.min.js');
-                    $this->script('jquery.cookie.js');
-                    $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.js', StatusNet::isHTTPS()).'"); }');
-                    $this->script('jquery.joverlay.js');
-                    $this->script('jquery.infieldlabel.js');
-                }
+                $this->script('extlib/jquery.js');
+                $this->script('extlib/jquery.form.js');
+                $this->script('extlib/jquery-ui/jquery-ui.js');
+                $this->script('extlib/jquery.cookie.js');
+                $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.js', GNUsocial::isHTTPS()).'"); }');
 
                 Event::handle('EndShowJQueryScripts', array($this));
             }
-            if (Event::handle('StartShowStatusNetScripts', array($this)) &&
-                Event::handle('StartShowLaconicaScripts', array($this))) {
-                if (common_config('site', 'minify')) {
-                    $this->script('util.min.js');
-                } else {
-                    $this->script('util.js');
-                    $this->script('xbImportNode.js');
-                    $this->script('geometa.js');
-                }
+            if (Event::handle('StartShowStatusNetScripts', array($this))) {
+                $this->script('util.js');
+                $this->script('xbImportNode.js');
+                $this->script('geometa.js');
+
                 // This route isn't available in single-user mode.
                 // Not sure why, but it causes errors here.
                 $this->inlineScript('var _peopletagAC = "' .
                                     common_local_url('peopletagautocomplete') . '";');
                 $this->showScriptMessages();
+                $this->showScriptVariables();
                 // Anti-framing code to avoid clickjacking attacks in older browsers.
                 // This will show a blank page if the page is being framed, which is
                 // consistent with the behavior of the 'X-Frame-Options: SAMEORIGIN'
@@ -400,7 +439,6 @@ class Action extends HTMLOutputter // lawsuit
                     $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 = ""; }; }');
                 }
                 Event::handle('EndShowStatusNetScripts', array($this));
-                Event::handle('EndShowLaconicaScripts', array($this));
             }
             Event::handle('EndShowScripts', array($this));
         }
@@ -422,16 +460,11 @@ class Action extends HTMLOutputter // lawsuit
 
             // TRANS: Localized tooltip for '...' expansion button on overlong remote messages.
             $messages['showmore_tooltip'] = _m('TOOLTIP', 'Show more');
-
-            // TRANS: Inline reply form submit button: submits a reply comment.
-            $messages['reply_submit'] = _m('BUTTON', 'Reply');
-
-            // TRANS: Placeholder text for inline reply form. Clicking in this box will turn it into a mini notice form.
-            $messages['reply_placeholder'] = _m('Write a reply...');
+            $messages['popup_close_button'] = _m('TOOLTIP', 'Close popup');
 
             $messages = array_merge($messages, $this->getScriptMessages());
 
-           Event::handle('EndScriptMessages', array($this, &$messages));
+            Event::handle('EndScriptMessages', array($this, &$messages));
         }
 
         if (!empty($messages)) {
@@ -441,6 +474,19 @@ class Action extends HTMLOutputter // lawsuit
         return $messages;
     }
 
+    protected function showScriptVariables()
+    {
+        $vars = array();
+
+        if (Event::handle('StartScriptVariables', array($this, &$vars))) {
+            $vars['urlNewNotice'] = common_local_url('newnotice');
+        }
+        if (!empty($vars)) {
+            $this->inlineScript('SN.V = ' . json_encode($vars));
+        }
+        return $vars;
+    }
+
     /**
      * If the action will need localizable text strings, export them here like so:
      *
@@ -489,15 +535,11 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showFeeds()
     {
-        $feeds = $this->getFeeds();
-
-        if ($feeds) {
-            foreach ($feeds as $feed) {
-                $this->element('link', array('rel' => $feed->rel(),
-                                             'href' => $feed->url,
-                                             'type' => $feed->mimeType(),
-                                             'title' => $feed->title));
-            }
+        foreach ($this->getFeeds() as $feed) {
+            $this->element('link', array('rel' => $feed->rel(),
+                                         'href' => $feed->url,
+                                         'type' => $feed->mimeType(),
+                                         'title' => $feed->title));
         }
     }
 
@@ -534,9 +576,11 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showBody()
     {
-        $this->elementStart('body', (common_current_user()) ? array('id' => strtolower($this->trimmed('action')),
-                                                                    'class' => 'user_in')
-                            : array('id' => strtolower($this->trimmed('action'))));
+        $params = array('id' => $this->getActionName());
+        if ($this->scoped instanceof Profile) {
+            $params['class'] = 'user_in';
+        }
+        $this->elementStart('body', $params);
         $this->elementStart('div', array('id' => 'wrap'));
         if (Event::handle('StartShowHeader', array($this))) {
             $this->showHeader();
@@ -583,8 +627,7 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showLogo()
     {
-        $this->elementStart('address', array('id' => 'site_contact',
-                                             'class' => 'vcard'));
+        $this->elementStart('address', array('id' => 'site_contact', 'class' => 'h-card'));
         if (Event::handle('StartAddressData', array($this))) {
             if (common_config('singleuser', 'enabled')) {
                 $user = User::singleUser();
@@ -597,10 +640,10 @@ class Action extends HTMLOutputter // lawsuit
                 $url = common_local_url('public');
             }
 
-            $this->elementStart('a', array('class' => 'url home bookmark',
+            $this->elementStart('a', array('class' => 'home bookmark',
                                            'href' => $url));
 
-            if (StatusNet::isHTTPS()) {
+            if (GNUsocial::isHTTPS()) {
                 $logoUrl = common_config('site', 'ssllogo');
                 if (empty($logoUrl)) {
                     // if logo is an uploaded file, try to fall back to HTTPS file URL
@@ -623,13 +666,11 @@ class Action extends HTMLOutputter // lawsuit
             }
 
             if (!empty($logoUrl)) {
-                $this->element('img', array('class' => 'logo photo',
+                $this->element('img', array('class' => 'logo u-photo p-name',
                                             'src' => $logoUrl,
                                             'alt' => common_config('site', 'name')));
             }
 
-            $this->text(' ');
-            $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
             $this->elementEnd('a');
 
             Event::handle('EndAddressData', array($this));
@@ -685,44 +726,42 @@ class Action extends HTMLOutputter // lawsuit
     function showNoticeForm()
     {
         // TRANS: Tab on the notice form.
-        $tabs = array('status' => _m('TAB','Status'));
+        $tabs = array('status' => array('title' => _m('TAB','Status'),
+                                        'href'  => common_local_url('newnotice')));
 
         $this->elementStart('div', 'input_forms');
 
+        $this->element('label', array('for'=>'input_form_nav'), _m('TAB', 'Share your:'));
+
         if (Event::handle('StartShowEntryForms', array(&$tabs))) {
             $this->elementStart('ul', array('class' => 'nav',
                                             'id' => 'input_form_nav'));
 
-            foreach ($tabs as $tag => $title) {
+            foreach ($tabs as $tag => $data) {
+                $tag = htmlspecialchars($tag);
                 $attrs = array('id' => 'input_form_nav_'.$tag,
                                'class' => 'input_form_nav_tab');
 
                 if ($tag == 'status') {
-                    // We're actually showing the placeholder form,
-                    // but we special-case the 'Status' tab as if
-                    // it were a small version of it.
                     $attrs['class'] .= ' current';
                 }
                 $this->elementStart('li', $attrs);
 
                 $this->element('a',
-                               array('href' => 'javascript:SN.U.switchInputFormTab("'.$tag.'")'),
-                               $title);
+                               array('onclick' => 'return SN.U.switchInputFormTab("'.$tag.'");',
+                                     'href' => $data['href']),
+                               $data['title']);
                 $this->elementEnd('li');
             }
 
             $this->elementEnd('ul');
 
-            $attrs = array('class' => 'input_form current',
-                           'id' => 'input_form_placeholder');
-            $this->elementStart('div', $attrs);
-            $form = new NoticePlaceholderForm($this);
-            $form->show();
-            $this->elementEnd('div');
-
-            foreach ($tabs as $tag => $title) {
+            foreach ($tabs as $tag => $data) {
                 $attrs = array('class' => 'input_form',
                                'id' => 'input_form_'.$tag);
+                if ($tag == 'status') {
+                    $attrs['class'] .= ' current';
+                }
 
                 $this->elementStart('div', $attrs);
 
@@ -956,7 +995,7 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
-    function showContent()
+    protected function showContent()
     {
     }
 
@@ -993,9 +1032,9 @@ class Action extends HTMLOutputter // lawsuit
     function showExportData()
     {
         $feeds = $this->getFeeds();
-        if ($feeds) {
-            $fl = new FeedList($this);
-            $fl->show($feeds);
+        if (!empty($feeds)) {
+            $fl = new FeedList($this, $feeds);
+            $fl->show();
         }
     }
 
@@ -1045,33 +1084,33 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showLicenses()
     {
-        $this->showStatusNetLicense();
+        $this->showGNUsocialLicense();
         $this->showContentLicense();
     }
 
     /**
-     * Show StatusNet license.
+     * Show GNU social license.
      *
      * @return nothing
      */
-    function showStatusNetLicense()
+    function showGNUsocialLicense()
     {
         if (common_config('site', 'broughtby')) {
-            // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is set.
+            // TRANS: First sentence of the GNU social site license. Used if 'broughtby' is set.
             // TRANS: Text between [] is a link description, text between () is the link itself.
             // TRANS: Make sure there is no whitespace between "]" and "(".
             // TRANS: "%%site.broughtby%%" is the value of the variable site.broughtby
             $instr = _('**%%site.name%%** is a social network, courtesy of [%%site.broughtby%%](%%site.broughtbyurl%%).');
         } else {
-            // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is not set.
+            // TRANS: First sentence of the GNU social site license. Used if 'broughtby' is not set.
             $instr = _('**%%site.name%%** is a social network.');
         }
         $instr .= ' ';
-        // TRANS: Second sentence of the StatusNet site license. Mentions the StatusNet source code license.
+        // TRANS: Second sentence of the GNU social site license. Mentions the GNU social source code license.
         // TRANS: Make sure there is no whitespace between "]" and "(".
-        // TRANS: Text between [] is a link description, text between () is the link itself.
-        // TRANS: %s is the version of StatusNet that is being used.
-        $instr .= sprintf(_('It runs on [GNU social](http://www.gnu.org/software/social/), version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), STATUSNET_VERSION);
+        // TRANS: [%1$s](%2$s) is a link description followed by the link itself
+        // TRANS: %3$s is the version of GNU social that is being used.
+        $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);
         $output = common_markup_to_html($instr);
         $this->raw($output);
         // do it
@@ -1110,7 +1149,7 @@ class Action extends HTMLOutputter // lawsuit
                 $image    = common_config('license', 'image');
                 $sslimage = common_config('license', 'sslimage');
 
-                if (StatusNet::isHTTPS()) {
+                if (GNUsocial::isHTTPS()) {
                     if (!empty($sslimage)) {
                         $url = $sslimage;
                     } else if (preg_match('#^http://i.creativecommons.org/#', $image)) {
@@ -1133,12 +1172,10 @@ class Action extends HTMLOutputter // lawsuit
                 // TRANS: license message in footer.
                 // TRANS: %1$s is the site name, %2$s is a link to the license URL, with a licence name set in configuration.
                 $notice = _('All %1$s content and data are available under the %2$s license.');
-                $link = "<a class=\"license\" rel=\"external license\" href=\"" .
-                        htmlspecialchars(common_config('license', 'url')) .
-                        "\">" .
-                        htmlspecialchars(common_config('license', 'title')) .
-                        "</a>";
-                $this->raw(sprintf(htmlspecialchars($notice),
+                $link = sprintf('<a class="license" rel="external license" href="%1$s">%2$s</a>',
+                                htmlspecialchars(common_config('license', 'url')),
+                                htmlspecialchars(common_config('license', 'title')));
+                $this->raw(@sprintf(htmlspecialchars($notice),
                                    htmlspecialchars(common_config('site', 'name')),
                                    $link));
                 $this->elementEnd('p');
@@ -1322,6 +1359,19 @@ class Action extends HTMLOutputter // lawsuit
         }
     }
 
+    /**
+     * This is a cheap hack to avoid a bug in DB_DataObject
+     * where '' is non-type-aware compared to 0, which means it
+     * will always be true for values like false and 0 too...
+     *
+     * Upstream bug is::
+     * https://pear.php.net/bugs/bug.php?id=20291
+     */
+    function booleanintstring($key, $def=false)
+    {
+        return $this->boolean($key, $def) ? '1' : '0';
+    }
+
     /**
      * Integer value of an argument
      *
@@ -1334,9 +1384,9 @@ class Action extends HTMLOutputter // lawsuit
      */
     function int($key, $defValue=null, $maxValue=null, $minValue=null)
     {
-        $arg = strtolower($this->trimmed($key));
+        $arg = intval($this->arg($key));
 
-        if (is_null($arg) || !is_integer($arg)) {
+        if (!is_numeric($this->arg($key)) || $arg != $this->arg($key)) {
             return $defValue;
         }
 
@@ -1359,26 +1409,99 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
-    function serverError($msg, $code=500)
+    function serverError($msg, $code=500, $format=null)
     {
-        $action = $this->trimmed('action');
-        common_debug("Server error '$code' on '$action': $msg", __FILE__);
-        throw new ServerException($msg, $code);
+        if ($format === null) {
+            $format = $this->format;
+        }
+
+        common_debug("Server error '{$code}' on '{$this->action}': {$msg}", __FILE__);
+
+        if (!array_key_exists($code, ServerErrorAction::$status)) {
+            $code = 500;
+        }
+
+        $status_string = ServerErrorAction::$status[$code];
+
+        switch ($format) {
+        case 'xml':
+            header("HTTP/1.1 {$code} {$status_string}");
+            $this->initDocument('xml');
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            if (!isset($this->callback)) {
+                header("HTTP/1.1 {$code} {$status_string}");
+            }
+            $this->initDocument('json');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+            $this->endDocument('json');
+            break;
+        default:
+            throw new ServerException($msg, $code);
+        }
+
+        exit((int)$code);
     }
 
     /**
      * Client error
      *
-     * @param string  $msg  error message to display
-     * @param integer $code http error code, 400 by default
+     * @param string  $msg    error message to display
+     * @param integer $code   http error code, 400 by default
+     * @param string  $format error format (json, xml, text) for ApiAction
      *
      * @return nothing
+     * @throws ClientException always
      */
-    function clientError($msg, $code=400)
-    {
-        $action = $this->trimmed('action');
-        common_debug("User error '$code' on '$action': $msg", __FILE__);
-        throw new ClientException($msg, $code);
+    function clientError($msg, $code=400, $format=null)
+    {
+        // $format is currently only relevant for an ApiAction anyway
+        if ($format === null) {
+            $format = $this->format;
+        }
+
+        common_debug("User error '{$code}' on '{$this->action}': {$msg}", __FILE__);
+
+        if (!array_key_exists($code, ClientErrorAction::$status)) {
+            $code = 400;
+        }
+        
+        $status_string = ClientErrorAction::$status[$code];
+
+        switch ($format) {
+        case 'xml':
+            header("HTTP/1.1 {$code} {$status_string}");
+            $this->initDocument('xml');
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            if (!isset($this->callback)) {
+                header("HTTP/1.1 {$code} {$status_string}");
+            }
+            $this->initDocument('json');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+            $this->endDocument('json');
+            break;
+        case 'text':
+            header("HTTP/1.1 {$code} {$status_string}");
+            header('Content-Type: text/plain; charset=utf-8');
+            echo $msg;
+            break;
+        default:
+            throw new ClientException($msg, $code);
+        }
+        exit((int)$code);
     }
 
     /**
@@ -1421,7 +1544,7 @@ class Action extends HTMLOutputter // lawsuit
      */
     function returnToArgs()
     {
-        $action = $this->trimmed('action');
+        $action = $this->getActionName();
         $args   = $this->args;
         unset($args['action']);
         if (common_config('site', 'fancy')) {
@@ -1532,7 +1655,7 @@ class Action extends HTMLOutputter // lawsuit
      */
     function getFeeds()
     {
-        return null;
+        return array();
     }
 
     /**