diff --git a/README b/README index c5adda1776..c687cb240a 100644 --- a/README +++ b/README @@ -2,8 +2,8 @@ README ------ -StatusNet 0.9.0 ("Stand") -4 Mar 2010 +StatusNet 0.9.1 ("Everybody Hurts") +28 Mar 2010 This is the README file for StatusNet, the Open Source microblogging platform. It includes installation instructions, descriptions of @@ -77,57 +77,34 @@ for additional terms. New this version ================ -This is a major feature release since version 0.8.3, released Feb 1 -2010. It is the final release version of 0.9.0, replacing any beta -versions. +This is a minor bug and feature release since version 0.9.0 released 4 +March 2010. + +Because of fixes to OStatus bugs, it is highly recommended that all +public sites upgrade to the new version immediately. Notable changes this version: -- Support for the new distributed status update standard OStatus - , based on PubSubHubbub, Salmon, Webfinger, - and Activity Streams. -- Support for location using the Geolocation API. Notices are (optionally) - marked with lat-long information with geo microformats, and can be shown - on a map. -- No fixed content size. Notice size is configurable, from 1 to - unlimited number of characters. Default is still 140! -- An authorization framework, allowing different levels of users. -- A Web-based administration panel. -- A moderation system that lets site moderators sandbox, silence, - or delete uncooperative users. -- A flag system that lets users flag profiles for moderator review. -- Support for OAuth authentication in the Twitter - API. -- User roles system that lets the owner of the site to assign - administrator and moderator roles to other users. -- A pluggable authentication system. -- An authentication plugin for LDAP servers. -- Many features that were core in 0.8.x are now plugins, such - as OpenID, Twitter integration, Facebook integration -- A much-improved offline processing system -- In-browser "realtime" updates using a number of realtime - servers (Meteor, Orbited, Cometd) -- A plugin to provide an interface optimized for mobile browsers -- Support for Facebook Connect -- Support for logging in with a Twitter account -- Vastly improved translation with additional languages and - translation in plugins -- Support for all-SSL instances -- Core support for "repeats" (like Twitter's "retweets") -- Pluggable caching system, with plugins for Memcached, - APC, XCache, and a disk-based cache -- Plugin to support RSSCloud -- A framework for adding advertisements to a public site, - and plugins for Google AdSense and OpenX server -- Plugins to throttle excessive subscriptions and registrations. -- A plugin to blacklist particular URLs or nicknames. +- Twitter bridge truncates and links back to original for long + notices. +- Changed "Home" link in main menu to "Personal". +- A new memcached plugin (using pecl/memcached versus pecl/memcache) +- Opt-in subscription to update@status.net +- Script to run commands on behalf of a user. +- Better Web UI for long notices. +- A plugin to open external links in their own window or tab +- Fixes to Salmon protocol for compatibility with other systems. +- Updates to latest ActivityStreams definition. +- Twitpic-compatible API for image upload. +- Background deletion of user accounts. +- Better support for HTTP basic authentication with CGI/FastCGI +- Better discovery on OStatus +- Support for PuSH-enabled RSS 2.0 feeds +- OpenID-only mode +- OpenID blacklist/whitelist +- OStatus unit tests -There are also literally thousands of bugs fixed and minor features -added. A full changelog is available at http://status.net/wiki/StatusNet_0.9.0. - -Under the covers, the software has a vastly improved plugin and -extension mechanism that makes writing powerful and flexible additions -to the core functionality much easier. +A full changelog is available at http://status.net/wiki/StatusNet_0.9.1. Prerequisites ============= @@ -239,9 +216,9 @@ especially if you've previously installed PHP/MySQL packages. 1. Unpack the tarball you downloaded on your Web server. Usually a command like this will work: - tar zxf statusnet-0.9.0.tar.gz + tar zxf statusnet-0.9.1.tar.gz - ...which will make a statusnet-0.9.0 subdirectory in your current + ...which will make a statusnet-0.9.1 subdirectory in your current directory. (If you don't have shell access on your Web server, you may have to unpack the tarball on your local computer and FTP the files to the server.) @@ -249,7 +226,7 @@ especially if you've previously installed PHP/MySQL packages. 2. Move the tarball to a directory of your choosing in your Web root directory. Usually something like this will work: - mv statusnet-0.9.0 /var/www/statusnet + mv statusnet-0.9.1 /var/www/statusnet This will make your StatusNet instance available in the statusnet path of your server, like "http://example.net/statusnet". "microblog" or @@ -664,7 +641,7 @@ with this situation. If you've been using StatusNet 0.7, 0.6, 0.5 or lower, or if you've been tracking the "git" version of the software, you will probably want to upgrade and keep your existing data. There is no automated -upgrade procedure in StatusNet 0.9.0. Try these step-by-step +upgrade procedure in StatusNet 0.9.1. Try these step-by-step instructions; read to the end first before trying them. 0. Download StatusNet and set up all the prerequisites as if you were @@ -685,7 +662,7 @@ instructions; read to the end first before trying them. 5. Once all writing processes to your site are turned off, make a final backup of the Web directory and database. 6. Move your StatusNet directory to a backup spot, like "statusnet.bak". -7. Unpack your StatusNet 0.9.0 tarball and move it to "statusnet" or +7. Unpack your StatusNet 0.9.1 tarball and move it to "statusnet" or wherever your code used to be. 8. Copy the config.php file and avatar directory from your old directory to your new directory. @@ -1522,7 +1499,7 @@ repository (see below), and you get a compilation error ("unexpected T_STRING") in the browser, check to see that you don't have any conflicts in your code. -If you upgraded to StatusNet 0.9.0 without reading the "Notice +If you upgraded to StatusNet 0.9.1 without reading the "Notice inboxes" section above, and all your users' 'Personal' tabs are empty, read the "Notice inboxes" section above. diff --git a/actions/showgroup.php b/actions/showgroup.php index 5704b13d14..a0d05ba37a 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -221,7 +221,8 @@ class ShowgroupAction extends GroupDesignAction function showGroupProfile() { - $this->elementStart('div', 'entity_profile vcard author'); + $this->elementStart('div', array('id' => 'i', + 'class' => 'entity_profile vcard author')); $this->element('h2', null, _('Group profile')); diff --git a/actions/subscriptions.php b/actions/subscriptions.php index ba6171ef4c..7b10b3425b 100644 --- a/actions/subscriptions.php +++ b/actions/subscriptions.php @@ -196,12 +196,30 @@ class SubscriptionsListItem extends SubscriptionListItem $this->out->hidden('token', common_session_token()); $this->out->hidden('profile', $this->profile->id); if (common_config('xmpp', 'enabled')) { - $this->out->checkbox('jabber', _('Jabber'), $sub->jabber); + $attrs = array('name' => 'jabber', + 'type' => 'checkbox', + 'class' => 'checkbox', + 'id' => 'jabber-'.$this->profile->id); + if ($sub->jabber) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + $this->out->element('label', array('for' => 'jabber-'.$this->profile->id), _('Jabber')); } else { $this->out->hidden('jabber', $sub->jabber); } if (common_config('sms', 'enabled')) { - $this->out->checkbox('sms', _('SMS'), $sub->sms); + $attrs = array('name' => 'sms', + 'type' => 'checkbox', + 'class' => 'checkbox', + 'id' => 'sms-'.$this->profile->id); + if ($sub->sms) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + $this->out->element('label', array('for' => 'sms-'.$this->profile->id), _('SMS')); } else { $this->out->hidden('sms', $sub->sms); } diff --git a/classes/User.php b/classes/User.php index 8ad2ec63d5..659ec9467b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -670,8 +670,12 @@ class User extends Memcached_DataObject function delete() { - $profile = $this->getProfile(); - $profile->delete(); + try { + $profile = $this->getProfile(); + $profile->delete(); + } catch (UserNoProfileException $unp) { + common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion."); + } $related = array('Fave', 'Confirm_address', @@ -679,6 +683,7 @@ class User extends Memcached_DataObject 'Foreign_link', 'Invitation', ); + Event::handle('UserDeleteRelated', array($this, &$related)); foreach ($related as $cls) { diff --git a/js/util.js b/js/util.js index 3efda0d7b9..1320d11b4b 100644 --- a/js/util.js +++ b/js/util.js @@ -61,10 +61,8 @@ var SN = { // StatusNet U: { // Utils FormNoticeEnhancements: function(form) { - form_id = form.attr('id'); - if (jQuery.data(form[0], 'ElementData') === undefined) { - MaxLength = $('#'+form_id+' #'+SN.C.S.NoticeTextCount).text(); + MaxLength = form.find('#'+SN.C.S.NoticeTextCount).text(); if (typeof(MaxLength) == 'undefined') { MaxLength = SN.C.I.MaxLength; } @@ -72,7 +70,7 @@ var SN = { // StatusNet SN.U.Counter(form); - NDT = $('#'+form_id+' #'+SN.C.S.NoticeDataText); + NDT = form.find('#'+SN.C.S.NoticeDataText); NDT.bind('keyup', function(e) { SN.U.Counter(form); @@ -83,11 +81,11 @@ var SN = { // StatusNet }); } else { - $('#'+form_id+' #'+SN.C.S.NoticeTextCount).text(jQuery.data(form[0], 'ElementData').MaxLength); + form.find('#'+SN.C.S.NoticeTextCount).text(jQuery.data(form[0], 'ElementData').MaxLength); } - if ($('body')[0].id != 'conversation') { - $('#'+form_id+' textarea').focus(); + if ($('body')[0].id != 'conversation' && window.location.hash.length === 0) { + form.find('textarea').focus(); } }, @@ -105,7 +103,6 @@ var SN = { // StatusNet Counter: function(form) { SN.C.I.FormNoticeCurrent = form; - form_id = form.attr('id'); var MaxLength = jQuery.data(form[0], 'ElementData').MaxLength; @@ -113,8 +110,8 @@ var SN = { // StatusNet return; } - var remaining = MaxLength - $('#'+form_id+' #'+SN.C.S.NoticeDataText).val().length; - var counter = $('#'+form_id+' #'+SN.C.S.NoticeTextCount); + var remaining = MaxLength - form.find('#'+SN.C.S.NoticeDataText).val().length; + var counter = form.find('#'+SN.C.S.NoticeTextCount); if (remaining.toString() != counter.text()) { if (!SN.C.I.CounterBlackout || remaining === 0) { @@ -174,7 +171,6 @@ var SN = { // StatusNet FormNoticeXHR: function(form) { SN.C.I.NoticeDataGeo = {}; - form_id = form.attr('id'); form.append(''); form.ajaxForm({ dataType: 'xml', @@ -403,58 +399,72 @@ var SN = { // StatusNet return; } - $.fn.jOverlay.options = { - method : 'GET', - data : '', - url : '', - color : '#000', - opacity : '0.6', - zIndex : 9999, - center : false, - imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', - bgClickToClose : true, - success : function() { - $('#jOverlayContent').append(''); - $('#jOverlayContent button').click($.closeOverlay); - }, - timeout : 0, - autoHide : true, - css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} - }; + var attachment_more = notice.find('.attachment.more'); + if (attachment_more.length > 0) { + $(attachment_more[0]).click(function() { + var m = $(this); + m.addClass(SN.C.S.Processing); + $.get(m.attr('href')+'/ajax', null, function(data) { + m.parent('.entry-content').html($(data).find('#attachment_view .entry-content').html()); + }); - notice.find('a.attachment').click(function() { - var attachId = ($(this).attr('id').substring('attachment'.length + 1)); - if (attachId) { - $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'}); return false; - } - }); - - if ($('#shownotice').length == 0) { - var t; - notice.find('a.thumbnail').hover( - function() { - var anchor = $(this); - $('a.thumbnail').children('img').hide(); - anchor.closest(".entry-title").addClass('ov'); - - if (anchor.children('img').length === 0) { - t = setTimeout(function() { - $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { - anchor.append(data); - }); - }, 500); - } - else { - anchor.children('img').show(); - } + }); + } + else { + $.fn.jOverlay.options = { + method : 'GET', + data : '', + url : '', + color : '#000', + opacity : '0.6', + zIndex : 9999, + center : false, + imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', + bgClickToClose : true, + success : function() { + $('#jOverlayContent').append(''); + $('#jOverlayContent button').click($.closeOverlay); }, - function() { - clearTimeout(t); - $('a.thumbnail').children('img').hide(); - $(this).closest('.entry-title').removeClass('ov'); + timeout : 0, + autoHide : true, + css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} + }; + + notice.find('a.attachment').click(function() { + var attachId = ($(this).attr('id').substring('attachment'.length + 1)); + if (attachId) { + $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'}); + return false; } - ); + }); + + if ($('#shownotice').length == 0) { + var t; + notice.find('a.thumbnail').hover( + function() { + var anchor = $(this); + $('a.thumbnail').children('img').hide(); + anchor.closest(".entry-title").addClass('ov'); + + if (anchor.children('img').length === 0) { + t = setTimeout(function() { + $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { + anchor.append(data); + }); + }, 500); + } + else { + anchor.children('img').show(); + } + }, + function() { + clearTimeout(t); + $('a.thumbnail').children('img').hide(); + $(this).closest('.entry-title').removeClass('ov'); + } + ); + } } }, diff --git a/lib/action.php b/lib/action.php index 491d7d4810..09113a598e 100644 --- a/lib/action.php +++ b/lib/action.php @@ -198,8 +198,7 @@ class Action extends HTMLOutputter // lawsuit if (Event::handle('StartShowStatusNetStyles', array($this)) && Event::handle('StartShowLaconicaStyles', array($this))) { - $this->cssLink('css/display.css',null,'screen, projection, tv'); - $this->cssLink('css/print.css','base','print'); + $this->cssLink('css/display.css',null, 'screen, projection, tv, print'); Event::handle('EndShowStatusNetStyles', array($this)); Event::handle('EndShowLaconicaStyles', array($this)); } diff --git a/lib/activity.php b/lib/activity.php index f9192c6b80..5d6230c6df 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -179,6 +179,17 @@ class Activity $this->actor = new ActivityObject($actorEl); + // Cliqset has bad actor IDs (just nickname of user). We + // work around it by getting the author data and using its + // id instead + + if (!preg_match('/^\w+:/', $this->actor->id)) { + $authorEl = ActivityUtils::child($entry, 'author'); + if (!empty($authorEl)) { + $authorObj = new ActivityObject($authorEl); + $this->actor->id = $authorObj->id; + } + } } else if (!empty($feed) && $subjectEl = $this->_child($feed, self::SUBJECT)) { diff --git a/lib/activityobject.php b/lib/activityobject.php index 34d1b91700..677a48197f 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -177,10 +177,7 @@ class ActivityObject $this->type = self::PERSON; // XXX: is this fair? $this->title = $this->_childContent($element, self::NAME); - $id = $this->_childContent($element, self::URI); - if (ActivityUtils::validateUri($id)) { - $this->id = $id; - } + $this->id = $this->_childContent($element, self::URI); if (empty($this->id)) { $email = $this->_childContent($element, self::EMAIL); @@ -193,15 +190,6 @@ class ActivityObject private function _fromAtomEntry($element) { - if ($element->localName == 'actor') { - // Old-fashioned ... - // First pull anything from , then we'll add on top. - $author = ActivityUtils::child($element->parentNode, 'author'); - if ($author) { - $this->_fromAuthor($author); - } - } - $this->type = $this->_childContent($element, Activity::OBJECTTYPE, Activity::SPEC); @@ -209,11 +197,6 @@ class ActivityObject $this->type = ActivityObject::NOTE; } - $id = $this->_childContent($element, self::ID); - if (ActivityUtils::validateUri($id)) { - $this->id = $id; - } - $this->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY); $this->content = ActivityUtils::getContent($element); @@ -226,6 +209,12 @@ class ActivityObject $this->source = $this->_getSource($element); $this->link = ActivityUtils::getPermalink($element); + + $this->id = $this->_childContent($element, self::ID); + + if (empty($this->id) && !empty($this->link)) { // fallback if there's no ID + $this->id = $this->link; + } } // @fixme rationalize with Activity::_fromRssItem() diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index fe38281af9..43f836e150 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -248,9 +248,7 @@ class Attachment extends AttachmentListItem $this->out->elementStart('div', array('id' => 'attachment_view', 'class' => 'hentry')); $this->out->elementStart('div', 'entry-title'); - $this->out->elementStart('a', $this->linkAttr()); - $this->out->element('span', null, $this->linkTitle()); - $this->out->elementEnd('a'); + $this->out->element('a', $this->linkAttr(), $this->linkTitle()); $this->out->elementEnd('div'); $this->out->elementStart('div', 'entry-content'); @@ -296,7 +294,7 @@ class Attachment extends AttachmentListItem } function linkAttr() { - return array('class' => 'external', 'href' => $this->attachment->url); + return array('rel' => 'external', 'href' => $this->attachment->url); } function linkTitle() { @@ -332,6 +330,15 @@ class Attachment extends AttachmentListItem $this->out->element('param', array('name' => 'autoStart', 'value' => 1)); $this->out->elementEnd('object'); break; + + case 'text/html': + if ($this->attachment->filename) { + // Locally-uploaded HTML. Scrub and display inline. + $this->showHtmlFile($this->attachment); + break; + } + // Fall through to default. + default: $this->showFallback(); } @@ -361,6 +368,59 @@ class Attachment extends AttachmentListItem } } + protected function showHtmlFile(File $attachment) + { + $body = $this->scrubHtmlFile($attachment); + if ($body) { + $this->out->raw($body); + } + } + + /** + * @return mixed false on failure, HTML fragment string on success + */ + protected function scrubHtmlFile(File $attachment) + { + $path = File::path($attachment->filename); + if (!file_exists($path) || !is_readable($path)) { + common_log(LOG_ERR, "Missing local HTML attachment $path"); + return false; + } + $raw = file_get_contents($path); + + // Normalize... + $dom = new DOMDocument(); + if(!$dom->loadHTML($raw)) { + common_log(LOG_ERR, "Bad HTML in local HTML attachment $path"); + return false; + } + + // Remove