]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/action.php
Making us less dependant on javascript trust
[quix0rs-gnu-social.git] / lib / action.php
index 0e5d7ae361c0539569e8052c37bc5e73f31da952..74ced563fbbbc46e3951dc9dfc5431db7ac38f89 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
@@ -55,7 +50,23 @@ require_once INSTALLDIR.'/lib/htmloutputter.php';
  */
 class Action extends HTMLOutputter // lawsuit
 {
-    var $args;
+    // This should be protected/private in the future
+    public $args = array();
+
+    // Action properties, set per-class
+    protected $action = false;
+    protected $ajax   = false;
+    protected $menus  = true;
+    protected $needLogin = false;
+    protected $needPost = false;
+
+    // The currently scoped profile (normally Profile::current; from $this->auth_user for API)
+    protected $scoped = null;
+
+    // Related to front-end user representation
+    protected $format = null;
+    protected $error  = null;
+    protected $msg    = null;
 
     /**
      * Constructor
@@ -73,6 +84,51 @@ class Action extends HTMLOutputter // lawsuit
         parent::__construct($output, $indent);
     }
 
+    function getError()
+    {
+        return $this->error;
+    }
+
+    function getInfo()
+    {
+        return $this->msg;
+    }
+
+    static public function run(array $args=array(), $output='php://output', $indent=null) {
+        $class = get_called_class();
+        $action = new $class($output, $indent);
+        $action->execute($args);
+        return $action;
+    }
+
+    public function execute(array $args=array()) {
+        // checkMirror stuff
+        if (common_config('db', 'mirror') && $this->isReadOnly($args)) {
+            if (is_array(common_config('db', 'mirror'))) {
+                // "load balancing", ha ha
+                $arr = common_config('db', 'mirror');
+                $k = array_rand($arr);
+                $mirror = $arr[$k];
+            } else {
+                $mirror = common_config('db', 'mirror');
+            }
+
+            // everyone else uses the mirror
+            common_config_set('db', 'database', $mirror);
+        }
+
+        $status = $this->prepare($args);
+        if ($status) {
+            $this->handle($args);
+        } else {
+            common_debug('Prepare failed for Action.');
+        }
+
+        $this->flush();
+
+        Event::handle('EndActionExecute', array($status, $this));
+    }
+
     /**
      * For initializing members of the class.
      *
@@ -80,12 +136,36 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return boolean true
      */
-    function prepare($argarray)
+    protected function prepare(array $args=array())
     {
-        $this->args =& common_copy_args($argarray);
+        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);
+        }
+
+        $this->args = common_copy_args($args);
+
+        $this->action = $this->trimmed('action');
+
+        if ($this->ajax || $this->boolean('ajax')) {
+            // check with StatusNet::isAjax()
+            StatusNet::setAjax(true);
+        }
+
+        if ($this->needLogin) {
+            $this->checkLogin(); // if not logged in, this redirs/excepts
+        }
+
+        $this->updateScopedProfile();
+
         return true;
     }
 
