diff options
46 files changed, 785 insertions, 219 deletions
diff --git a/EVENTS.txt b/EVENTS.txt index 2b16d43c0..fa25aabcd 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -170,12 +170,6 @@ StartShowBody: called before showing the <body> element and children EndShowBody: called after showing the <body> element (and </body>) - $action: action object being shown -StartHeadChildren: called before showing the children of <head> element (after <head> tag) -- $action: action object being shown - -EndHeadChildren: called after showing the children of <head> element (before </head>) -- $action: action object being shown - StartPersonalGroupNav: beginning of personal group nav menu - $action: action object being shown @@ -271,3 +265,9 @@ GetValidDaemons: Just before determining which daemons to run HandleQueuedNotice: Handle a queued notice at queue time (or immediately if no queue) - &$notice: notice to handle + +StartShowHeadElements: Right after the <head> tag +- $action: the current action + +EndShowHeadElements: Right before the </head> tag; put <script>s here if you need them in <head> +- $action: the current action diff --git a/actions/allrss.php b/actions/allrss.php index 57efb73f0..28b1be27d 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -68,6 +68,7 @@ class AllrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index 02a684b38..ded419dd7 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -362,13 +362,13 @@ class AvatarsettingsAction extends AccountSettingsAction $profile = $user->getProfile(); $avatar = $profile->getOriginalAvatar(); - $avatar->delete(); + if($avatar) $avatar->delete(); $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - $avatar->delete(); + if($avatar) $avatar->delete(); $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); - $avatar->delete(); + if($avatar) $avatar->delete(); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); - $avatar->delete(); + if($avatar) $avatar->delete(); $this->showForm(_('Avatar deleted.'), true); } diff --git a/actions/favoritesrss.php b/actions/favoritesrss.php index 2d5ce9854..62f06e841 100644 --- a/actions/favoritesrss.php +++ b/actions/favoritesrss.php @@ -50,11 +50,11 @@ require_once INSTALLDIR.'/lib/rssaction.php'; */ class FavoritesrssAction extends Rss10Action { - + /** The user whose favorites to display */ - + var $user = null; - + /** * Find the user to display by supplied nickname * @@ -66,7 +66,7 @@ class FavoritesrssAction extends Rss10Action function prepare($args) { parent::prepare($args); - + $nickname = $this->trimmed('nickname'); $this->user = User::staticGet('nickname', $nickname); @@ -74,10 +74,11 @@ class FavoritesrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } - + /** * Get notices * diff --git a/actions/grouprss.php b/actions/grouprss.php index 70c1ded48..6a6b55e78 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -104,6 +104,7 @@ class groupRssAction extends Rss10Action return false; } + $this->notices = $this->getNotices($this->limit); return true; } diff --git a/actions/newnotice.php b/actions/newnotice.php index 4c6372c2b..23ec2a1b5 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -433,13 +433,14 @@ class NewnoticeAction extends Action $content = $this->trimmed('status_textarea'); if (!$content) { $replyto = $this->trimmed('replyto'); + $inreplyto = $this->trimmed('inreplyto'); $profile = Profile::staticGet('nickname', $replyto); if ($profile) { $content = '@' . $profile->nickname . ' '; } } - $notice_form = new NoticeForm($this, '', $content); + $notice_form = new NoticeForm($this, '', $content, null, $inreplyto); $notice_form->show(); } diff --git a/actions/publicrss.php b/actions/publicrss.php index 593888b9f..0c5d061cb 100644 --- a/actions/publicrss.php +++ b/actions/publicrss.php @@ -50,8 +50,22 @@ require_once INSTALLDIR.'/lib/rssaction.php'; class PublicrssAction extends Rss10Action { /** + * Read arguments and initialize members + * + * @param array $args Arguments from $_REQUEST + * @return boolean success + */ + + function prepare($args) + { + parent::prepare($args); + $this->notices = $this->getNotices($this->limit); + return true; + } + + /** * Initialization. - * + * * @return boolean true */ function init() @@ -73,7 +87,7 @@ class PublicrssAction extends Rss10Action while ($notice->fetch()) { $notices[] = clone($notice); } - + return $notices; } diff --git a/actions/repliesrss.php b/actions/repliesrss.php index c71c9226f..76aae21ad 100644 --- a/actions/repliesrss.php +++ b/actions/repliesrss.php @@ -38,6 +38,7 @@ class RepliesrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } diff --git a/actions/showstream.php b/actions/showstream.php index 2e9679fae..cdac4f47b 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -378,8 +378,13 @@ class ShowstreamAction extends ProfileAction $this->showEmptyListMessage(); } + $args = array('nickname' => $this->user->nickname); + if (!empty($this->tag)) + { + $args['tag'] = $this->tag; + } $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page, - 'showstream', array('nickname' => $this->user->nickname)); + 'showstream', $args); } function showAnonymousMessage() diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php index 4deb1b764..493144e77 100644 --- a/actions/twitapigroups.php +++ b/actions/twitapigroups.php @@ -293,6 +293,105 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; } } + function join($args, $apidata) + { + parent::handle($args); + + common_debug("in groups api action"); + + $this->auth_user = $apidata['user']; + $group = $this->get_group($apidata['api_arg'], $apidata); + + if (empty($group)) { + $this->clientError('Not Found', 404, $apidata['content-type']); + return false; + } + + if($this->auth_user->isMember($group)){ + $this->clientError(_('You are already a member of that group'), $code = 403); + return false; + } + + if (Group_block::isBlocked($group, $this->auth_user->getProfile())) { + $this->clientError(_('You have been blocked from that group by the admin.'), 403); + return false; + } + + $member = new Group_member(); + + $member->group_id = $group->id; + $member->profile_id = $this->auth_user->id; + $member->created = common_sql_now(); + + $result = $member->insert(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + $this->serverError(sprintf(_('Could not join user %s to group %s'), + $this->auth_user->nickname, $group->nickname)); + } + + switch($apidata['content-type']) { + case 'xml': + $this->show_single_xml_group($group); + break; + case 'json': + $this->show_single_json_group($group); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + } + } + + function leave($args, $apidata) + { + parent::handle($args); + + common_debug("in groups api action"); + + $this->auth_user = $apidata['user']; + $group = $this->get_group($apidata['api_arg'], $apidata); + + if (empty($group)) { + $this->clientError('Not Found', 404, $apidata['content-type']); + return false; + } + + if(! $this->auth_user->isMember($group)){ + $this->clientError(_('You are not a member of that group'), $code = 403); + return false; + } + + $member = new Group_member(); + + $member->group_id = $group->id; + $member->profile_id = $this->auth_user->id; + + if (!$member->find(true)) { + $this->serverError(_('Could not find membership record.')); + return; + } + + $result = $member->delete(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + $this->serverError(sprintf(_('Could not remove user %s to group %s'), + $this->auth_user->nickname, $group->nickname)); + } + + switch($apidata['content-type']) { + case 'xml': + $this->show_single_xml_group($group); + break; + case 'json': + $this->show_single_json_group($group); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + } + } + function is_member($args, $apidata) { parent::handle($args); @@ -326,4 +425,29 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; $this->clientError(_('API method not found!'), $code = 404); } } + + function create($args, $apidata) + { + die("todo"); + } + + function update($args, $apidata) + { + die("todo"); + } + + function update_group_logo($args, $apidata) + { + die("todo"); + } + + function destroy($args, $apidata) + { + die("todo"); + } + + function tag($args, $apidata) + { + die("todo"); + } } diff --git a/actions/userrss.php b/actions/userrss.php index fa6d588cd..19e610551 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -25,7 +25,6 @@ require_once(INSTALLDIR.'/lib/rssaction.php'); class UserrssAction extends Rss10Action { - var $user = null; var $tag = null; function prepare($args) @@ -39,6 +38,7 @@ class UserrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } @@ -64,9 +64,8 @@ class UserrssAction extends Rss10Action function getNotices($limit=0) { - $user = $this->user; - + if (is_null($user)) { return null; } diff --git a/classes/Notice.php b/classes/Notice.php index 2138e0561..f3fa9af78 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -909,7 +909,8 @@ class Notice extends Memcached_DataObject $qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') "; $cnt++; if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { - Notice_inbox::gc($id); + // FIXME: Causes lag in replicated servers + // Notice_inbox::gc($id); } if ($cnt >= MAX_BOXCARS) { $inbox = new Notice_inbox(); diff --git a/classes/User.php b/classes/User.php index 11cb4f08b..5e74c7fde 100644 --- a/classes/User.php +++ b/classes/User.php @@ -117,11 +117,15 @@ class User extends Memcached_DataObject function allowed_nickname($nickname) { // XXX: should already be validated for size, content, etc. - static $blacklist = array('rss', 'xrds', 'doc', 'main', - 'settings', 'notice', 'user', - 'search', 'avatar', 'tag', 'tags', - 'api', 'message', 'group', 'groups', - 'local'); + + $blacklist = array(); + + //all directory and file names should be blacklisted + $d = dir(INSTALLDIR); + while (false !== ($entry = $d->read())) { + $blacklist[]=$entry; + } + $d->close(); $merged = array_merge($blacklist, common_config('nickname', 'blacklist')); return !in_array($nickname, $merged); } diff --git a/db/sms_carrier.sql b/db/sms_carrier.sql index 055606f58..0e94df296 100644 --- a/db/sms_carrier.sql +++ b/db/sms_carrier.sql @@ -61,4 +61,5 @@ VALUES (100113, 'T-Mobile Germany', '%s@t-mobile-sms.de', now()), (100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()), (100115, 'E-Plus', '%s@smsmail.eplus.de', now()), - (100116, 'Cellular South', '%s@csouth1.com', now()); + (100116, 'Cellular South', '%s@csouth1.com', now()), + (100117, 'ChinaMobile (139)', '%s@139.com', now()); diff --git a/doc-src/bookmarklet b/doc-src/bookmarklet index 6cd2c08f9..e5ded7702 100644 --- a/doc-src/bookmarklet +++ b/doc-src/bookmarklet @@ -2,6 +2,4 @@ A bookmarklet is a small piece of javascript code used as a bookmark. This one w Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy. -<MTMarkdownOptions output='raw'> -<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a> -</MTMarkdownOptions> +<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a> diff --git a/doc-src/im b/doc-src/im index c722a4e2c..631f6d9bb 100644 --- a/doc-src/im +++ b/doc-src/im @@ -37,10 +37,10 @@ currently-implemented commands: * **help**: Show this help. List available Jabber/XMPP commands * **follow <nickname>**: Subscribe to <nickname> * **sub <nickname>**: Same as follow -* **leave <nickname>**: Subscribe to <nickname> +* **leave <nickname>**: Unsubscribe from <nickname> * **unsub <nickname>**: Same as leave * **d <nickname> <text>**: Send direct message to <nickname> with message body <text> * **get <nickname>**: Get last notice from <nickname> * **last <nickname>**: Same as 'get' * **whois <nickname>**: Get Profile info on <nickname> -* **fav <nickname>**: Add user's last notice as a favorite
\ No newline at end of file +* **fav <nickname>**: Add user's last notice as a favorite diff --git a/extlib/Auth/OpenID/BigMath.php b/extlib/Auth/OpenID/BigMath.php index 45104947d..b5fc627a0 100644 --- a/extlib/Auth/OpenID/BigMath.php +++ b/extlib/Auth/OpenID/BigMath.php @@ -376,7 +376,7 @@ function Auth_OpenID_detectMathLibrary($exts) // Try to load dynamic modules. if (!$loaded) { foreach ($extension['modules'] as $module) { - if (@dl($module . "." . PHP_SHLIB_SUFFIX)) { + if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($module . "." . PHP_SHLIB_SUFFIX)) { $loaded = true; break; } diff --git a/extlib/Auth/Yadis/XML.php b/extlib/Auth/Yadis/XML.php index 4854f12bb..7232d6cbd 100644 --- a/extlib/Auth/Yadis/XML.php +++ b/extlib/Auth/Yadis/XML.php @@ -349,7 +349,7 @@ function &Auth_Yadis_getXMLParser() foreach ($extensions as $name => $params) { if (!extension_loaded($name)) { foreach ($params['libname'] as $libname) { - if (@dl($libname)) { + if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($libname)) { $classname = $params['classname']; } } diff --git a/extlib/OAuth.php b/extlib/OAuth.php index fd4853554..648627b57 100644 --- a/extlib/OAuth.php +++ b/extlib/OAuth.php @@ -327,7 +327,7 @@ class OAuthRequest {/*{{{*/ public function get_normalized_http_url() {/*{{{*/ $parts = parse_url($this->http_url); - $port = @$parts['port']; + $port = isset($parts['port']) ? $parts['port'] : null; $scheme = $parts['scheme']; $host = $parts['host']; $path = @$parts['path']; diff --git a/extlib/PEAR.php b/extlib/PEAR.php index 4c24c6006..fcefa964a 100644 --- a/extlib/PEAR.php +++ b/extlib/PEAR.php @@ -746,7 +746,7 @@ class PEAR { if (!extension_loaded($ext)) { // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1) || !function_exists('dl')) { return false; } if (OS_WINDOWS) { @@ -49,7 +49,13 @@ function getPath($req) ) { return $req['p']; } else if (array_key_exists('PATH_INFO', $_SERVER)) { - return $_SERVER['PATH_INFO']; + $path = $_SERVER['PATH_INFO']; + $script = $_SERVER['SCRIPT_NAME']; + if (substr($path, 0, mb_strlen($script)) == $script) { + return substr($path, mb_strlen($script)); + } else { + return $path; + } } else { return null; } diff --git a/install.php b/install.php index 46248c789..c2a5bb29e 100644 --- a/install.php +++ b/install.php @@ -244,7 +244,7 @@ function main() */ function haveExternalLibrary($external_library) { - if (isset($external_library['include']) && ! @include_once $external_library['include'] ) { + if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) { return false; } if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) { @@ -256,6 +256,15 @@ function haveExternalLibrary($external_library) return true; } +// Attempt to include a PHP file and report if it worked, while +// suppressing the annoying warning messages on failure. +function haveIncludeFile($filename) { + $old = error_reporting(error_reporting() & ~E_WARNING); + $ok = include_once($filename); + error_reporting($old); + return $ok; +} + /** * Check if all is ready for installation * @@ -328,12 +337,19 @@ function checkPrereqs() */ function checkExtension($name) { - if (!extension_loaded($name)) { - if (!@dl($name.'.so')) { - return false; - } + if (extension_loaded($name)) { + return true; + } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) { + // dl will throw a fatal error if it's disabled or we're in safe mode. + // More fun, it may not even exist under some SAPIs in 5.3.0 or later... + $soname = $name . '.' . PHP_SHLIB_SUFFIX; + if (PHP_SHLIB_SUFFIX == 'dll') { + $soname = "php_" . $soname; + } + return @dl($soname); + } else { + return false; } - return true; } /** @@ -390,7 +406,7 @@ E_O_T; E_O_T; foreach ($present_libraries as $library) { echo '<li>'; - if ($library['url']) { + if (isset($library['url'])) { echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>'; } else { echo htmlentities($library['name']); diff --git a/lib/action.php b/lib/action.php index 8056cb9ec..02793f069 100644 --- a/lib/action.php +++ b/lib/action.php @@ -120,17 +120,14 @@ class Action extends HTMLOutputter // lawsuit { // XXX: attributes (profile?) $this->elementStart('head'); - if (Event::handle('StartHeadChildren', array($this))) { - $this->showTitle(); - $this->showShortcutIcon(); - $this->showStylesheets(); - $this->showScripts(); - $this->showOpenSearch(); - $this->showFeeds(); - $this->showDescription(); - $this->extraHead(); - Event::handle('EndHeadChildren', array($this)); - } + $this->showTitle(); + $this->showShortcutIcon(); + $this->showStylesheets(); + $this->showScripts(); + $this->showOpenSearch(); + $this->showFeeds(); + $this->showDescription(); + $this->extraHead(); $this->elementEnd('head'); } @@ -879,6 +876,7 @@ class Action extends HTMLOutputter // lawsuit */ function handle($argarray=null) { + header('Vary: Accept-Encoding,Cookie'); $lm = $this->lastModified(); $etag = $this->etag(); if ($etag) { diff --git a/lib/designsettings.php b/lib/designsettings.php index fdc05562e..820d534f2 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -325,7 +325,6 @@ class DesignSettingsAction extends AccountSettingsAction parent::showScripts(); $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/farbtastic/farbtastic.go.js'); $this->script('js/userdesign.go.js'); $this->autofocus('design_background-image_file'); diff --git a/lib/mail.php b/lib/mail.php index df585406c..5bf4d7425 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -551,9 +551,9 @@ function mail_notify_fave($other, $user, $notice) common_init_locale($other->language); - $subject = sprintf(_('%s added your notice as a favorite'), $bestname); + $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname); - $body = sprintf(_("%1\$s just added your notice from %2\$s". + $body = sprintf(_("%1\$s (@%7\$s) just added your notice from %2\$s". " as one of their favorites.\n\n" . "The URL of your notice is:\n\n" . "%3\$s\n\n" . @@ -570,7 +570,8 @@ function mail_notify_fave($other, $user, $notice) $notice->content, common_local_url('showfavorites', array('nickname' => $user->nickname)), - common_config('site', 'name')); + common_config('site', 'name'), + $user->nickname); common_init_locale(); mail_to_user($other, $subject, $body); @@ -607,9 +608,9 @@ function mail_notify_attn($user, $notice) $conversationUrl = null; } - $subject = sprintf(_('%s sent a notice to your attention'), $bestname); + $subject = sprintf(_('%s (@%s) sent a notice to your attention'), $bestname, $sender->nickname); - $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n". + $body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n". "The notice is here:\n\n". "\t%3\$s\n\n" . "It reads:\n\n". @@ -629,10 +630,11 @@ function mail_notify_attn($user, $notice) $notice->content,//%4 $conversationUrl,//%5 common_local_url('newnotice', - array('replyto' => $sender->nickname)),//%6 + array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6 common_local_url('replies', array('nickname' => $user->nickname)),//%7 - common_local_url('emailsettings'));//%8 + common_local_url('emailsettings'), //%8 + $sender->nickname); //%9 common_init_locale(); mail_to_user($user, $subject, $body); diff --git a/lib/noticeform.php b/lib/noticeform.php index cee46709e..186330bf1 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -70,6 +70,12 @@ class NoticeForm extends Form var $user = null; /** + * The notice being replied to + */ + + var $inreplyto = null; + + /** * Constructor * * @param HTMLOutputter $out output channel @@ -77,12 +83,13 @@ class NoticeForm extends Form * @param string $content content to pre-fill */ - function __construct($out=null, $action=null, $content=null, $user=null) + function __construct($out=null, $action=null, $content=null, $user=null, $inreplyto=null) { parent::__construct($out); $this->action = $action; $this->content = $content; + $this->inreplyto = $inreplyto; if ($user) { $this->user = $user; @@ -168,7 +175,7 @@ class NoticeForm extends Form if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); } - $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto'); + $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto'); } /** diff --git a/lib/noticelist.php b/lib/noticelist.php index ec85e4a5c..d4cd3ff6e 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -261,7 +261,7 @@ class NoticeListItem extends Widget $attrs = array('href' => $this->profile->profileurl, 'class' => 'url'); if (!empty($this->profile->fullname)) { - $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ') '; + $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')'; } $this->out->elementStart('a', $attrs); $this->showAvatar(); @@ -418,9 +418,17 @@ class NoticeListItem extends Widget function showContext() { - // XXX: also show context if there are replies to this notice - if (!empty($this->notice->conversation) - && $this->notice->conversation != $this->notice->id) { + $hasConversation = false; + if( !empty($this->notice->conversation) + && $this->notice->conversation != $this->notice->id){ + $hasConversation = true; + }else{ + $conversation = Notice::conversationStream($this->notice->id, 1, 1); + if($conversation->N > 0){ + $hasConversation = true; + } + } + if ($hasConversation){ $convurl = common_local_url('conversation', array('id' => $this->notice->conversation)); $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id, @@ -442,7 +450,7 @@ class NoticeListItem extends Widget { if (common_logged_in()) { $reply_url = common_local_url('newnotice', - array('replyto' => $this->profile->nickname)); + array('replyto' => $this->profile->nickname, 'inreplyto' => $this->notice->id)); $this->out->elementStart('a', array('href' => $reply_url, 'class' => 'notice_reply', 'title' => _('Reply to this notice'))); diff --git a/lib/router.php b/lib/router.php index 0e5fe3a54..33b098473 100644 --- a/lib/router.php +++ b/lib/router.php @@ -172,6 +172,10 @@ class Router $m->connect('notice/new?replyto=:replyto', array('action' => 'newnotice'), array('replyto' => '[A-Za-z0-9_-]+')); + $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto', + array('action' => 'newnotice'), + array('replyto' => '[A-Za-z0-9_-]+'), + array('inreplyto' => '[0-9]+')); $m->connect('notice/:notice/file', array('action' => 'file'), diff --git a/lib/rssaction.php b/lib/rssaction.php index dd0f1005a..faf6bec7d 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -78,25 +78,12 @@ class Rss10Action extends Action function prepare($args) { parent::prepare($args); + $this->limit = (int) $this->trimmed('limit'); + if ($this->limit == 0) { $this->limit = DEFAULT_RSS_LIMIT; } - return true; - } - - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - - function handle($args) - { - // Parent handling, including cache check - parent::handle($args); if (common_config('site', 'private')) { if (!isset($_SERVER['PHP_AUTH_USER'])) { @@ -122,8 +109,21 @@ class Rss10Action extends Action } } - // Get the list of notices - $this->notices = $this->getNotices($this->limit); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + // Parent handling, including cache check + parent::handle($args); $this->showRss(); } @@ -140,7 +140,7 @@ class Rss10Action extends Action } /** - * Get the notices to output in this stream + * Get the notices to output in this stream. * * @return array an array of Notice objects sorted in reverse chron */ diff --git a/lib/util.php b/lib/util.php index bd15a5622..56753debe 100644 --- a/lib/util.php +++ b/lib/util.php @@ -391,10 +391,10 @@ function common_render_content($text, $notice) { $r = common_render_text($text); $id = $notice->profile_id; - $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r); - $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); - $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); return $r; } @@ -442,9 +442,9 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) { ')'. '(?:'. '(?:\:\d+)?'. //:port - '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"]*)?'. // /path - '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\/]*)?'. // ?query string - '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\/\?\#]*)?'. // #fragment + '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@]*)?'. // /path + '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@\/]*)?'. // ?query string + '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\@/\?\#]*)?'. // #fragment ')(?<![\?\.\,\#\,])'. ')'. '#ixu'; @@ -552,12 +552,13 @@ function common_linkify($url) { } if (!empty($f)) { - if (isset($f->filename)) { + if ($f->isEnclosure()) { $is_attachment = true; $attachment_id = $f->id; - } else { // if it has OEmbed info, it's an attachment, too + } else { $foe = File_oembed::staticGet('file_id', $f->id); if (!empty($foe)) { + // if it has OEmbed info, it's an attachment, too $is_attachment = true; $attachment_id = $f->id; @@ -1393,9 +1394,6 @@ function common_shorten_url($long_url) $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]); $short_url = $short_url_service->shorten($long_url); - if(substr($short_url,0,7)=='http://'){ - $short_url = substr($short_url,7); - } return $short_url; } diff --git a/plugins/Autocomplete/Autocomplete.js b/plugins/Autocomplete/Autocomplete.js index dfadea004..3eff685a8 100644 --- a/plugins/Autocomplete/Autocomplete.js +++ b/plugins/Autocomplete/Autocomplete.js @@ -1,38 +1,37 @@ $(document).ready(function(){ - $.getJSON($('address .url')[0].href+'/api/statuses/friends.json?user_id=' + current_user['id'] + '&lite=true&callback=?', - function(friends){ - $('#notice_data-text').autocomplete(friends, { + $('#notice_data-text').autocomplete($('address .url')[0].href+'/plugins/Autocomplete/autocomplete.json', { multiple: true, multipleSeparator: " ", minChars: 1, formatItem: function(row, i, max){ - return '@' + row.screen_name + ' (' + row.name + ')'; + row = eval("(" + row + ")"); + switch(row.type) + { + case 'user': + return row.nickname + ' (' + row.fullname + ')'; + case 'group': + return row.nickname + ' (' + row.fullname + ')'; + } }, formatMatch: function(row, i, max){ - return '@' + row.screen_name; + row = eval("(" + row + ")"); + switch(row.type) + { + case 'user': + return row.nickname; + case 'group': + return row.nickname; + } }, formatResult: function(row){ - return '@' + row.screen_name; + row = eval("(" + row + ")"); + switch(row.type) + { + case 'user': + return '@' + row.nickname; + case 'group': + return '!' + row.nickname; + } } }); - } - ); - $.getJSON($('address .url')[0].href+'/api/statusnet/groups/list.json?user_id=' + current_user['id'] + '&callback=?', - function(groups){ - $('#notice_data-text').autocomplete(groups, { - multiple: true, - multipleSeparator: " ", - minChars: 1, - formatItem: function(row, i, max){ - return '!' + row.nickname + ' (' + row.fullname + ')'; - }, - formatMatch: function(row, i, max){ - return '!' + row.nickname; - }, - formatResult: function(row){ - return '!' + row.nickname; - } - }); - } - ); }); diff --git a/plugins/Autocomplete/AutocompletePlugin.php b/plugins/Autocomplete/AutocompletePlugin.php index b75397270..baaec73c1 100644 --- a/plugins/Autocomplete/AutocompletePlugin.php +++ b/plugins/Autocomplete/AutocompletePlugin.php @@ -31,6 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +require_once(INSTALLDIR.'/plugins/Autocomplete/autocomplete.php'); + class AutocompletePlugin extends Plugin { function __construct() @@ -40,13 +42,6 @@ class AutocompletePlugin extends Plugin function onEndShowScripts($action){ if (common_logged_in()) { - $current_user = common_current_user(); - $js_string = <<<EOT -<script type="text/javascript"> -var current_user = { id: '$current_user->id' }; -</script> -EOT; - $action->raw($js_string); $action->script('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.pack.js'); $action->script('plugins/Autocomplete/Autocomplete.js'); } @@ -59,5 +54,12 @@ EOT; } } + function onRouterInitialized($m) + { + if (common_logged_in()) { + $m->connect('plugins/Autocomplete/autocomplete.json', array('action'=>'autocomplete')); + } + } + } ?> diff --git a/plugins/Autocomplete/autocomplete.php b/plugins/Autocomplete/autocomplete.php new file mode 100644 index 000000000..aa57b3915 --- /dev/null +++ b/plugins/Autocomplete/autocomplete.php @@ -0,0 +1,136 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * List users for autocompletion + * + * 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 Plugin + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @copyright 2008-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') && !defined('LACONICA')) { + exit(1); +} + +/** + * List users for autocompletion + * + * This is the form for adding a new g + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class AutocompleteAction extends Action +{ + private $result; + + /** + * Last-modified date for page + * + * When was the content of this page last modified? Based on notice, + * profile, avatar. + * + * @return int last-modified date as unix timestamp + */ + function lastModified() + { + $max=0; + foreach($this->users as $user){ + $max = max($max,strtotime($user->modified),strtotime($user->profile->modified)); + } + foreach($this->groups as $group){ + $max = max($max,strtotime($group->modified)); + } + return $max; + } + + /** + * An entity tag for this page + * + * Shows the ETag for the page, based on the notice ID and timestamps + * for the notice, profile, and avatar. It's weak, since we change + * the date text "one hour ago", etc. + * + * @return string etag + */ + function etag() + { + return '"' . implode(':', array($this->arg('action'), + crc32($this->arg('q')), //the actual string can have funny characters in we don't want showing up in the etag + $this->arg('limit'), + $this->lastModified())) . '"'; + } + + function prepare($args) + { + parent::prepare($args); + $this->groups=array(); + $this->users=array(); + $q = $this->arg('q'); + $limit = $this->arg('limit'); + if($limit > 200) $limit=200; //prevent DOS attacks + if(substr($q,0,1)=='@'){ + //user search + $q=substr($q,1); + $user = new User(); + $user->limit($limit); + $user->whereAdd('nickname like \'' . trim($user->escape($q), '\'') . '%\''); + $user->find(); + while($user->fetch()) { + $profile = Profile::staticGet($user->id); + $user->profile=$profile; + $this->users[]=$user; + } + } + if(substr($q,0,1)=='!'){ + //group search + $q=substr($q,1); + $group = new User_group(); + $group->limit($limit); + $group->whereAdd('nickname like \'' . trim($group->escape($q), '\'') . '%\''); + $group->find(); + while($group->fetch()) { + $this->groups[]=$group; + } + } + return true; + } + + function handle($args) + { + parent::handle($args); + $results = array(); + foreach($this->users as $user){ + $results[]=array('nickname' => $user->nickname, 'fullname'=> $user->profile->fullname, 'type'=>'user'); + } + foreach($this->groups as $group){ + $results[]=array('nickname' => $group->nickname, 'fullname'=> $group->fullname, 'type'=>'group'); + } + foreach($results as $result) { + print json_encode($result) . "\n"; + } + } +} diff --git a/plugins/Autocomplete/readme.txt b/plugins/Autocomplete/readme.txt index 3272aa1ee..1db4c6565 100644 --- a/plugins/Autocomplete/readme.txt +++ b/plugins/Autocomplete/readme.txt @@ -1,5 +1,7 @@ Autocomplete allows users to autocomplete screen names in @ replies. When an "@" is typed into the notice text area, an autocomplete box is displayed populated with the user's friends' screen names. +Note: This plugin doesn't work if the site is in Private mode, i.e. when $config['site']['private'] is set to true. + Installation ============ Add "addPlugin('Autocomplete');" to the bottom of your config.php diff --git a/plugins/InfiniteScroll/InfiniteScrollPlugin.php b/plugins/InfiniteScroll/InfiniteScrollPlugin.php index c955298cb..5928c007f 100644 --- a/plugins/InfiniteScroll/InfiniteScrollPlugin.php +++ b/plugins/InfiniteScroll/InfiniteScrollPlugin.php @@ -40,7 +40,7 @@ class InfiniteScrollPlugin extends Plugin function onEndShowScripts($action) { - $action->script('plugins/InfiniteScroll/jquery.infinitescroll.min.js'); + $action->script('plugins/InfiniteScroll/jquery.infinitescroll.js'); $action->script('plugins/InfiniteScroll/infinitescroll.js'); } } diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js index 6513072d0..ae4d53d09 100644 --- a/plugins/InfiniteScroll/infinitescroll.js +++ b/plugins/InfiniteScroll/infinitescroll.js @@ -1,6 +1,7 @@ jQuery(document).ready(function($){ $('notices_primary').infinitescroll({ debug: true, + infiniteScroll : false, nextSelector : "li.nav_next a", loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif', text : "<em>Loading the next set of posts...</em>", @@ -12,4 +13,3 @@ jQuery(document).ready(function($){ NoticeAttachments(); }); }); - diff --git a/plugins/InfiniteScroll/jquery.infinitescroll.js b/plugins/InfiniteScroll/jquery.infinitescroll.js index 670686b0e..ec31bb086 100644 --- a/plugins/InfiniteScroll/jquery.infinitescroll.js +++ b/plugins/InfiniteScroll/jquery.infinitescroll.js @@ -92,14 +92,14 @@ if (props.isDuringAjax || props.isInvalidPage || props.isDone) return; - if ( !isNearBottom(opts,props) ) return; + if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return; // we dont want to fire the ajax multiple times props.isDuringAjax = true; // show the loading message and hide the previous/next links props.loadingMsg.appendTo( opts.contentSelector ).show(); - $( opts.navSelector ).hide(); + if(opts.infiniteScroll) $( opts.navSelector ).hide(); // increment the URL bit. e.g. /page/3/ props.currPage++; @@ -205,10 +205,19 @@ } }); - // bind scroll handler to element (if its a local scroll) or window - $(opts.localMode ? this : window) - .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } ) - .trigger('scroll.infscr'); // trigger the event, in case it's a short page + if(opts.infiniteScroll){ + // bind scroll handler to element (if its a local scroll) or window + $(opts.localMode ? this : window) + .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } ) + .trigger('scroll.infscr'); // trigger the event, in case it's a short page + }else{ + $(opts.nextSelector).click( + function(){ + infscrSetup(path,opts,props,callback); + return false; + } + ); + } return this; @@ -222,6 +231,7 @@ $.infinitescroll = { defaults : { debug : false, + infiniteScroll : true, preload : false, nextSelector : "div.navigation a:first", loadingImg : "http://www.infinite-scroll.com/loading.gif", diff --git a/plugins/Meteor/meteorupdater.js b/plugins/Meteor/meteorupdater.js index 2e688336f..9ce68775b 100644 --- a/plugins/Meteor/meteorupdater.js +++ b/plugins/Meteor/meteorupdater.js @@ -1,5 +1,6 @@ -// update the local timeline from a Meteor server -// +// Update the local timeline from a Meteor server +// XXX: If @a is subscribed to @b, @a should get @b's notices in @a's Personal timeline. +// Do Replies timeline. var MeteorUpdater = function() { diff --git a/plugins/PiwikAnalyticsPlugin.php b/plugins/PiwikAnalyticsPlugin.php index 85a24c132..8191f5181 100644 --- a/plugins/PiwikAnalyticsPlugin.php +++ b/plugins/PiwikAnalyticsPlugin.php @@ -59,9 +59,9 @@ if (!defined('STATUSNET')) { class PiwikAnalyticsPlugin extends Plugin { /** the base of your Piwik installation */ - var $piwikroot = null; + public $piwikroot = null; /** the Piwik Id of your statusnet installation */ - var $piwikId = null; + public $piwikId = null; /** * constructor @@ -73,7 +73,7 @@ class PiwikAnalyticsPlugin extends Plugin function __construct($root=null, $id=null) { $this->piwikroot = $root; - $this->piwikid = $id; + $this->piwikId = $id; parent::__construct(); } @@ -96,7 +96,7 @@ document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/ja </script> <script type="text/javascript"> try { - var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4); + var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", {$this->piwikId}); piwikTracker.trackPageView(); piwikTracker.enableLinkTracking(); } catch( err ) {} @@ -108,4 +108,4 @@ ENDOFPIWIK; $action->raw($piwikCode); return true; } -}
\ No newline at end of file +} diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 82eca3d08..e30c41156 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -50,6 +50,11 @@ class RealtimePlugin extends Plugin protected $favorurl = null; protected $deleteurl = null; + /** + * When it's time to initialize the plugin, calculate and + * pass the URLs we need. + */ + function onInitializePlugin() { $this->replyurl = common_local_url('newnotice'); @@ -57,29 +62,26 @@ class RealtimePlugin extends Plugin // FIXME: need to find a better way to pass this pattern in $this->deleteurl = common_local_url('deletenotice', array('notice' => '0000000000')); + return true; } function onEndShowScripts($action) { - $path = null; + $timeline = $this->_getTimeline($action); - switch ($action->trimmed('action')) { - case 'public': - $path = array('public'); - break; - case 'tag': - $tag = $action->trimmed('tag'); - if (!empty($tag)) { - $path = array('tag', $tag); - } else { - return true; - } - break; - default: + // If there's not a timeline on this page, + // just return true + + if (empty($timeline)) { return true; } - $timeline = $this->_pathToChannel($path); + $base = $action->selfUrl(); + if (mb_strstr($base, '?')) { + $url = $base . '&realtime=1'; + } else { + $url = $base . '?realtime=1'; + } $scripts = $this->_getScripts(); @@ -95,10 +97,22 @@ class RealtimePlugin extends Plugin $user_id = 0; } + if ($action->boolean('realtime')) { + $realtimeUI = ' RealtimeUpdate.initPopupWindow();'; + } + else { + $iconurl = common_path('plugins/Realtime/icon_external.gif'); + $realtimeUI = ' RealtimeUpdate.addPopup("'.$url.'", "'.$timeline.'", "'. $iconurl .'");'; + } + $action->elementStart('script', array('type' => 'text/javascript')); - $action->raw("$(document).ready(function() { "); - $action->raw($this->_updateInitialize($timeline, $user_id)); - $action->raw(" });"); + + $script = ' $(document).ready(function() { '. + $realtimeUI. + $this->_updateInitialize($timeline, $user_id). + '}); '; + $action->raw($script); + $action->elementEnd('script'); return true; @@ -108,13 +122,23 @@ class RealtimePlugin extends Plugin { $paths = array(); - // XXX: Add other timelines; this is just for the public one + // Add to the author's timeline + + $user = User::staticGet('id', $notice->profile_id); + + if (!empty($user)) { + $paths[] = array('showstream', $user->nickname); + } + + // Add to the public timeline if ($notice->is_local || ($notice->is_local == 0 && !common_config('public', 'localonly'))) { $paths[] = array('public'); } + // Add to the tags timeline + $tags = $this->getNoticeTags($notice); if (!empty($tags)) { @@ -123,6 +147,46 @@ class RealtimePlugin extends Plugin } } + // Add to inbox timelines + // XXX: do a join + + $inbox = new Notice_inbox(); + $inbox->notice_id = $notice->id; + + if ($inbox->find()) { + while ($inbox->fetch()) { + $user = User::staticGet('id', $inbox->user_id); + $paths[] = array('all', $user->nickname); + } + } + + // Add to the replies timeline + + $reply = new Reply(); + $reply->notice_id = $notice->id; + + if ($reply->find()) { + while ($reply->fetch()) { + $user = User::staticGet('id', $reply->profile_id); + if (!empty($user)) { + $paths[] = array('replies', $user->nickname); + } + } + } + + // Add to the group timeline + // XXX: join + + $gi = new Group_inbox(); + $gi->notice_id = $notice->id; + + if ($gi->find()) { + while ($gi->fetch()) { + $ug = User_group::staticGet('id', $gi->group_id); + $paths[] = array('showgroup', $ug->nickname); + } + } + if (count($paths) > 0) { $json = $this->noticeAsJson($notice); @@ -140,6 +204,39 @@ class RealtimePlugin extends Plugin return true; } + function onStartShowBody($action) + { + $realtime = $action->boolean('realtime'); + if (!$realtime) { + return true; + } + + $action->elementStart('body', + (common_current_user()) ? array('id' => $action->trimmed('action'), + 'class' => 'user_in') + : array('id' => $action->trimmed('action'))); + + $action->elementStart('div', array('id' => 'header')); + + // XXX hack to deal with JS that tries to get the + // root url from page output + + $action->elementStart('address'); + $action->element('a', array('class' => 'url', + 'href' => common_local_url('public')), + ''); + $action->elementEnd('address'); + + if (common_logged_in()) { + $action->showNoticeForm(); + } + $action->elementEnd('div'); + + $action->showContentBlock(); + $action->elementEnd('body'); + return false; // No default processing + } + function noticeAsJson($notice) { // FIXME: this code should be abstracted to a neutral third @@ -224,4 +321,41 @@ class RealtimePlugin extends Plugin { return ''; } + + function _getTimeline($action) + { + $path = null; + $timeline = null; + + $action_name = $action->trimmed('action'); + + switch ($action_name) { + case 'public': + $path = array('public'); + break; + case 'tag': + $tag = $action->trimmed('tag'); + if (!empty($tag)) { + $path = array('tag', $tag); + } + break; + case 'showstream': + case 'all': + case 'replies': + case 'showgroup': + $nickname = common_canonical_nickname($action->trimmed('nickname')); + if (!empty($nickname)) { + $path = array($action_name, $nickname); + } + break; + default: + break; + } + + if (!empty($path)) { + $timeline = $this->_pathToChannel($path); + } + + return $timeline; + } } diff --git a/plugins/Realtime/icon_external.gif b/plugins/Realtime/icon_external.gif Binary files differnew file mode 100644 index 000000000..c4118d53b --- /dev/null +++ b/plugins/Realtime/icon_external.gif diff --git a/plugins/Realtime/jquery.getUrlParam.js b/plugins/Realtime/jquery.getUrlParam.js new file mode 100644 index 000000000..e8f73eb47 --- /dev/null +++ b/plugins/Realtime/jquery.getUrlParam.js @@ -0,0 +1,72 @@ +/* Copyright (c) 2006-2007 Mathias Bank (http://www.mathias-bank.de) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * Version 2.1 + * + * Thanks to + * Hinnerk Ruemenapf - http://hinnerk.ruemenapf.de/ for bug reporting and fixing. + * Tom Leonard for some improvements + * + */ +jQuery.fn.extend({ +/** +* Returns get parameters. +* +* If the desired param does not exist, null will be returned +* +* To get the document params: +* @example value = $(document).getUrlParam("paramName"); +* +* To get the params of a html-attribut (uses src attribute) +* @example value = $('#imgLink').getUrlParam("paramName"); +*/ + getUrlParam: function(strParamName){ + strParamName = escape(unescape(strParamName)); + + var returnVal = new Array(); + var qString = null; + + if ($(this).attr("nodeName")=="#document") { + //document-handler + + if (window.location.search.search(strParamName) > -1 ){ + + qString = window.location.search.substr(1,window.location.search.length).split("&"); + } + + } else if ($(this).attr("src")!="undefined") { + + var strHref = $(this).attr("src") + if ( strHref.indexOf("?") > -1 ){ + var strQueryString = strHref.substr(strHref.indexOf("?")+1); + qString = strQueryString.split("&"); + } + } else if ($(this).attr("href")!="undefined") { + + var strHref = $(this).attr("href") + if ( strHref.indexOf("?") > -1 ){ + var strQueryString = strHref.substr(strHref.indexOf("?")+1); + qString = strQueryString.split("&"); + } + } else { + return null; + } + + + if (qString==null) return null; + + + for (var i=0;i<qString.length; i++){ + if (escape(unescape(qString[i].split("=")[0])) == strParamName){ + returnVal.push(qString[i].split("=")[1]); + } + + } + + + if (returnVal.length==0) return null; + else if (returnVal.length==1) return returnVal[0]; + else return returnVal; + } +});
\ No newline at end of file diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index d55db5859..57fe0a843 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -1,8 +1,8 @@ // add a notice encoded as JSON into the current timeline // +// TODO: i18n RealtimeUpdate = { - _userid: 0, _replyurl: '', _favorurl: '', @@ -10,10 +10,10 @@ RealtimeUpdate = { init: function(userid, replyurl, favorurl, deleteurl) { - RealtimeUpdate._userid = userid; - RealtimeUpdate._replyurl = replyurl; - RealtimeUpdate._favorurl = favorurl; - RealtimeUpdate._deleteurl = deleteurl; + RealtimeUpdate._userid = userid; + RealtimeUpdate._replyurl = replyurl; + RealtimeUpdate._favorurl = favorurl; + RealtimeUpdate._deleteurl = deleteurl; }, receive: function(data) @@ -21,7 +21,7 @@ RealtimeUpdate = { id = data.id; // Don't add it if it already exists - + // if ($("#notice-"+id).length > 0) { return; } @@ -50,30 +50,19 @@ RealtimeUpdate = { "<p class=\"entry-content\">"+html+"</p>"+ "</div>"+ "<div class=\"entry-content\">"+ - "<dl class=\"timestamp\">"+ - "<dt>Published</dt>"+ - "<dd>"+ - "<a rel=\"bookmark\" href=\""+data['url']+"\" >"+ + "<a class=\"timestamp\" rel=\"bookmark\" href=\""+data['url']+"\" >"+ "<abbr class=\"published\" title=\""+data['created_at']+"\">a few seconds ago</abbr>"+ "</a> "+ - "</dd>"+ - "</dl>"+ - "<dl class=\"device\">"+ - "<dt>From</dt> "+ - "<dd>"+source+"</dd>"+ // may have a link, I think - "</dl>"; - + "<span class=\"source\">"+ + "from "+ + "<span class=\"device\">"+source+"</span>"+ // may have a link + "</span>"; if (data['in_reply_to_status_id']) { - ni = ni+" <dl class=\"response\">"+ - "<dt>To</dt>"+ - "<dd>"+ - "<a href=\""+data['in_reply_to_status_url']+"\" rel=\"in-reply-to\">in reply to</a>"+ - "</dd>"+ - "</dl>"; + ni = ni+" <a class=\"response\" href=\""+data['in_reply_to_status_url']+"\">in context</a>"; } ni = ni+"</div>"+ - "<div class=\"notice-options\">"; + "<div class=\"notice-options\">"; if (RealtimeUpdate._userid != 0) { var input = $("form#form_notice fieldset input#token"); @@ -95,12 +84,12 @@ RealtimeUpdate = { var ff; ff = "<form id=\"favor-"+id+"\" class=\"form_favor\" method=\"post\" action=\""+RealtimeUpdate._favorurl+"\">"+ - "<fieldset>"+ - "<legend>Favor this notice</legend>"+ // XXX: i18n + "<fieldset>"+ + "<legend>Favor this notice</legend>"+ "<input name=\"token-"+id+"\" type=\"hidden\" id=\"token-"+id+"\" value=\""+session_key+"\"/>"+ "<input name=\"notice\" type=\"hidden\" id=\"notice-n"+id+"\" value=\""+id+"\"/>"+ "<input type=\"submit\" id=\"favor-submit-"+id+"\" name=\"favor-submit-"+id+"\" class=\"submit\" value=\"Favor\" title=\"Favor this notice\"/>"+ - "</fieldset>"+ + "</fieldset>"+ "</form>"; return ff; }, @@ -108,28 +97,51 @@ RealtimeUpdate = { makeReplyLink: function(id, nickname) { var rl; - rl = "<dl class=\"notice_reply\">"+ - "<dt>Reply to this notice</dt>"+ - "<dd>"+ - "<a href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span>"+ - "</a>"+ - "</dd>"+ - "</dl>"; + rl = "<a class=\"notice_reply\" href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span></a>"; return rl; - }, + }, makeDeleteLink: function(id) { var dl, delurl; delurl = RealtimeUpdate._deleteurl.replace("0000000000", id); - dl = "<dl class=\"notice_delete\">"+ - "<dt>Delete this notice</dt>"+ - "<dd>"+ - "<a href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>"+ - "</dd>"+ - "</dl>"; + dl = "<a class=\"notice_delete\" href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>"; return dl; }, + + addPopup: function(url, timeline, iconurl) + { + $('#site_nav_local_views .current a').append('<button id="realtime_timeline" title="Real-time pop window">↗</button>'); + + $('#realtime_timeline').css({ + 'margin':'2px 0 0 11px', + 'background':'transparent url('+ iconurl + ') no-repeat 45% 45%', + 'text-indent':'-9999px', + 'width':'16px', + 'height':'16px', + 'padding':'0', + 'display':'block', + 'float':'right', + 'border':'none', + 'cursor':'pointer' + }); + + $('#realtime_timeline').click(function() { + window.open(url, + timeline, + 'toolbar=no,resizable=yes,scrollbars=yes,status=yes'); + + return false; + }); + }, + + initPopupWindow: function() + { + window.resizeTo(575, 640); + $('address').hide(); + $('#content').css({'width':'92%'}); + } } + diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README index ce23a2695..b996f96cc 100644 --- a/plugins/recaptcha/README +++ b/plugins/recaptcha/README @@ -6,7 +6,7 @@ Use: 1. Get an API key from http://recaptcha.net 2. In config.php add: -include_once('plugins/recaptcha.php'); +include_once('plugins/recaptcha/recaptcha.php'); $captcha = new recaptcha(publickey, privatekey, showErrors); Changelog diff --git a/tests/URLDetectionTest.php b/tests/URLDetectionTest.php index 87b537646..1c3f7cd96 100644 --- a/tests/URLDetectionTest.php +++ b/tests/URLDetectionTest.php @@ -27,6 +27,8 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase return array( array('not a link :: no way', 'not a link :: no way'), + array('link http://www.somesite.com/xyz/35637563@N00/52803365/ link', + 'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'), array('http://127.0.0.1', '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), array('127.0.0.1', diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css index 3851bc057..3fe05eb3d 100644 --- a/theme/cloudy/css/display.css +++ b/theme/cloudy/css/display.css @@ -120,6 +120,10 @@ float:left; margin-left:11px; float:left; } +.form_settings .form_data textarea { +width:325px; +} + .form_settings .form_data input.submit { margin-left:0; } @@ -968,9 +972,6 @@ right:7px; top:47px; right:7px; } -.notice-options .notice_reply dt { -display:none; -} .notice-options input, .notice-options a { @@ -978,13 +979,13 @@ text-indent:-9999px; outline:none; } -.notice-options .notice_reply a, +.notice-options .notice_reply, .notice-options input.submit { display:block; border:0; } -.notice-options .notice_reply a, -.notice-options .notice_delete a { +.notice-options .notice_reply, +.notice-options .notice_delete { text-decoration:none; padding-left:16px; } @@ -1375,6 +1376,12 @@ padding-top:160px; #smssettings #form_notice, #twittersettings #form_notice, #imsettings #form_notice, +#userdesignsettings #form_notice, +#groupdesignsettings #form_notice, +#grouplogo #form_notice, +#editgroup #form_notice, +#blockedfromgroup #form_notice, +#groupmembers #form_notice, #doc #form_notice, #usergroups #form_notice, #invite #form_notice, @@ -1584,11 +1591,11 @@ background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no background:none; } -.notice-options .notice_reply a, +.notice-options .notice_reply, .notice-options form input.submit { background-color:transparent; } -.notice-options .notice_reply a { +.notice-options .notice_reply { background:transparent url(../images/icons/icon_reply.gif) no-repeat 0 45%; } .notice-options form.form_favor input.submit { @@ -1597,7 +1604,7 @@ background:transparent url(../images/icons/icon_favourite.gif) no-repeat 0 45%; .notice-options form.form_disfavor input.submit { background:transparent url(../images/icons/icon_disfavourite.gif) no-repeat 0 45%; } -.notice-options .notice_delete a { +.notice-options .notice_delete { background:transparent url(../images/icons/icon_trash.gif) no-repeat 0 45%; } |