diff options
author | Evan Prodromou <evan@status.net> | 2009-12-22 16:44:19 -0800 |
---|---|---|
committer | Evan Prodromou <evan@status.net> | 2009-12-22 16:44:19 -0800 |
commit | f6bf9529805cd58fdd1671dd9b133bde05e8ae87 (patch) | |
tree | cf272bd1105da48f016b635db4d9c34810adcbda /lib | |
parent | f987273f118a12d443b6789c2ab59d7a4b01f678 (diff) | |
parent | 30c2e2ce83282f0bc268153d7ec465fbb5cf00ca (diff) |
Merge branch 'testing'
Diffstat (limited to 'lib')
-rw-r--r-- | lib/action.php | 32 | ||||
-rw-r--r-- | lib/api.php | 63 | ||||
-rw-r--r-- | lib/apiauth.php | 2 | ||||
-rw-r--r-- | lib/command.php | 100 | ||||
-rw-r--r-- | lib/commandinterpreter.php | 19 | ||||
-rw-r--r-- | lib/common.php | 4 | ||||
-rw-r--r-- | lib/curry.php | 36 | ||||
-rw-r--r-- | lib/default.php | 5 | ||||
-rw-r--r-- | lib/error.php | 2 | ||||
-rw-r--r-- | lib/htmloutputter.php | 100 | ||||
-rw-r--r-- | lib/language.php | 127 | ||||
-rw-r--r-- | lib/messageform.php | 3 | ||||
-rw-r--r-- | lib/noticeform.php | 3 | ||||
-rw-r--r-- | lib/noticelist.php | 85 | ||||
-rw-r--r-- | lib/oauthstore.php | 5 | ||||
-rw-r--r-- | lib/plugin.php | 29 | ||||
-rw-r--r-- | lib/profileformaction.php | 4 | ||||
-rw-r--r-- | lib/repeatform.php | 145 | ||||
-rw-r--r-- | lib/router.php | 30 | ||||
-rw-r--r-- | lib/rssaction.php | 2 | ||||
-rw-r--r-- | lib/schema.php | 18 | ||||
-rw-r--r-- | lib/subs.php | 6 | ||||
-rw-r--r-- | lib/util.php | 86 | ||||
-rw-r--r-- | lib/xmloutputter.php | 5 |
24 files changed, 800 insertions, 111 deletions
diff --git a/lib/action.php b/lib/action.php index 8ad391755..dac0e2583 100644 --- a/lib/action.php +++ b/lib/action.php @@ -68,7 +68,7 @@ class Action extends HTMLOutputter // lawsuit * @see XMLOutputter::__construct * @see HTMLOutputter::__construct */ - function __construct($output='php://output', $indent=true) + function __construct($output='php://output', $indent=null) { parent::__construct($output, $indent); } @@ -952,6 +952,36 @@ class Action extends HTMLOutputter // lawsuit } /** + * Integer value of an argument + * + * @param string $key query key we're interested in + * @param string $defValue optional default value (default null) + * @param string $maxValue optional max value (default null) + * @param string $minValue optional min value (default null) + * + * @return integer integer value + */ + + function int($key, $defValue=null, $maxValue=null, $minValue=null) + { + $arg = strtolower($this->trimmed($key)); + + if (is_null($arg) || !is_integer($arg)) { + return $defValue; + } + + if (!is_null($maxValue)) { + $arg = min($arg, $maxValue); + } + + if (!is_null($minValue)) { + $arg = max($arg, $minValue); + } + + return $arg; + } + + /** * Server error * * @param string $msg error message to display diff --git a/lib/api.php b/lib/api.php index 5a3bb5ee4..4ed49e452 100644 --- a/lib/api.php +++ b/lib/api.php @@ -53,13 +53,14 @@ if (!defined('STATUSNET')) { class ApiAction extends Action { - var $format = null; - var $user = null; - var $page = null; - var $count = null; - var $max_id = null; - var $since_id = null; - var $since = null; + var $format = null; + var $user = null; + var $auth_user = null; + var $page = null; + var $count = null; + var $max_id = null; + var $since_id = null; + var $since = null; /** * Initialization. @@ -190,13 +191,14 @@ class ApiAction extends Action $twitter_user['following'] = false; $twitter_user['notifications'] = false; - if (isset($apidata['user'])) { + if (isset($this->auth_user)) { - $twitter_user['following'] = $apidata['user']->isSubscribed($profile); + $twitter_user['following'] = $this->auth_user->isSubscribed($profile); // Notifications on? $sub = Subscription::pkeyGet(array('subscriber' => - $apidata['user']->id, 'subscribed' => $profile->id)); + $this->auth_user->id, + 'subscribed' => $profile->id)); if ($sub) { $twitter_user['notifications'] = ($sub->jabber || $sub->sms); @@ -216,6 +218,21 @@ class ApiAction extends Action function twitterStatusArray($notice, $include_user=true) { + $base = $this->twitterSimpleStatusArray($notice, $include_user); + + if (!empty($notice->repeat_of)) { + $original = Notice::staticGet('id', $notice->repeat_of); + if (!empty($original)) { + $original_array = $this->twitterSimpleStatusArray($original, $include_user); + $base['retweeted_status'] = $original_array; + } + } + + return $base; + } + + function twitterSimpleStatusArray($notice, $include_user=true) + { $profile = $notice->getProfile(); $twitter_status = array(); @@ -448,9 +465,9 @@ class ApiAction extends Action } } - function showTwitterXmlStatus($twitter_status) + function showTwitterXmlStatus($twitter_status, $tag='status') { - $this->elementStart('status'); + $this->elementStart($tag); foreach($twitter_status as $element => $value) { switch ($element) { case 'user': @@ -465,11 +482,14 @@ class ApiAction extends Action case 'geo': $this->showGeoRSS($value); break; + case 'retweeted_status': + $this->showTwitterXmlStatus($value, 'retweeted_status'); + break; default: $this->element($element, null, $value); } } - $this->elementEnd('status'); + $this->elementEnd($tag); } function showTwitterXmlGroup($twitter_group) @@ -588,7 +608,7 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null) + function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null) { $this->initDocument('rss'); @@ -602,6 +622,15 @@ class ApiAction extends Action 'href' => $suplink, 'type' => 'application/json')); } + + if (!is_null($logo)) { + $this->elementStart('image'); + $this->element('link', null, $link); + $this->element('title', null, $title); + $this->element('url', null, $logo); + $this->elementEnd('image'); + } + $this->element('description', null, $subtitle); $this->element('language', null, 'en-us'); $this->element('ttl', null, '40'); @@ -621,7 +650,7 @@ class ApiAction extends Action $this->endTwitterRss(); } - function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) + function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null) { $this->initDocument('atom'); @@ -630,6 +659,10 @@ class ApiAction extends Action $this->element('id', null, $id); $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); + if (!is_null($logo)) { + $this->element('logo',null,$logo); + } + if (!is_null($suplink)) { # For FriendFeed's SUP protocol $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', diff --git a/lib/apiauth.php b/lib/apiauth.php index 0d1613d38..7102764cb 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -53,8 +53,6 @@ require_once INSTALLDIR . '/lib/api.php'; class ApiAuthAction extends ApiAction { - var $auth_user = null; - /** * Take arguments for running, and output basic auth header if needed * diff --git a/lib/command.php b/lib/command.php index bcc551c81..67140c348 100644 --- a/lib/command.php +++ b/lib/command.php @@ -372,6 +372,7 @@ class MessageCommand extends Command } $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); if ($message) { + $message->notify(); $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); } else { $channel->error($this->user, _('Error sending direct message.')); @@ -379,6 +380,65 @@ class MessageCommand extends Command } } +class RepeatCommand extends Command +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + if(substr($this->other,0,1)=='#'){ + //repeating a specific notice_id + + $notice = Notice::staticGet(substr($this->other,1)); + if (!$notice) { + $channel->error($this->user, _('Notice with that id does not exist')); + return; + } + $recipient = $notice->getProfile(); + }else{ + //repeating a given user's last notice + + $recipient = + common_relative_profile($this->user, common_canonical_nickname($this->other)); + + if (!$recipient) { + $channel->error($this->user, _('No such user.')); + return; + } + $notice = $recipient->getCurrentNotice(); + if (!$notice) { + $channel->error($this->user, _('User has no last notice')); + return; + } + } + + if($this->user->id == $notice->profile_id) + { + $channel->error($this->user, _('Cannot repeat your own notice')); + return; + } + + if ($recipient->hasRepeated($notice->id)) { + $channel->error($this->user, _('Already repeated that notice')); + return; + } + + $repeat = $notice->repeat($this->user->id, $channel->source); + + if ($repeat) { + common_broadcast_notice($repeat); + $channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname)); + } else { + $channel->error($this->user, _('Error repeating notice.')); + } + } +} + class ReplyCommand extends Command { var $other = null; @@ -433,8 +493,9 @@ class ReplyCommand extends Command return; } - $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), 1, - $notice->id); + $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(), + array('reply_to' => $notice->id)); + if ($notice) { $channel->output($this->user, sprintf(_('Reply to %s sent'), $recipient->nickname)); } else { @@ -579,6 +640,38 @@ class OnCommand extends Command } } +class LoginCommand extends Command +{ + function execute($channel) + { + $disabled = common_config('logincommand','disabled'); + $disabled = isset($disabled) && $disabled; + if($disabled) { + $channel->error($this->user, _('Login command is disabled')); + return; + } + $login_token = Login_token::staticGet('user_id',$this->user->id); + if($login_token){ + $login_token->delete(); + } + $login_token = new Login_token(); + $login_token->user_id = $this->user->id; + $login_token->token = common_good_rand(16); + $login_token->created = common_sql_now(); + $result = $login_token->insert(); + if (!$result) { + common_log_db_error($login_token, 'INSERT', __FILE__); + $channel->error($this->user, sprintf(_('Could not create login token for %s'), + $this->user->nickname)); + return; + } + $channel->output($this->user, + sprintf(_('This link is useable only once, and is good for only 2 minutes: %s'), + common_local_url('login', + array('user_id'=>$login_token->user_id, 'token'=>$login_token->token)))); + } +} + class SubscriptionsCommand extends Command { function execute($channel) @@ -663,9 +756,12 @@ class HelpCommand extends Command "whois <nickname> - get profile info on user\n". "fav <nickname> - add user's last notice as a 'fave'\n". "fav #<notice_id> - add notice with the given id as a 'fave'\n". + "repeat #<notice_id> - repeat a notice with a given id\n". + "repeat <nickname> - repeat the last notice from user\n". "reply #<notice_id> - reply to notice with a given id\n". "reply <nickname> - reply to the last notice from user\n". "join <group> - join group\n". + "login - Get a link to login to the web interface\n". "drop <group> - leave group\n". "stats - get your stats\n". "stop - same as 'off'\n". diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php index 25f2e4b3e..c2add7299 100644 --- a/lib/commandinterpreter.php +++ b/lib/commandinterpreter.php @@ -41,6 +41,12 @@ class CommandInterpreter return null; } return new HelpCommand($user); + case 'login': + if ($arg) { + return null; + } else { + return new LoginCommand($user); + } case 'subscribers': if ($arg) { return null; @@ -163,6 +169,19 @@ class CommandInterpreter } else { return new ReplyCommand($user, $other, $extra); } + case 'repeat': + case 'rp': + case 'rt': + case 'rd': + if (!$arg) { + return null; + } + list($other, $extra) = $this->split_arg($arg); + if ($extra) { + return null; + } else { + return new RepeatCommand($user, $other); + } case 'whois': if (!$arg) { return null; diff --git a/lib/common.php b/lib/common.php index 9b3ded037..7fa1910af 100644 --- a/lib/common.php +++ b/lib/common.php @@ -20,9 +20,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } //exit with 200 response, if this is checking fancy from the installer -if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } +if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } -define('STATUSNET_VERSION', '0.9.0rc1'); +define('STATUSNET_VERSION', '0.9.0rc2'); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('STATUSNET_CODENAME', 'Stand'); diff --git a/lib/curry.php b/lib/curry.php new file mode 100644 index 000000000..6136dcdc3 --- /dev/null +++ b/lib/curry.php @@ -0,0 +1,36 @@ +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * PHP 5.3 implementation of function currying, using native closures. + * On 5.2 and lower we use the fallback implementation in util.php + * + * @param callback $fn + * @param ... any remaining arguments will be appended to call-time params + * @return callback + */ +function curry($fn) { + $extra_args = func_get_args(); + array_shift($extra_args); + return function() use ($fn, $extra_args) { + $args = func_get_args(); + return call_user_func_array($fn, + array_merge($args, $extra_args)); + }; +} diff --git a/lib/default.php b/lib/default.php index d4ef045ea..42d4623b1 100644 --- a/lib/default.php +++ b/lib/default.php @@ -53,6 +53,7 @@ $default = 'shorturllength' => 30, 'dupelimit' => 60, # default for same person saying the same thing 'textlimit' => 140, + 'indent' => true, ), 'db' => array('database' => 'YOU HAVE TO SET THIS IN config.php', @@ -74,7 +75,7 @@ $default = array('enabled' => false, 'subsystem' => 'db', # default to database, or 'stomp' 'stomp_server' => null, - 'queue_basename' => 'statusnet', + 'queue_basename' => '/queue/statusnet/', 'stomp_username' => null, 'stomp_password' => null, ), @@ -228,4 +229,6 @@ $default = array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth 'omb' => array('timeout' => 5), // HTTP request timeout in seconds when contacting remote hosts for OMB updates + 'logincommand' => + array('disabled' => true), ); diff --git a/lib/error.php b/lib/error.php index 3162cfe65..87a4d913b 100644 --- a/lib/error.php +++ b/lib/error.php @@ -50,7 +50,7 @@ class ErrorAction extends Action var $message = null; var $default = null; - function __construct($message, $code, $output='php://output', $indent=true) + function __construct($message, $code, $output='php://output', $indent=null) { parent::__construct($output, $indent); diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index d267526c8..2091c6e2c 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -67,7 +67,7 @@ class HTMLOutputter extends XMLOutputter * @param boolean $indent Whether to indent output, default true */ - function __construct($output='php://output', $indent=true) + function __construct($output='php://output', $indent=null) { parent::__construct($output, $indent); } @@ -350,14 +350,43 @@ class HTMLOutputter extends XMLOutputter */ function script($src, $type='text/javascript') { - $url = parse_url($src); - if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) - { - $src = common_path($src) . '?version=' . STATUSNET_VERSION; + if(Event::handle('StartScriptElement', array($this,&$src,&$type))) { + $url = parse_url($src); + if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) + { + $src = common_path($src) . '?version=' . STATUSNET_VERSION; + } + $this->element('script', array('type' => $type, + 'src' => $src), + ' '); + Event::handle('EndScriptElement', array($this,$src,$type)); + } + } + + /** + * output a script (almost always javascript) tag with inline + * code. + * + * @param string $code code to put in the script tag + * @param string $type 'type' attribute value of the tag + * + * @return void + */ + + function inlineScript($code, $type='text/javascript') + { + if(Event::handle('StartInlineScriptElement', array($this,&$code,&$type))) { + $this->elementStart('script', array('type' => $type)); + if($type == 'text/javascript') { + $this->raw('/*<![CDATA[*/ '); // XHTML compat + } + $this->raw($code); + if($type == 'text/javascript') { + $this->raw(' /*]]>*/'); // XHTML compat + } + $this->elementEnd('script'); + Event::handle('EndInlineScriptElement', array($this,$code,$type)); } - $this->element('script', array('type' => $type, - 'src' => $src), - ' '); } /** @@ -371,19 +400,44 @@ class HTMLOutputter extends XMLOutputter */ function cssLink($src,$theme=null,$media=null) { - $url = parse_url($src); - if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) - { - if(file_exists(Theme::file($src,$theme))){ - $src = Theme::path($src, $theme) . '?version=' . STATUSNET_VERSION; - }else{ - $src = common_path($src); + if(Event::handle('StartCssLinkElement', array($this,&$src,&$theme,&$media))) { + $url = parse_url($src); + if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) + { + if(file_exists(Theme::file($src,$theme))){ + $src = Theme::path($src, $theme); + }else{ + $src = common_path($src); + } + $src.= '?version=' . STATUSNET_VERSION; } + $this->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => $src, + 'media' => $media)); + Event::handle('EndCssLinkElement', array($this,$src,$theme,$media)); + } + } + + /** + * output a style (almost always css) tag with inline + * code. + * + * @param string $code code to put in the style tag + * @param string $type 'type' attribute value of the tag + * @param string $media 'media' attribute value of the tag + * + * @return void + */ + + function style($code, $type = 'text/css', $media = null) + { + if(Event::handle('StartStyleElement', array($this,&$code,&$type,&$media))) { + $this->elementStart('style', array('type' => $type, 'media' => $media)); + $this->raw($code); + $this->elementEnd('style'); + Event::handle('EndStyleElement', array($this,$code,$type,$media)); } - $this->element('link', array('rel' => 'stylesheet', - 'type' => 'text/css', - 'href' => $src, - 'media' => $media)); } /** @@ -414,7 +468,6 @@ class HTMLOutputter extends XMLOutputter } } - /** * Internal script to autofocus the given element on page onload. * @@ -425,13 +478,10 @@ class HTMLOutputter extends XMLOutputter */ function autofocus($id) { - $this->elementStart('script', array('type' => 'text/javascript')); - $this->raw('/*<![CDATA[*/'. + $this->inlineScript( ' $(document).ready(function() {'. ' var el = $("#' . $id . '");'. ' if (el.length) { el.focus(); }'. - ' });'. - ' /*]]>*/'); - $this->elementEnd('script'); + ' });'); } } diff --git a/lib/language.php b/lib/language.php index 4fc45bafe..d8f529201 100644 --- a/lib/language.php +++ b/lib/language.php @@ -36,6 +36,33 @@ if (!function_exists('gettext')) { require_once("php-gettext/gettext.inc"); } + +if (!function_exists('dpgettext')) { + /** + * Context-aware dgettext wrapper; use when messages in different contexts + * won't be distinguished from the English source but need different translations. + * The context string will appear as msgctxt in the .po files. + * + * Not currently exposed in PHP's gettext module; implemented to be compat + * with gettext.h's macros. + * + * @param string $domain domain identifier, or null for default domain + * @param string $context context identifier, should be some key like "menu|file" + * @param string $msgid English source text + * @return string original or translated message + */ + function dpgettext($domain, $context, $msg) + { + $msgid = $context . "\004" . $msg; + $out = dcgettext($domain, $msgid, LC_MESSAGES); + if ($out == $msgid) { + return $msg; + } else { + return $out; + } + } +} + if (!function_exists('pgettext')) { /** * Context-aware gettext wrapper; use when messages in different contexts @@ -51,8 +78,30 @@ if (!function_exists('pgettext')) { */ function pgettext($context, $msg) { + return dpgettext(textdomain(NULL), $context, $msg); + } +} + +if (!function_exists('dnpgettext')) { + /** + * Context-aware dngettext wrapper; use when messages in different contexts + * won't be distinguished from the English source but need different translations. + * The context string will appear as msgctxt in the .po files. + * + * Not currently exposed in PHP's gettext module; implemented to be compat + * with gettext.h's macros. + * + * @param string $domain domain identifier, or null for default domain + * @param string $context context identifier, should be some key like "menu|file" + * @param string $msg singular English source text + * @param string $plural plural English source text + * @param int $n number of items to control plural selection + * @return string original or translated message + */ + function dnpgettext($domain, $context, $msg, $plural, $n) + { $msgid = $context . "\004" . $msg; - $out = dcgettext(textdomain(NULL), $msgid, LC_MESSAGES); + $out = dcngettext($domain, $msgid, $plural, $n, LC_MESSAGES); if ($out == $msgid) { return $msg; } else { @@ -78,14 +127,78 @@ if (!function_exists('npgettext')) { */ function npgettext($context, $msg, $plural, $n) { - $msgid = $context . "\004" . $msg; - $out = dcngettext(textdomain(NULL), $msgid, $plural, $n, LC_MESSAGES); - if ($out == $msgid) { - return $msg; + return dnpgettext(textdomain(NULL), $msgid, $plural, $n, LC_MESSAGES); + } +} + +/** + * Shortcut for *gettext functions with smart domain detection. + * + * If calling from a plugin, this function checks which plugin was + * being called from and uses that as text domain, which will have + * been set up during plugin initialization. + * + * Also handles plurals and contexts depending on what parameters + * are passed to it: + * + * gettext -> _m($msg) + * ngettext -> _m($msg1, $msg2, $n) + * pgettext -> _m($ctx, $msg) + * npgettext -> _m($ctx, $msg1, $msg2, $n) + * + * @fixme may not work properly in eval'd code + * + * @param string $msg + * @return string + */ +function _m($msg/*, ...*/) +{ + $domain = _mdomain(debug_backtrace()); + $args = func_get_args(); + switch(count($args)) { + case 1: return dgettext($domain, $msg); + case 2: return dpgettext($domain, $args[0], $args[1]); + case 3: return dngettext($domain, $args[0], $args[1], $args[2]); + case 4: return dnpgettext($domain, $args[0], $args[1], $args[2], $args[3]); + default: throw new Exception("Bad parameter count to _m()"); + } +} + +/** + * Looks for which plugin we've been called from to set the gettext domain. + * + * @param array $backtrace debug_backtrace() output + * @return string + * @private + * @fixme could explode if SN is under a 'plugins' folder or share name. + */ +function _mdomain($backtrace) +{ + /* + 0 => + array + 'file' => string '/var/www/mublog/plugins/FeedSub/FeedSubPlugin.php' (length=49) + 'line' => int 77 + 'function' => string '_m' (length=2) + 'args' => + array + 0 => &string 'Feeds' (length=5) + */ + static $cached; + $path = $backtrace[0]['file']; + if (!isset($cached[$path])) { + if (DIRECTORY_SEPARATOR !== '/') { + $path = strtr($path, DIRECTORY_SEPARATOR, '/'); + } + $cut = strpos($path, '/plugins/') + 9; + $cut2 = strpos($path, '/', $cut); + if ($cut && $cut2) { + $cached[$path] = substr($path, $cut, $cut2 - $cut); } else { - return $out; + return null; } } + return $cached[$path]; } @@ -159,6 +272,7 @@ function get_nice_language_list() function get_all_languages() { return array( 'ar' => array('q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'), + 'arz' => array('q' => 0.8, 'lang' => 'arz', 'name' => 'Egyptian Spoken Arabic', 'direction' => 'rtl'), 'bg' => array('q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'), 'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'), 'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'), @@ -173,6 +287,7 @@ function get_all_languages() { 'ga' => array('q' => 0.5, 'lang' => 'ga', 'name' => 'Galician', 'direction' => 'ltr'), 'he' => array('q' => 0.5, 'lang' => 'he', 'name' => 'Hebrew', 'direction' => 'rtl'), 'hsb' => array('q' => 0.8, 'lang' => 'hsb', 'name' => 'Upper Sorbian', 'direction' => 'ltr'), + 'ia' => array('q' => 0.8, 'lang' => 'ia', 'name' => 'Interlingua', 'direction' => 'ltr'), 'is' => array('q' => 0.1, 'lang' => 'is', 'name' => 'Icelandic', 'direction' => 'ltr'), 'it' => array('q' => 1, 'lang' => 'it', 'name' => 'Italian', 'direction' => 'ltr'), 'jp' => array('q' => 0.5, 'lang' => 'ja', 'name' => 'Japanese', 'direction' => 'ltr'), diff --git a/lib/messageform.php b/lib/messageform.php index b034be312..0c568e1bd 100644 --- a/lib/messageform.php +++ b/lib/messageform.php @@ -154,9 +154,6 @@ class MessageForm extends Form $contentLimit = Message::maxContent(); - $this->out->element('script', array('type' => 'text/javascript'), - 'maxLength = ' . $contentLimit . ';'); - if ($contentLimit > 0) { $this->out->elementStart('dl', 'form_note'); $this->out->element('dt', null, _('Available characters')); diff --git a/lib/noticeform.php b/lib/noticeform.php index ec8624597..593a1e932 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -178,9 +178,6 @@ class NoticeForm extends Form $contentLimit = Notice::maxContent(); - $this->out->element('script', array('type' => 'text/javascript'), - 'maxLength = ' . $contentLimit . ';'); - if ($contentLimit > 0) { $this->out->elementStart('dl', 'form_note'); $this->out->element('dt', null, _('Available characters')); diff --git a/lib/noticelist.php b/lib/noticelist.php index 21cec528f..4c11ceed6 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -147,6 +147,10 @@ class NoticeListItem extends Widget var $notice = null; + /** The notice that was repeated. */ + + var $repeat = null; + /** The profile of the author of the notice, extracted once for convenience. */ var $profile = null; @@ -162,8 +166,18 @@ class NoticeListItem extends Widget function __construct($notice, $out=null) { parent::__construct($out); - $this->notice = $notice; - $this->profile = $notice->getProfile(); + if (!empty($notice->repeat_of)) { + $original = Notice::staticGet('id', $notice->repeat_of); + if (empty($original)) { // could have been deleted + $this->notice = $notice; + } else { + $this->notice = $original; + $this->repeat = $notice; + } + } else { + $this->notice = $notice; + } + $this->profile = $this->notice->getProfile(); } /** @@ -202,6 +216,7 @@ class NoticeListItem extends Widget $this->showNoticeSource(); $this->showNoticeLocation(); $this->showContext(); + $this->showRepeat(); $this->out->elementEnd('div'); } @@ -212,6 +227,7 @@ class NoticeListItem extends Widget $this->out->elementStart('div', 'notice-options'); $this->showFaveForm(); $this->showReplyLink(); + $this->showRepeatForm(); $this->showDeleteLink(); $this->out->elementEnd('div'); } @@ -227,8 +243,9 @@ class NoticeListItem extends Widget { // XXX: RDFa // TODO: add notice_type class e.g., notice_video, notice_image + $id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id; $this->out->elementStart('li', array('class' => 'hentry notice', - 'id' => 'notice-' . $this->notice->id)); + 'id' => 'notice-' . $id)); } /** @@ -508,6 +525,40 @@ class NoticeListItem extends Widget } /** + * show a link to the author of repeat + * + * @return void + */ + + function showRepeat() + { + if (!empty($this->repeat)) { + + $repeater = Profile::staticGet('id', $this->repeat->profile_id); + + $attrs = array('href' => $repeater->profileurl, + 'class' => 'url'); + + if (!empty($repeater->fullname)) { + $attrs['title'] = $repeater->fullname . ' (' . $repeater->nickname . ')'; + } + + $this->out->elementStart('span', 'repeat vcard'); + + $this->out->raw(_('Repeated by')); + + $avatar = $repeater->getAvatar(AVATAR_MINI_SIZE); + + $this->out->elementStart('a', $attrs); + + $this->out->element('span', 'nickname', $repeater->nickname); + $this->out->elementEnd('a'); + + $this->out->elementEnd('span'); + } + } + + /** * show a link to reply to the current notice * * Should either do the reply in the current notice form (if available), or @@ -540,11 +591,13 @@ class NoticeListItem extends Widget { $user = common_current_user(); + $todel = (empty($this->repeat)) ? $this->notice : $this->repeat; + if (!empty($user) && - ($this->notice->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) { + ($todel->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) { $deleteurl = common_local_url('deletenotice', - array('notice' => $this->notice->id)); + array('notice' => $todel->id)); $this->out->element('a', array('href' => $deleteurl, 'class' => 'notice_delete', 'title' => _('Delete this notice')), _('Delete')); @@ -552,6 +605,28 @@ class NoticeListItem extends Widget } /** + * show the form to repeat a notice + * + * @return void + */ + + function showRepeatForm() + { + $user = common_current_user(); + if ($user && $user->id != $this->notice->profile_id) { + $profile = $user->getProfile(); + if ($profile->hasRepeated($this->notice->id)) { + $this->out->element('span', array('class' => 'repeated', + 'title' => _('Notice repeated')), + _('Repeated')); + } else { + $rf = new RepeatForm($this->out, $this->notice); + $rf->show(); + } + } + } + + /** * finish the notice * * Close the last elements in the notice list item diff --git a/lib/oauthstore.php b/lib/oauthstore.php index e34bf8a5e..df63cc151 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -359,9 +359,8 @@ class StatusNetOAuthDataStore extends OAuthDataStore $notice = Notice::saveNew($author->id, $omb_notice->getContent(), 'omb', - false, - null, - $omb_notice->getIdentifierURI()); + array('is_local' => Notice::REMOTE_OMB, + 'uri' => $omb_notice->getIdentifierURI())); common_broadcast_notice($notice, true); } diff --git a/lib/plugin.php b/lib/plugin.php index 87d7be5a7..de7313e59 100644 --- a/lib/plugin.php +++ b/lib/plugin.php @@ -65,6 +65,8 @@ class Plugin Event::addHandler(mb_substr($method, 2), array($this, $method)); } } + + $this->setupGettext(); } function initialize() @@ -76,4 +78,31 @@ class Plugin { return true; } + + /** + * Checks if this plugin has localization that needs to be set up. + * Gettext localizations can be called via the _m() helper function. + */ + protected function setupGettext() + { + $class = get_class($this); + if (substr($class, -6) == 'Plugin') { + $name = substr($class, 0, -6); + $path = INSTALLDIR . "/plugins/$name/locale"; + if (file_exists($path) && is_dir($path)) { + bindtextdomain($name, $path); + } + } + } + + protected function log($level, $msg) + { + common_log($level, get_class($this) . ': '.$msg); + } + + protected function debug($msg) + { + $this->log(LOG_DEBUG, $msg); + } } + diff --git a/lib/profileformaction.php b/lib/profileformaction.php index 8cb5f6a93..8a934666e 100644 --- a/lib/profileformaction.php +++ b/lib/profileformaction.php @@ -120,7 +120,7 @@ class ProfileFormAction extends Action if ($action) { common_redirect(common_local_url($action, $args), 303); } else { - $this->clientError(_("No return-to arguments")); + $this->clientError(_("No return-to arguments.")); } } @@ -134,6 +134,6 @@ class ProfileFormAction extends Action function handlePost() { - $this->serverError(_("unimplemented method")); + $this->serverError(_("Unimplemented method.")); } } diff --git a/lib/repeatform.php b/lib/repeatform.php new file mode 100644 index 000000000..50e5d6dbe --- /dev/null +++ b/lib/repeatform.php @@ -0,0 +1,145 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Form for repeating a notice + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Form for repeating a notice + * + * @category Form + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class RepeatForm extends Form +{ + /** + * Notice to repeat + */ + + var $notice = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to repeat + */ + + function __construct($out=null, $notice=null) + { + parent::__construct($out); + + $this->notice = $notice; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'repeat-' . $this->notice->id; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('repeat'); + } + + /** + * Include a session token for CSRF protection + * + * @return void + */ + + function sessionToken() + { + $this->out->hidden('token-' . $this->notice->id, + common_session_token()); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _('Repeat this notice')); + } + + /** + * Data elements + * + * @return void + */ + + function formData() + { + $this->out->hidden('notice-n'.$this->notice->id, + $this->notice->id, + 'notice'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('repeat-submit-' . $this->notice->id, + _('Repeat'), 'submit', null, _('Repeat this notice')); + } + + /** + * Class of the form. + * + * @return string the form's class + */ + + function formClass() + { + return 'form_repeat'; + } +} diff --git a/lib/router.php b/lib/router.php index 1a090861e..474e05996 100644 --- a/lib/router.php +++ b/lib/router.php @@ -88,6 +88,8 @@ class Router $m->connect('doc/:title', array('action' => 'doc')); + $m->connect('main/login?user_id=:user_id&token=:token', array('action'=>'login'), array('user_id'=> '[0-9]+', 'token'=>'.+')); + // main stuff is repetitive $main = array('login', 'logout', 'register', 'subscribe', @@ -97,6 +99,7 @@ class Router 'groupblock', 'groupunblock', 'sandbox', 'unsandbox', 'silence', 'unsilence', + 'repeat', 'deleteuser'); foreach ($main as $a) { @@ -280,12 +283,13 @@ class Router array('action' => 'ApiTimelineFriends', 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/home_timeline.:format', - array('action' => 'ApiTimelineFriends', + array('action' => 'ApiTimelineHome', 'format' => '(xml|json|rss|atom)')); $m->connect('api/statuses/home_timeline/:id.:format', - array('action' => 'ApiTimelineFriends', + array('action' => 'ApiTimelineHome', 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); @@ -316,6 +320,18 @@ class Router 'id' => '[a-zA-Z0-9]+', 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/retweeted_by_me.:format', + array('action' => 'ApiTimelineRetweetedByMe', + 'format' => '(xml|json|atom)')); + + $m->connect('api/statuses/retweeted_to_me.:format', + array('action' => 'ApiTimelineRetweetedToMe', + 'format' => '(xml|json|atom)')); + + $m->connect('api/statuses/retweets_of_me.:format', + array('action' => 'ApiTimelineRetweetsOfMe', + 'format' => '(xml|json|atom)')); + $m->connect('api/statuses/friends.:format', array('action' => 'ApiUserFriends', 'format' => '(xml|json)')); @@ -356,6 +372,16 @@ class Router 'id' => '[0-9]+', 'format' => '(xml|json)')); + $m->connect('api/statuses/retweet/:id.:format', + array('action' => 'ApiStatusesRetweet', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/retweets/:id.:format', + array('action' => 'ApiStatusesRetweets', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + // users $m->connect('api/users/show.:format', diff --git a/lib/rssaction.php b/lib/rssaction.php index d591c99ed..62e3f21b6 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -52,7 +52,7 @@ class Rss10Action extends Action * @see Action::__construct */ - function __construct($output='php://output', $indent=true) + function __construct($output='php://output', $indent=null) { parent::__construct($output, $indent); } diff --git a/lib/schema.php b/lib/schema.php index df7cb65f5..a8ba91b87 100644 --- a/lib/schema.php +++ b/lib/schema.php @@ -94,7 +94,7 @@ class Schema public function getTableDef($name) { - $res =& $this->conn->query('DESCRIBE ' . $name); + $res = $this->conn->query('DESCRIBE ' . $name); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -213,7 +213,7 @@ class Schema $sql .= "); "; - $res =& $this->conn->query($sql); + $res = $this->conn->query($sql); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -234,7 +234,7 @@ class Schema public function dropTable($name) { - $res =& $this->conn->query("DROP TABLE $name"); + $res = $this->conn->query("DROP TABLE $name"); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -269,7 +269,7 @@ class Schema $name = "$table_".implode("_", $columnNames)."_idx"; } - $res =& $this->conn->query("ALTER TABLE $table ". + $res = $this->conn->query("ALTER TABLE $table ". "ADD INDEX $name (". implode(",", $columnNames).")"); @@ -291,7 +291,7 @@ class Schema public function dropIndex($table, $name) { - $res =& $this->conn->query("ALTER TABLE $table DROP INDEX $name"); + $res = $this->conn->query("ALTER TABLE $table DROP INDEX $name"); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -314,7 +314,7 @@ class Schema { $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef); - $res =& $this->conn->query($sql); + $res = $this->conn->query($sql); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -339,7 +339,7 @@ class Schema $sql = "ALTER TABLE $table MODIFY COLUMN " . $this->_columnSql($columndef); - $res =& $this->conn->query($sql); + $res = $this->conn->query($sql); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -363,7 +363,7 @@ class Schema { $sql = "ALTER TABLE $table DROP COLUMN $columnName"; - $res =& $this->conn->query($sql); + $res = $this->conn->query($sql); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); @@ -446,7 +446,7 @@ class Schema $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase); - $res =& $this->conn->query($sql); + $res = $this->conn->query($sql); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); diff --git a/lib/subs.php b/lib/subs.php index 2fc3160de..4b6b03967 100644 --- a/lib/subs.php +++ b/lib/subs.php @@ -127,6 +127,12 @@ function subs_unsubscribe_to($user, $other) if (!$user->isSubscribed($other)) return _('Not subscribed!'); + // Don't allow deleting self subs + + if ($user->id == $other->id) { + return _('Couldn\'t delete self-subscription.'); + } + $sub = DB_DataObject::factory('subscription'); $sub->subscriber = $user->id; diff --git a/lib/util.php b/lib/util.php index 5d20ed82d..ed81aeba1 100644 --- a/lib/util.php +++ b/lib/util.php @@ -135,7 +135,7 @@ function common_check_user($nickname, $password) if (0 == strcmp(common_munge_password($password, $user->id), $user->password)) { //internal checking passed - $authenticatedUser =& $user; + $authenticatedUser = $user; } } } @@ -531,19 +531,23 @@ function callback_helper($matches, $callback, $notice_id) { return substr($matches[0],0,$left) . $result . substr($matches[0],$right); } -function curry($fn) { - //TODO switch to a PHP 5.3 function closure based approach if PHP 5.3 is used - $args = func_get_args(); - array_shift($args); - $id = uniqid('_partial'); - $GLOBALS[$id] = array($fn, $args); - return create_function('', - '$args = func_get_args(); '. - 'return call_user_func_array('. - '$GLOBALS["'.$id.'"][0],'. - 'array_merge('. - '$args,'. - '$GLOBALS["'.$id.'"][1]));'); +if (version_compare(PHP_VERSION, '5.3.0', 'ge')) { + // lambda implementation in a separate file; PHP 5.2 won't parse it. + require_once INSTALLDIR . "/lib/curry.php"; +} else { + function curry($fn) { + $args = func_get_args(); + array_shift($args); + $id = uniqid('_partial'); + $GLOBALS[$id] = array($fn, $args); + return create_function('', + '$args = func_get_args(); '. + 'return call_user_func_array('. + '$GLOBALS["'.$id.'"][0],'. + 'array_merge('. + '$args,'. + '$GLOBALS["'.$id.'"][1]));'); + } } function common_linkify($url) { @@ -1078,18 +1082,21 @@ function common_request_id() function common_log($priority, $msg, $filename=null) { - $msg = '[' . common_request_id() . '] ' . $msg; - $logfile = common_config('site', 'logfile'); - if ($logfile) { - $log = fopen($logfile, "a"); - if ($log) { - $output = common_log_line($priority, $msg); - fwrite($log, $output); - fclose($log); + if(Event::handle('StartLog', array(&$priority, &$msg, &$filename))){ + $msg = '[' . common_request_id() . '] ' . $msg; + $logfile = common_config('site', 'logfile'); + if ($logfile) { + $log = fopen($logfile, "a"); + if ($log) { + $output = common_log_line($priority, $msg); + fwrite($log, $output); + fclose($log); + } + } else { + common_ensure_syslog(); + syslog($priority, $msg); } - } else { - common_ensure_syslog(); - syslog($priority, $msg); + Event::handle('EndLog', array($priority, $msg, $filename)); } } @@ -1245,8 +1252,12 @@ function common_copy_args($from) return $to; } -// Neutralise the evil effects of magic_quotes_gpc in the current request. -// This is used before handing a request off to OAuthRequest::from_request. +/** + * Neutralise the evil effects of magic_quotes_gpc in the current request. + * This is used before handing a request off to OAuthRequest::from_request. + * @fixme Doesn't consider vars other than _POST and _GET? + * @fixme Can't be undone and could corrupt data if run twice. + */ function common_remove_magic_from_request() { if(get_magic_quotes_gpc()) { @@ -1448,6 +1459,17 @@ function common_database_tablename($tablename) return $tablename; } +/** + * Shorten a URL with the current user's configured shortening service, + * or ur1.ca if configured, or not at all if no shortening is set up. + * Length is not considered. + * + * @param string $long_url + * @return string may return the original URL if shortening failed + * + * @fixme provide a way to specify a particular shortener + * @fixme provide a way to specify to use a given user's shortening preferences + */ function common_shorten_url($long_url) { $user = common_current_user(); @@ -1468,6 +1490,16 @@ function common_shorten_url($long_url) } } +/** + * @return mixed array($proxy, $ip) for web requests; proxy may be null + * null if not a web request + * + * @fixme X-Forwarded-For can be chained by multiple proxies; + we should parse the list and provide a cleaner array + * @fixme X-Forwarded-For can be forged by clients; only use them if trusted + * @fixme X_Forwarded_For headers will override X-Forwarded-For read through $_SERVER; + * use function to get exact request headers from Apache if possible. + */ function common_client_ip() { if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) { diff --git a/lib/xmloutputter.php b/lib/xmloutputter.php index 5f06e491d..15b18e7d9 100644 --- a/lib/xmloutputter.php +++ b/lib/xmloutputter.php @@ -67,10 +67,13 @@ class XMLOutputter * @param boolean $indent Whether to indent output, default true */ - function __construct($output='php://output', $indent=true) + function __construct($output='php://output', $indent=null) { $this->xw = new XMLWriter(); $this->xw->openURI($output); + if(is_null($indent)) { + $indent = common_config('site', 'indent'); + } $this->xw->setIndent($indent); } |