+    function updateScopedProfile() {
+        $this->scoped = Profile::current();
+        return $this->scoped;
+    }
+
     /**
      * Show page, a template method.
      *
@@ -95,10 +175,12 @@ class Action extends HTMLOutputter // lawsuit
     {
         if (Event::handle('StartShowHTML', array($this))) {
             $this->startHTML();
+            $this->flush();
             Event::handle('EndShowHTML', array($this));
         }
         if (Event::handle('StartShowHead', array($this))) {
             $this->showHead();
+            $this->flush();
             Event::handle('EndShowHead', array($this));
         }
         if (Event::handle('StartShowBody', array($this))) {
@@ -111,6 +193,19 @@ class Action extends HTMLOutputter // lawsuit
         }
     }
 
+    function endHTML()
+    {
+        global $_startTime;
+
+        if (isset($_startTime)) {
+            $endTime = microtime(true);
+            $diff = round(($endTime - $_startTime) * 1000);
+            $this->raw("<!-- ${diff}ms -->");
+        }
+
+        return parent::endHTML();
+    }
+
     /**
      * Show head, a template method.
      *
@@ -145,7 +240,7 @@ class Action extends HTMLOutputter // lawsuit
     {
         $this->element('title', null,
                        // TRANS: Page title. %1$s is the title, %2$s is the site name.
-                       sprintf(_("%1\$s - %2\$s"),
+                       sprintf(_('%1$s - %2$s'),
                                $this->title(),
                                common_config('site', 'name')));
     }
@@ -161,7 +256,7 @@ class Action extends HTMLOutputter // lawsuit
     function title()
     {
         // TRANS: Page title for a page without a title set.
-        return _("Untitled page");
+        return _('Untitled page');
     }
 
     /**
@@ -202,42 +297,30 @@ 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('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]');
+                               'href="'.Theme::path('css/ie.css', 'base').'?version='.GNUSOCIAL_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]');
+                                       'href="'.Theme::path('css/ie'.$ver.'.css', 'base').'?version='.GNUSOCIAL_VERSION.'" /><![endif]');
                     }
                 }
-                $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
-                               'href="'.Theme::path('css/ie.css', null).'?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='.GNUSOCIAL_VERSION.'" /><![endif]');
+                }
                 Event::handle('EndShowUAStyles', array($this));
             }
 
-            if (Event::handle('StartShowDesign', array($this))) {
-
-                $user = common_current_user();
-
-                if (empty($user) || $user->viewdesigns) {
-                    $design = $this->getDesign();
-
-                    if (!empty($design)) {
-                        $design->showCSS($this);
-                    }
-                }
-
-                Event::handle('EndShowDesign', array($this));
-            }
             Event::handle('EndShowStyles', array($this));
 
             if (common_config('custom_css', 'enabled')) {
@@ -254,14 +337,28 @@ class Action extends HTMLOutputter // lawsuit
 
     function primaryCssLink($mainTheme=null, $media=null)
     {
+        $theme = new Theme($mainTheme);
+
+        // Some themes may have external stylesheets, such as using the
+        // Google Font APIs to load webfonts.
+        foreach ($theme->getExternals() as $url) {
+            $this->cssLink($url, $mainTheme, $media);
+        }
+
         // If the currently-selected theme has dependencies on other themes,
         // we'll need to load their display.css files as well in order.
-        $theme = new Theme($mainTheme);
         $baseThemes = $theme->getDeps();
         foreach ($baseThemes as $baseTheme) {
             $this->cssLink('css/display.css', $baseTheme, $media);
         }
         $this->cssLink('css/display.css', $mainTheme, $media);
+
+        // Additional styles for RTL languages
+        if (is_rtl(common_language())) {
+            if (file_exists(Theme::file('css/rtl.css'))) {
+                $this->cssLink('css/rtl.css', $mainTheme, $media);
+            }
+        }
     }
 
     /**
@@ -273,21 +370,33 @@ class Action extends HTMLOutputter // lawsuit
     {
         if (Event::handle('StartShowScripts', array($this))) {
             if (Event::handle('StartShowJQueryScripts', array($this))) {
-                $this->script('jquery.min.js');
-                $this->script('jquery.form.min.js');
-                $this->script('jquery.cookie.min.js');
-                $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.min.js').'"); }');
-                $this->script('jquery.joverlay.min.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', StatusNet::isHTTPS()).'"); }');
+                $this->script('extlib/jquery.infieldlabel.js');
+
                 Event::handle('EndShowJQueryScripts', array($this));
             }
-            if (Event::handle('StartShowStatusNetScripts', array($this)) &&
-                Event::handle('StartShowLaconicaScripts', array($this))) {
-                $this->script('util.min.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();
-                // Frame-busting code to avoid clickjacking attacks.
-                $this->inlineScript('if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
+                // 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'
+                // header, which prevents framing in newer browser.
+                if (common_config('javascript', 'bustframes')) {
+                    $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));
         }
@@ -300,26 +409,31 @@ class Action extends HTMLOutputter // lawsuit
      * events and appending to the array. Try to avoid adding strings that won't be used, as
      * they'll be added to HTML output.
      */
