* @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
*/
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; // implies canPost if true
+ protected $canPost = false; // can this action handle POST method?
+
+ // 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
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.
*
*
* @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);
+ }
+
+ // needPost, of course, overrides canPost if true
+ if (!$this->canPost) {
+ $this->canPost = $this->needPost;
+ }
+
+ $this->args = common_copy_args($args);
- if ($this->boolean('ajax')) {
+ // 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);
}
+ if ($this->needLogin) {
+ $this->checkLogin(); // if not logged in, this redirs/excepts
+ }
+
+ $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.
*
*/
function showPage()
{
+ if (StatusNet::isAjax()) {
+ self::showAjax();
+ return;
+ }
if (Event::handle('StartShowHTML', array($this))) {
$this->startHTML();
$this->flush();
}
}
+ 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;
// 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'));
+ $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]');
- }
- }
- $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));
}
- 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')) {
$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);
+ }
+ }
}
/**
{
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/json2.min.js').'"); }');
- $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/json2.js').'"); }');
- $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', StatusNet::isHTTPS()).'"); }');
+ $this->script('extlib/jquery.infieldlabel.js');
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') . '";');
+ common_local_url('peopletagautocomplete') . '";');
$this->showScriptMessages();
- // Frame-busting code to avoid clickjacking attacks.
+ // 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) { window.top.location.href = window.self.location.href; }');
+ $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));
}
$messages = array_merge($messages, $this->getScriptMessages());
- Event::handle('EndScriptMessages', array($this, &$messages));
+ Event::handle('EndScriptMessages', array($this, &$messages));
}
if (!empty($messages)) {
*/
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();
*/
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();
$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 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);
}
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));
function showPrimaryNav()
{
$this->elementStart('div', array('id' => 'site_nav_global_primary'));
+
+ $user = common_current_user();
+
+ if (!empty($user) || !common_config('site', 'private')) {
+ $form = new SearchForm($this);
+ $form->show();
+ }
+
$pn = new PrimaryNav($this);
$pn->show();
$this->elementEnd('div');
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);
*
* @return nothing
*/
- function showContent()
+ protected function showContent()
{
}
*/
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 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);
// do it
// 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');
/**
* 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');
*/
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;
}
*
* @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, $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']);
+ $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)
*/
- function clientError($msg, $code=400)
+ public function checkLogin($redir=true)
{
- $action = $this->trimmed('action');
- common_debug("User error '$code' on '$action': $msg", __FILE__);
- throw new ClientException($msg, $code);
+ 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);
}
/**
*/
function returnToArgs()
{
- $action = $this->trimmed('action');
+ $action = $this->getActionName();
$args = $this->args;
unset($args['action']);
if (common_config('site', 'fancy')) {
{
// 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) {
- $lattrs['class'] = $class;
- if ($is_selected) {
- $lattrs['class'] = trim('current ' . $lattrs['class']);
- }
+ $classes[] = trim($class);
+ }
+ if ($is_selected) {
+ $classes[] = 'current';
+ }
+
+ if (!empty($classes)) {
+ $lattrs['class'] = implode(' ', $classes);
}
- (is_null($id)) ? $lattrs : $lattrs['id'] = $id;
+ if (!is_null($id)) {
+ $lattrs['id'] = $id;
+ }
$this->elementStart('li', $lattrs);
$attrs['href'] = $url;
return null;
}
- /**
- * A design for this action
- *
- * @return Design a design object to use
- */
- function getDesign()
- {
- return Design::siteDesign();
- }
-
/**
* Check the session token.
*