-    
     function showScriptMessages()
     {
         $messages = array();
-       
+
         if (Event::handle('StartScriptMessages', array($this, &$messages))) {
             // Common messages needed for timeline views etc...
 
             // 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 = array_merge($messages, $this->getScriptMessages());
-           
-           Event::handle('EndScriptMessages', array($this, &$messages));
+
+            Event::handle('EndScriptMessages', array($this, &$messages));
         }
-       
+
         if (!empty($messages)) {
             $this->inlineScript('SN.messages=' . json_encode($messages));
         }
-       
+
         return $messages;
     }
 
@@ -422,11 +536,14 @@ class Action extends HTMLOutputter // lawsuit
         $this->elementStart('div', array('id' => 'wrap'));
         if (Event::handle('StartShowHeader', array($this))) {
             $this->showHeader();
+            $this->flush();
             Event::handle('EndShowHeader', array($this));
         }
         $this->showCore();
+        $this->flush();
         if (Event::handle('StartShowFooter', array($this))) {
             $this->showFooter();
+            $this->flush();
             Event::handle('EndShowFooter', array($this));
         }
         $this->elementEnd('div');
@@ -451,14 +568,7 @@ class Action extends HTMLOutputter // lawsuit
 
             Event::handle('EndShowSiteNotice', array($this));
         }
-        if (common_logged_in()) {
-            if (Event::handle('StartShowNoticeForm', array($this))) {
-                $this->showNoticeForm();
-                Event::handle('EndShowNoticeForm', array($this));
-            }
-        } else {
-            $this->showAnonymousMessage();
-        }
+
         $this->elementEnd('div');
     }
 
@@ -476,9 +586,13 @@ class Action extends HTMLOutputter // lawsuit
                 $user = User::singleUser();
                 $url = common_local_url('showstream',
                                         array('nickname' => $user->nickname));
+            } else if (common_logged_in()) {
+                $cur = common_current_user();
+                $url = common_local_url('all', array('nickname' => $cur->nickname));
             } else {
                 $url = common_local_url('public');
             }
+
             $this->elementStart('a', array('class' => 'url home bookmark',
                                            'href' => $url));
 
@@ -488,7 +602,7 @@ class Action extends HTMLOutputter // lawsuit
                     // if logo is an uploaded file, try to fall back to HTTPS file URL
                     $httpUrl = common_config('site', 'logo');
                     if (!empty($httpUrl)) {
-                        $f = File::staticGet('url', $httpUrl);
+                        $f = File::getKV('url', $httpUrl);
                         if (!empty($f) && !empty($f->filename)) {
                             // this will handle the HTTPS case
                             $logoUrl = File::url($f->filename);
@@ -513,6 +627,7 @@ class Action extends HTMLOutputter // lawsuit
             $this->text(' ');
             $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
             $this->elementEnd('a');
+
             Event::handle('EndAddressData', array($this));
         }
         $this->elementEnd('address');
@@ -525,83 +640,18 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showPrimaryNav()
     {
+        $this->elementStart('div', array('id' => 'site_nav_global_primary'));
+
         $user = common_current_user();
-        $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
-        // TRANS: DT element for primary navigation menu. String is hidden in default CSS.
-        $this->element('dt', null, _('Primary site navigation'));
-        $this->elementStart('dd');
-        $this->elementStart('ul', array('class' => 'nav'));
-        if (Event::handle('StartPrimaryNav', array($this))) {
-            if ($user) {
-                // TRANS: Tooltip for main menu option "Personal"
-                $tooltip = _m('TOOLTIP', 'Personal profile and friends timeline');
-                $this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
-                                // TRANS: Main menu option when logged in for access to personal profile and friends timeline
-                                _m('MENU', 'Personal'), $tooltip, false, 'nav_home');
-                // TRANS: Tooltip for main menu option "Account"
-                $tooltip = _m('TOOLTIP', 'Change your email, avatar, password, profile');
-                $this->menuItem(common_local_url('profilesettings'),
-                                // TRANS: Main menu option when logged in for access to user settings
-                                _('Account'), $tooltip, false, 'nav_account');
-                // TRANS: Tooltip for main menu option "Services"
-                $tooltip = _m('TOOLTIP', 'Connect to services');
-                $this->menuItem(common_local_url('oauthconnectionssettings'),
-                                // TRANS: Main menu option when logged in and connection are possible for access to options to connect to other services
-                                _('Connect'), $tooltip, false, 'nav_connect');
-                if ($user->hasRight(Right::CONFIGURESITE)) {
-                    // TRANS: Tooltip for menu option "Admin"
-                    $tooltip = _m('TOOLTIP', 'Change site configuration');
-                    $this->menuItem(common_local_url('siteadminpanel'),
-                                    // TRANS: Main menu option when logged in and site admin for access to site configuration
-                                    _m('MENU', 'Admin'), $tooltip, false, 'nav_admin');
-                }
-                if (common_config('invite', 'enabled')) {
-                    // TRANS: Tooltip for main menu option "Invite"
-                    $tooltip = _m('TOOLTIP', 'Invite friends and colleagues to join you on %s');
-                    $this->menuItem(common_local_url('invite'),
-                                    // TRANS: Main menu option when logged in and invitations are allowed for inviting new users
-                                    _m('MENU', 'Invite'),
-                                    sprintf($tooltip,
-                                            common_config('site', 'name')),
-                                    false, 'nav_invitecontact');
-                }
-                // TRANS: Tooltip for main menu option "Logout"
-                $tooltip = _m('TOOLTIP', 'Logout from the site');
-                $this->menuItem(common_local_url('logout'),
-                                // TRANS: Main menu option when logged in to log out the current user
-                                _m('MENU', 'Logout'), $tooltip, false, 'nav_logout');
-            }
-            else {
-                if (!common_config('site', 'closed') && !common_config('site', 'inviteonly')) {
-                    // TRANS: Tooltip for main menu option "Register"
-                    $tooltip = _m('TOOLTIP', 'Create an account');
-                    $this->menuItem(common_local_url('register'),
-                                    // TRANS: Main menu option when not logged in to register a new account
-                                    _m('MENU', 'Register'), $tooltip, false, 'nav_register');
-                }
-                // TRANS: Tooltip for main menu option "Login"
-                $tooltip = _m('TOOLTIP', 'Login to the site');
-                $this->menuItem(common_local_url('login'),
-                                // TRANS: Main menu option when not logged in to log in
-                                _m('MENU', 'Login'), $tooltip, false, 'nav_login');
-            }
-            // TRANS: Tooltip for main menu option "Help"
-            $tooltip = _m('TOOLTIP', 'Help me!');
-            $this->menuItem(common_local_url('doc', array('title' => 'help')),
-                            // TRANS: Main menu option for help on the StatusNet site
-                            _m('MENU', 'Help'), $tooltip, false, 'nav_help');
-            if ($user || !common_config('site', 'private')) {
-                // TRANS: Tooltip for main menu option "Search"
-                $tooltip = _m('TOOLTIP', 'Search for people or text');
-                $this->menuItem(common_local_url('peoplesearch'),
-                                // TRANS: Main menu option when logged in or when the StatusNet instance is not private
-                                _m('MENU', 'Search'), $tooltip, false, 'nav_search');
-            }
-            Event::handle('EndPrimaryNav', array($this));
+
+        if (!empty($user) || !common_config('site', 'private')) {
+            $form = new SearchForm($this);
+            $form->show();
         }
-        $this->elementEnd('ul');
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
+
+        $pn = new PrimaryNav($this);
+        $pn->show();
+        $this->elementEnd('div');
     }
 
     /**
@@ -614,14 +664,10 @@ class Action extends HTMLOutputter // lawsuit
         // Revist. Should probably do an hAtom pattern here
         $text = common_config('site', 'notice');
         if ($text) {
-            $this->elementStart('dl', array('id' => 'site_notice',
+            $this->elementStart('div', array('id' => 'site_notice',
                                             'class' => 'system_notice'));
-            // TRANS: DT element for site notice. String is hidden in default CSS.
-            $this->element('dt', null, _('Site notice'));
-            $this->elementStart('dd', null);
             $this->raw($text);
-            $this->elementEnd('dd');
-            $this->elementEnd('dl');
+            $this->elementEnd('div');
         }
     }
 
@@ -634,8 +680,75 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showNoticeForm()
     {
-        $notice_form = new NoticeForm($this);
-        $notice_form->show();
+        // TRANS: Tab on the notice form.
+        $tabs = array('status' => array('title' => _m('TAB','Status'),
+                                        'href'  => common_local_url('newnotice')));
+
+        $this->elementStart('div', 'input_forms');
+
+        if (Event::handle('StartShowEntryForms', array(&$tabs))) {
+            $this->elementStart('ul', array('class' => 'nav',
+                                            'id' => 'input_form_nav'));
+
+            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('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 => $data) {
+                $attrs = array('class' => 'input_form',
+                               'id' => 'input_form_'.$tag);
+
+                $this->elementStart('div', $attrs);
+
+                $form = null;
+
+                if (Event::handle('StartMakeEntryForm', array($tag, $this, &$form))) {
+                    if ($tag == 'status') {
+                        $options = $this->noticeFormOptions();
+                        $form = new NoticeForm($this, $options);
+                    }
+                    Event::handle('EndMakeEntryForm', array($tag, $this, $form));
+                }
+
+                if (!empty($form)) {
+                    $form->show();
+                }
+
+                $this->elementEnd('div');
+            }
+        }
+
+        $this->elementEnd('div');
+    }
+
+    function noticeFormOptions()
+    {
+        return array();
     }
 
     /**
@@ -660,19 +773,28 @@ class Action extends HTMLOutputter // lawsuit
     function showCore()
     {
         $this->elementStart('div', array('id' => 'core'));
+        $this->elementStart('div', array('id' => 'aside_primary_wrapper'));
+        $this->elementStart('div', array('id' => 'content_wrapper'));
+        $this->elementStart('div', array('id' => 'site_nav_local_views_wrapper'));
         if (Event::handle('StartShowLocalNavBlock', array($this))) {
             $this->showLocalNavBlock();
+            $this->flush();
             Event::handle('EndShowLocalNavBlock', array($this));
         }
         if (Event::handle('StartShowContentBlock', array($this))) {
             $this->showContentBlock();
+            $this->flush();
             Event::handle('EndShowContentBlock', array($this));
         }
         if (Event::handle('StartShowAside', array($this))) {
             $this->showAside();
+            $this->flush();
             Event::handle('EndShowAside', array($this));
         }
         $this->elementEnd('div');
+        $this->elementEnd('div');
+        $this->elementEnd('div');
+        $this->elementEnd('div');
     }
 
     /**
@@ -682,13 +804,25 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showLocalNavBlock()
     {
-        $this->elementStart('dl', array('id' => 'site_nav_local_views'));
-        // TRANS: DT element for local views block. String is hidden in default CSS.
-        $this->element('dt', null, _('Local views'));
-        $this->elementStart('dd');
+        // Need to have this ID for CSS; I'm too lazy to add it to
+        // all menus
+        $this->elementStart('div', array('id' => 'site_nav_local_views'));
+        // Cheat cheat cheat!
         $this->showLocalNav();
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
+        $this->elementEnd('div');
+    }
+
+    /**
+     * If there's a logged-in user, show a bit of login context
+     *
+     * @return nothing
+     */
+    function showProfileBlock()
+    {
+        if (common_logged_in()) {
+            $block = new DefaultProfileBlock($this);
+            $block->show();
+        }
     }
 
     /**
@@ -700,7 +834,43 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showLocalNav()
     {
-        // does nothing by default
+        $nav = new DefaultLocalNav($this);
+        $nav->show();
+    }
+
+    /**
+     * Show menu for an object (group, profile)
+     *
+     * This block will only show if a subclass has overridden
+     * the showObjectNav() method.
+     *
+     * @return nothing
+     */
+    function showObjectNavBlock()
+    {
+        $rmethod = new ReflectionMethod($this, 'showObjectNav');
+        $dclass = $rmethod->getDeclaringClass()->getName();
+
+        if ($dclass != 'Action') {
+            // Need to have this ID for CSS; I'm too lazy to add it to
+            // all menus
+            $this->elementStart('div', array('id' => 'site_nav_object',
+                                             'class' => 'section'));
+            $this->showObjectNav();
+            $this->elementEnd('div');
+        }
+    }
+
+    /**
+     * Show object navigation.
+     *
+     * If there are things to do with this object, show it here.
+     *
+     * @return nothing
+     */
+    function showObjectNav()
+    {
+        /* Nothing here. */
     }
 
     /**
@@ -711,6 +881,12 @@ class Action extends HTMLOutputter // lawsuit
     function showContentBlock()
     {
         $this->elementStart('div', array('id' => 'content'));
+        if (common_logged_in()) {
+            if (Event::handle('StartShowNoticeForm', array($this))) {
+                $this->showNoticeForm();
+                Event::handle('EndShowNoticeForm', array($this));
+            }
+        }
         if (Event::handle('StartShowPageTitle', array($this))) {
             $this->showPageTitle();
             Event::handle('EndShowPageTitle', array($this));
@@ -751,17 +927,13 @@ class Action extends HTMLOutputter // lawsuit
 
         if ($dclass != 'Action' || Event::hasHandler('StartShowPageNotice')) {
 
-            $this->elementStart('dl', array('id' => 'page_notice',
+            $this->elementStart('div', array('id' => 'page_notice',
                                             'class' => 'system_notice'));
-            // TRANS: DT element for page notice. String is hidden in default CSS.
-            $this->element('dt', null, _('Page notice'));
-            $this->elementStart('dd');
             if (Event::handle('StartShowPageNotice', array($this))) {
                 $this->showPageNotice();
                 Event::handle('EndShowPageNotice', array($this));
             }
-            $this->elementEnd('dd');
-            $this->elementEnd('dl');
+            $this->elementEnd('div');
         }
     }
 
@@ -796,6 +968,11 @@ class Action extends HTMLOutputter // lawsuit
     {
         $this->elementStart('div', array('id' => 'aside_primary',
                                          'class' => 'aside'));
+        $this->showProfileBlock();
+        if (Event::handle('StartShowObjectNavBlock', array($this))) {
+            $this->showObjectNavBlock();
+            Event::handle('EndShowObjectNavBlock', array($this));
+        }
         if (Event::handle('StartShowSections', array($this))) {
             $this->showSections();
             Event::handle('EndShowSections', array($this));
@@ -841,8 +1018,11 @@ class Action extends HTMLOutputter // lawsuit
     function showFooter()
     {
         $this->elementStart('div', array('id' => 'footer'));
-        $this->showSecondaryNav();
-        $this->showLicenses();
+        if (Event::handle('StartShowInsideFooter', array($this))) {
+            $this->showSecondaryNav();
+            $this->showLicenses();
+            Event::handle('EndShowInsideFooter', array($this));
+        }
         $this->elementEnd('div');
     }
 
@@ -853,48 +1033,8 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showSecondaryNav()
     {
-        $this->elementStart('dl', array('id' => 'site_nav_global_secondary'));
-        // TRANS: DT element for secondary navigation menu. String is hidden in default CSS.
-        $this->element('dt', null, _('Secondary site navigation'));
-        $this->elementStart('dd', null);
-        $this->elementStart('ul', array('class' => 'nav'));
-        if (Event::handle('StartSecondaryNav', array($this))) {
-            $this->menuItem(common_local_url('doc', array('title' => 'help')),
-                            // TRANS: Secondary navigation menu option leading to help on StatusNet.
-                            _('Help'));
-            $this->menuItem(common_local_url('doc', array('title' => 'about')),
-                            // TRANS: Secondary navigation menu option leading to text about StatusNet site.
-                            _('About'));
-            $this->menuItem(common_local_url('doc', array('title' => 'faq')),
-                            // TRANS: Secondary navigation menu option leading to Frequently Asked Questions.
-                            _('FAQ'));
-            $bb = common_config('site', 'broughtby');
-            if (!empty($bb)) {
-                $this->menuItem(common_local_url('doc', array('title' => 'tos')),
-                                // TRANS: Secondary navigation menu option leading to Terms of Service.
-                                _('TOS'));
-            }
-            $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
-                            // TRANS: Secondary navigation menu option leading to privacy policy.
-                            _('Privacy'));
-            $this->menuItem(common_local_url('doc', array('title' => 'source')),
-                            // TRANS: Secondary navigation menu option. Leads to information about StatusNet and its license.
-                            _('Source'));
-            $this->menuItem(common_local_url('version'),
-                            // TRANS: Secondary navigation menu option leading to version information on the StatusNet site.
-                            _('Version'));
-            $this->menuItem(common_local_url('doc', array('title' => 'contact')),
-                            // TRANS: Secondary navigation menu option leading to e-mail contact information on the
-                            // TRANS: StatusNet site, where to report bugs, ...
-                            _('Contact'));
-            $this->menuItem(common_local_url('doc', array('title' => 'badge')),
-                            // TRANS: Secondary navigation menu option. Leads to information about embedding a timeline widget.
-                            _('Badge'));
-            Event::handle('EndSecondaryNav', array($this));
-        }
-        $this->elementEnd('ul');
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
+        $sn = new SecondaryNav($this);
+        $sn->show();
     }
 
     /**
@@ -904,41 +1044,35 @@ class Action extends HTMLOutputter // lawsuit
      */
     function showLicenses()
     {
-        $this->elementStart('dl', array('id' => 'licenses'));
-        $this->showStatusNetLicense();
+        $this->showGNUsocialLicense();
         $this->showContentLicense();
-        $this->elementEnd('dl');
     }
 
     /**
-     * Show StatusNet license.
+     * Show GNU social license.
      *
      * @return nothing
      */
-    function showStatusNetLicense()
+    function showGNUsocialLicense()
     {
-        // TRANS: DT element for StatusNet software license.
-        $this->element('dt', array('id' => 'site_statusnet_license'), _('StatusNet software license'));
-        $this->elementStart('dd', null);
         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 microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%).');
+            $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.
-            $instr = _('**%%site.name%%** is a microblogging service.');
+            // 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 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);
+        // 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);
-        $this->elementEnd('dd');
         // do it
     }
 
@@ -950,10 +1084,6 @@ class Action extends HTMLOutputter // lawsuit
     function showContentLicense()
     {
         if (Event::handle('StartShowContentLicense', array($this))) {
-            // TRANS: DT element for StatusNet site content license.
-            $this->element('dt', array('id' => 'site_content_license'), _('Site content license'));
-            $this->elementStart('dd', array('id' => 'site_content_license_cc'));
-
             switch (common_config('license', 'type')) {
             case 'private':
                 // TRANS: Content license displayed when license is set to 'private'.
@@ -1014,7 +1144,6 @@ class Action extends HTMLOutputter // lawsuit
                 break;
             }
 
-            $this->elementEnd('dd');
             Event::handle('EndShowContentLicense', array($this));
         }
     }
@@ -1093,11 +1222,9 @@ class Action extends HTMLOutputter // lawsuit
     /**
      * Handler method
      *
-     * @param array $argarray is ignored since it's now passed in in prepare()
-     *
      * @return boolean is read only action?
      */
-    function handle($argarray=null)
+    protected function handle()
     {
         header('Vary: Accept-Encoding,Cookie');
 
@@ -1231,26 +1358,121 @@ 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)
+    function clientError($msg, $code=400, $format=null)
     {
-        $action = $this->trimmed('action');
-        common_debug("User error '$code' on '$action': $msg", __FILE__);
-        throw new ClientException($msg, $code);
+        // $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']);
+            $this->text(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);
+    }
+
+    /**
+     * If not logged in, take appropriate action (redir or exception)
+     *
+     * @param boolean $redir Redirect to login if not logged in
+     *
+     * @return boolean true if logged in (never returns if not)
+     */
+    public function checkLogin($redir=true)
+    {
+        if (common_logged_in()) {
+            return true;
+        }
+
+        if ($redir==true) {
+            common_set_returnto($_SERVER['REQUEST_URI']);
+            common_redirect(common_local_url('login'));
+        }
+
+        // TRANS: Error message displayed when trying to perform an action that requires a logged in user.
+        $this->clientError(_('Not logged in.'), 403);
     }
 
     /**
@@ -1297,16 +1519,26 @@ class Action extends HTMLOutputter // lawsuit
      *
      * @return nothing
      */
-    function menuItem($url, $text, $title=null, $is_selected=false, $id=null)
+    function menuItem($url, $text, $title=null, $is_selected=false, $id=null, $class=null)
     {
         // Added @id to li for some control.
         // XXX: We might want to move this to htmloutputter.php
-        $lattrs = array();
+        $lattrs  = array();
+        $classes = array();
+        if ($class !== null) {
+            $classes[] = trim($class);
+        }
         if ($is_selected) {
-            $lattrs['class'] = 'current';
+            $classes[] = 'current';
         }
 
-        (is_null($id)) ? $lattrs : $lattrs['id'] = $id;
+        if (!empty($classes)) {
+            $lattrs['class'] = implode(' ', $classes);
+        }
+
+        if (!is_null($id)) {
+            $lattrs['id'] = $id;
+        }
 
         $this->elementStart('li', $lattrs);
         $attrs['href'] = $url;
@@ -1335,11 +1567,8 @@ class Action extends HTMLOutputter // lawsuit
     {
         // Does a little before-after block for next/prev page
         if ($have_before || $have_after) {
-            $this->elementStart('dl', 'pagination');
-            // TRANS: DT element for pagination (previous/next, etc.).
-            $this->element('dt', null, _('Pagination'));
-            $this->elementStart('dd', null);
-            $this->elementStart('ul', array('class' => 'nav'));
+            $this->elementStart('ul', array('class' => 'nav',
+                                            'id' => 'pagination'));
         }
         if ($have_before) {
             $pargs   = array('page' => $page-1);
@@ -1363,8 +1592,6 @@ class Action extends HTMLOutputter // lawsuit
         }
         if ($have_before || $have_after) {
             $this->elementEnd('ul');
-            $this->elementEnd('dd');
-            $this->elementEnd('dl');
         }
     }
 
@@ -1380,16 +1607,6 @@ class Action extends HTMLOutputter // lawsuit
         return null;
     }
 
-    /**
-     * A design for this action
-     *
-     * @return Design a design object to use
-     */
-    function getDesign()
-    {
-        return Design::siteDesign();
-    }
-
     /**
      * Check the session token.
      *