## New this version
-This is a security fix and bug fix release since 1.1.0,
-released 2 July 2012. All 1.1.0 sites should upgrade to this version.
+This is a security fix and bug fix release since 1.1.1-alpha2.
+The current release base version, 1.1.2, began work on 2014-10-25.
+All 1.1.1 sites should upgrade to this version.
-It includes the following changes:
+So far it includes the following changes:
+
+- XSS security fix (thanks Simon Waters, https://www.surevine.com/ )
+
+Upgrades from _StatusNet_ 1.1.1 will also experience these improvements:
- Fixes for SQL injection errors in profile lists.
- Improved ActivityStreams JSON representation of activities and objects.
- Fix error in OStatus subscription for remote groups.
- Fix error in XMPP distribution.
+
### Troubleshooting
The primary output for GNU social is syslog,
### Unstable version
If you're adventurous or impatient, you may want
-to install the development version of
-StatusNet. To get it, use the git version control
-tool <http://git-scm.com/> like so:
+to install the development version of GNU social.
+To get it, use the git version control tool
+<http://git-scm.com/> like so:
git clone git@gitorious.org:social/mainline.git
-Using it is a mixed bag. On the positive side, it
-usually includes the latest security and bug fix
-patches. On the downside, it may also include
-changes that require admin intervention (like
-running a script or even raw SQL!) that may not be
-documented yet. It may be a good idea to test this
-version before installing it on your production
-machines.
+In the current phase of development it is probably
+recommended to use git as a means to stay up to date
+with the source code. You can choose between these
+branches:
+- 1.1.x "stable", few updates, well tested code
+- master "testing", more updates, usually working well
+- nightly "unstable", most updates, not always working
-To keep it up-to-date, use 'git pull'. Watch for
-conflicts!
+To keep it up-to-date, use 'git pull'. Watch for conflicts!
## Further information
-There are several ways to get more information
-about GNU social.
+There are several ways to get more information about GNU social.
* The #social IRC channel on freenode.net <http://www.freenode.net/>.
* The GNU social website
* Following us on GNU social -- http://quitter.se/gnusocial
-* Following us on Twitter -- https://twitter.com/gnusocial
* GNU social has a bug tracker for any defects you may find, or ideas for
making things better. http://bugz.foocorp.net/
+* Patches are welcome, either on the bug tracker or our repository at
+ Gitorious. https://gitorious.org/social/mainline
Credits
=======
* Brion Vibber, StatusNet, Inc.
* James Walker, StatusNet, Inc.
* Samantha Doherty, designer, StatusNet, Inc.
+* Simon Waters, Surevine
### Extra special thanks to the GNU socialites
function showContent() {
$this->areYouSureForm();
+ $block = new AccountProfileBlock($this, $this->profile);
+ $block->show();
}
function title() {
*/
class NewgroupAction extends FormAction
{
+ protected $group;
+
+ function getGroup() {
+ return $this->group;
+ }
+
function title()
{
// TRANS: Title for form to create a group.
$enclosure->$key = $this->$key;
}
- $needMoreMetadataMimetypes = array(null, 'text/html', 'application/xhtml+xml');
+ $needMoreMetadataMimetypes = array(null, 'application/xhtml+xml');
if (!isset($this->filename) && in_array(common_bare_mime($enclosure->mimetype), $needMoreMetadataMimetypes)) {
// This fetches enclosure metadata for non-local links with unset/HTML mimetypes,
$author->getNickname(),
$this->content);
+ $maxlen = self::maxContent();
+ if ($maxlen > 0 && mb_strlen($content) > $maxlen) {
+ // Web interface and current Twitter API clients will
+ // pull the original notice's text, but some older
+ // clients and RSS/Atom feeds will see this trimmed text.
+ //
+ // Unfortunately this is likely to lose tags or URLs
+ // at the end of long notices.
+ $content = mb_substr($content, 0, $maxlen - 4) . ' ...';
+ }
+
+
// Scope is same as this one's
return self::saveNew($repeater->id,
$content,
SN.U.NoticeInlineReplyTrigger(notice);
return false;
});
- $('li.notice-reply-comments a')
- .on('click', function () {
+ $(document).on('click', 'li.notice-reply-comments a', function () {
var url = $(this).attr('href');
var area = $(this).closest('.threaded-replies');
$.get(url, {ajax: 1}, function (data, textStatus, xhr) {
return;
}
- 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: 1}, function (data) {
- m.parent('.e-content').html($(data).find('#attachment_view .e-content').html());
- });
+ $(document).on('click','.attachment.more',function () {
+ var m = $(this);
+ m.addClass(SN.C.S.Processing);
+ $.get(m.attr('href'), {ajax: 1}, function (data) {
+ m.parent('.e-content').html($(data).find('#attachment_view .e-content').html());
+ });
+
+ return false;
+ });
- return false;
- }).attr('title', SN.msg('showmore_tooltip'));
- }
},
/**
{
$details = array();
+ $source_profile = $source->getProfile();
+ $target_profile = $target->getProfile();
+
$details['screen_name'] = $source->nickname;
- $details['followed_by'] = $target->isSubscribed($source);
- $details['following'] = $source->isSubscribed($target);
+ $details['followed_by'] = $target->isSubscribed($source_profile);
+ $details['following'] = $source->isSubscribed($target_profile);
$notifications = false;
- if ($source->isSubscribed($target)) {
+ if ($source->isSubscribed($target_profile)) {
$sub = Subscription::pkeyGet(array('subscriber' =>
$source->id, 'subscribed' => $target->id));
}
$details['notifications_enabled'] = $notifications;
- $details['blocking'] = $source->hasBlocked($target);
+ $details['blocking'] = $source->hasBlocked($target_profile);
$details['id'] = intval($source->id);
return $details;
define('GNUSOCIAL_ENGINE', 'GNU social');
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
-define('GNUSOCIAL_BASE_VERSION', '1.1.1');
-define('GNUSOCIAL_LIFECYCLE', 'alpha2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+define('GNUSOCIAL_BASE_VERSION', '1.1.2');
+define('GNUSOCIAL_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
return $options;
}
+
+ function getGroup()
+ {
+ return $this->group;
+ }
}
class GroupAdminSection extends ProfileSection
*
* @param Action $action current action, used for output
*/
- function __construct($action=null)
+ function __construct(Action $action=null)
{
parent::__construct($action);
}
}
- function item($actionName, $args, $label, $description, $id=null, $cls=null)
+ function item($actionName, array $args, $label, $description, $id=null, $cls=null)
{
if (empty($id)) {
$id = $this->menuItemID($actionName, $args);
$cls);
}
- function isCurrent($actionName, $args)
+ function isCurrent($actionName, array $args)
{
if ($actionName != $this->actionName) {
return false;
*
* @param Action $action current action, used for output
*/
- function __construct($action=null)
+ function __construct(Action $action=null)
{
parent::__construct($action);
$this->action = $action;
*
* @param Action $action current action, used for output
*/
- function __construct($action=null, $q = null)
+ function __construct(Action $action=null, $q = null)
{
parent::__construct($action);
$this->q = $q;
* Construction
*
* @param Action $action current action, used for output
+ * @param User $user Current user or NULL if "guest"
*/
- function __construct($action=null, $user=null)
+ function __construct(Action $action=null, User $user=null)
{
parent::__construct($action);
$this->user = $user;
static function saveNew($profile, $title, $url, $rawtags, $description,
$options=null)
{
+ if (!common_valid_http_url($url)) {
+ throw new ClientException(_m('Only web bookmarks can be posted (HTTP or HTTPS).'));
+ }
+
$nb = self::getByURL($profile, $url);
if (!empty($nb)) {
function onRouterInitialized($m)
{
+ $m->connect(
+ 'directory/users/:filter/sort_by/:sort/reverse/:reverse',
+ array('action' => 'userdirectory'),
+ array('filter' => '[0-9a-zA-Z]|(0-9)'),
+ array('sort' => '[a-z]+'),
+ array('reverse' => '[0-9]')
+ );
+
+ $m->connect(
+ 'directory/users/:filter/sort_by/:sort',
+ array('action' => 'userdirectory'),
+ array('filter' => '[0-9a-zA-Z]|(0-9)'),
+ array('sort' => '[a-z]+')
+ );
+
+
$m->connect(
'directory/users/:filter',
array('action' => 'userdirectory'),
array('filter' => '[0-9a-zA-Z]|(0-9)')
);
+
+ $m->connect(
+ 'directory/users/sort_by/:sort/reverse/:reverse',
+ array('action' => 'userdirectory'),
+ array('sort' => '[a-z]+'),
+ array('reverse' => '[0-9]')
+ );
+
+ $m->connect(
+ 'directory/users/sort_by/:sort',
+ array('action' => 'userdirectory'),
+ array('sort' => '[a-z]+')
+ );
$m->connect(
'directory/users',
array('action' => 'groupdirectory')
);
+ $m->connect(
+ 'groups/all',
+ array('action' => 'groupdirectory')
+ );
+
+
return true;
}
} elseif (isset($this->filter) && $this->filter != 'all') {
$args['filter'] = $this->filter;
}
+
+ if (isset($this->sort)) {
+ $args['sort'] = $this->sort;
+ }
+ if (!empty($this->reverse)) {
+ $args['reverse'] = $this->reverse;
+ }
$this->pagination(
$this->page > 1,
function endActions()
{
+
+ // delete button
+ $cur = common_current_user();
+ list($action, $r2args) = $this->out->returnToArgs();
+ $r2args['action'] = $action;
+ if ($cur instanceof User && $cur->hasRight(Right::DELETEUSER)) {
+ $this->out->elementStart('li', 'entity_delete');
+ $df = new DeleteUserForm($this->out, $this->profile, $r2args);
+ $df->show();
+ $this->out->elementEnd('li');
+ }
+
$this->out->elementEnd('ul');
$this->out->elementEnd('td');
}
throw new AlreadyFulfilledException(_('You have already favorited this!'));
}
- $now = common_sql_now();
+ $fave = Fave::addNew($this->scoped, $this->target);
- $act = new Activity();
- $act->id = Fave::newUri($this->scoped, $this->target, $now);
- $act->type = Fave::getObjectType();
- $act->actor = $this->scoped->asActivityObject();
- $act->target = $this->target->asActivityObject();
- $act->objects = array(clone($act->target));
- $act->verb = ActivityVerb::FAVORITE;
- $act->title = ActivityUtils::verbToTitle($act->verb);
- $act->time = strtotime($now);
- // TRANS: Notification given when a user marks a notice as favorite.
- // TRANS: %1$s is a user name or full name, %2$s is a notice URI, %3$s the link to the user's profile.
- $act->content = sprintf(_('<a href="%3$s">%1$s</a> marked notice <a href="%2$s">%2$s</a> as a favorite.'),
- htmlspecialchars($this->scoped->getBestName()),
- htmlspecialchars($this->target->getUrl()),
- htmlspecialchars($this->scoped->getUrl()));
-
-
- $stored = Notice::saveActivity($act, $this->scoped,
- array('uri'=>$act->id));
+ if (empty($fave)) {
+ $this->serverError(_('Could not create favorite.'));
+ }
+ $this->notify($fave, $this->target, $this->scoped);
Fave::blowCacheForProfileId($this->scoped->id);
return _('Favorited the notice');
$disfavor->show();
}
}
+
+ /**
+ * Notify the author of the favorite that the user likes their notice
+ *
+ * @param Favorite $fave the favorite in question
+ * @param Notice $notice the notice that's been faved
+ * @param User $user the user doing the favoriting
+ *
+ * @return void
+ */
+ function notify($fave, $notice, $user)
+ {
+ $other = User::getKV('id', $notice->profile_id);
+ if ($other && $other->id != $user->id && !empty($other->email)) {
+ require_once INSTALLDIR.'/lib/mail.php';
+
+ mail_notify_fave($other, $user->getProfile(), $notice);
+ // XXX: notify by IM
+ // XXX: notify by SMS
+ }
+ }
}
* @param Net_URL_Mapper $m path-to-action mapper
* @return boolean hook return
*/
- function onRouterInitialized($m)
+ function onRouterInitialized(Net_URL_Mapper $m)
{
$m->connect('group/:nickname/favorited',
array('action' => 'groupfavorited'),
return true;
}
- function onEndGroupGroupNav(GroupNav $nav)
+ function onEndGroupGroupNav(Menu $nav)
{
$action_name = $nav->action->trimmed('action');
$nickname = $nav->group->nickname;
*
* @return boolean hook value
*/
- function onPluginVersion(&$versions)
+ function onPluginVersion(array &$versions)
{
$url = 'http://status.net/wiki/Plugin:GroupFavorited';
*
* @see Action
*/
- function onEndGroupGroupNav($groupnav)
+ function onEndGroupGroupNav(Menu $groupnav)
{
$action = $groupnav->action;
$group = $groupnav->group;
*
* @param GroupEditForm $form form being shown
*/
- function onEndGroupEditFormData($form)
+ function onEndGroupEditFormData(GroupEditForm $form)
{
$gps = null;
return true;
}
- function onEndGroupSaveForm($action)
+ function onEndGroupSaveForm(Action $action)
{
+ // The Action class must contain this method
+ assert(is_callable(array($action, 'getGroup')));
+
$gps = null;
- if (!empty($action->group)) {
- $gps = Group_privacy_settings::getKV('group_id', $action->group->id);
+ if ($action->getGroup() instanceof User_group) {
+ $gps = Group_privacy_settings::getKV('group_id', $action->getGroup()->id);
}
$orig = null;
if (empty($gps)) {
$gps = new Group_privacy_settings();
- $gps->group_id = $action->group->id;
+ $gps->group_id = $action->getGroup()->id;
} else {
$orig = clone($gps);
}
*
* @return boolean hook value
*/
- function onStartInterpretCommand($cmd, $arg, $user, &$result)
+ function onStartInterpretCommand($cmd, $arg, User $user, &$result)
{
if ($cmd == 'd' || $cmd == 'dm') {
*
* @return boolean hook value
*/
- function onEndGroupActionsList($widget, $group)
+ function onEndGroupActionsList(Widget $widget, User_group $group)
{
$cur = common_current_user();
$action = $widget->out;
*
* @param
*/
- function onStartNoticeSave(&$notice) {
+ function onStartNoticeSave(Notice &$notice) {
// Look for group tags
// FIXME: won't work for remote groups
// @fixme if Notice::saveNew is refactored so we can just pull its list
*
* @return boolean hook value
*/
- function onEndGroupProfileElements($action, $group)
+ function onEndGroupProfileElements(Action $action, User_group $group)
{
$gps = Group_privacy_settings::forGroup($group);
return true;
}
- function onStartShowExportData($action)
+ function onStartShowExportData(Action $action)
{
if ($action instanceof ShowgroupAction) {
- $gps = Group_privacy_settings::forGroup($action->group);
+ $gps = Group_privacy_settings::forGroup($action->getGroup());
if ($gps->allow_privacy == Group_privacy_settings::ALWAYS) {
return false;
+
+// notices
jQuery(document).ready(function($){
$('notices_primary').infinitescroll({
debug: false,
SN.U.NoticeReplyTo($(this));
SN.U.NoticeWithAttachment($(this));
});
+
+ // moving the loaded notices out of their container
+ $('#infscr-loading').remove();
+ var ids_to_append = Array(); var i=0;
+ $.each($('.infscr-pages').children('.notice'),function(){
+
+ // remove dupes
+ if($('.threaded-notices > #' + $(this).attr('id')).length > 0) {
+ $(this).remove();
+ }
+
+ // keep new unique notices
+ else {
+ ids_to_append[i] = $(this).attr('id');
+ i++;
+ }
+ });
+ var loaded_html = $('.infscr-pages').html();
+ $('.infscr-pages').remove();
+
+ // no results
+ if(loaded_html == '') {
+ }
+ // append
+ else {
+ $('#notices_primary ol.notices').append(loaded_html);
+ }
+
});
});
+
+
+// users
+jQuery(document).ready(function($){
+ $('profile_list').infinitescroll({
+ debug: false,
+ infiniteScroll : !infinite_scroll_on_next_only,
+ nextSelector : 'body#subscribers li.nav_next a, body#subscriptions li.nav_next a',
+ loadingImg : ajax_loader_url,
+ text : "<em>Loading the next set of users...</em>",
+ donetext : "<em>Congratulations, you\'ve reached the end of the Internet.</em>",
+ navSelector : "#pagination",
+ contentSelector : "#content_inner ul.profile_list",
+ itemSelector : "#content_inner ul.profile_list > li"
+ },function(){
+ // Reply button and attachment magic need to be set up
+ // for each new notice.
+ // DO NOT run SN.Init.Notices() which will duplicate stuff.
+ $(this).find('.profile').each(function() {
+ SN.U.NoticeReplyTo($(this));
+ SN.U.NoticeWithAttachment($(this));
+ });
+
+ // moving the loaded notices out of their container
+ $('#infscr-loading').remove();
+ var ids_to_append = Array(); var i=0;
+ $.each($('.infscr-pages').children('.profile'),function(){
+
+ // remove dupes
+ if($('.profile_list > #' + $(this).attr('id')).length > 0) {
+ $(this).remove();
+ }
+
+ // keep new unique notices
+ else {
+ ids_to_append[i] = $(this).attr('id');
+ i++;
+ }
+ });
+ var loaded_html = $('.infscr-pages').html();
+ $('.infscr-pages').remove();
+
+ // no results
+ if(loaded_html == '') {
+ }
+ // append
+ else {
+ $('#content_inner ul.profile_list').append(loaded_html);
+ }
+
+ });
+});
+
+
+// user directory
+jQuery(document).ready(function($){
+ $('profile_list').infinitescroll({
+ debug: false,
+ infiniteScroll : !infinite_scroll_on_next_only,
+ nextSelector : 'body#userdirectory li.nav_next a',
+ loadingImg : ajax_loader_url,
+ text : "<em>Loading the next set of users...</em>",
+ donetext : "<em>Congratulations, you\'ve reached the end of the Internet.</em>",
+ navSelector : "#pagination",
+ contentSelector : "#profile_directory table.profile_list tbody",
+ itemSelector : "#profile_directory table.profile_list tbody tr"
+ },function(){
+ // Reply button and attachment magic need to be set up
+ // for each new notice.
+ // DO NOT run SN.Init.Notices() which will duplicate stuff.
+ $(this).find('.profile').each(function() {
+ SN.U.NoticeReplyTo($(this));
+ SN.U.NoticeWithAttachment($(this));
+ });
+
+ // moving the loaded notices out of their container
+ $('#infscr-loading').remove();
+ var ids_to_append = Array(); var i=0;
+ $.each($('.infscr-pages').children('.profile'),function(){
+
+ // remove dupes
+ if($('.profile_list > #' + $(this).attr('id')).length > 0) {
+ $(this).remove();
+ }
+
+ // keep new unique notices
+ else {
+ ids_to_append[i] = $(this).attr('id');
+ i++;
+ }
+ });
+ var loaded_html = $('.infscr-pages').html();
+ $('.infscr-pages').remove();
+
+ // no results
+ if(loaded_html == '') {
+ }
+ // append
+ else {
+ $('#profile_directory table.profile_list tbody').append(loaded_html);
+ }
+
+ });
+});
\ No newline at end of file
$oprofile = $oprofile->checkAuthorship($activity);
$profile = $oprofile->localProfile();
} catch (Exception $e) {
- common_log(LOG_ERR, 'Could not get a profile or check authorship ('.get_class($e).': "'.$e->getMessage().'")');
+ common_log(LOG_ERR, 'Could not get a profile or check authorship ('.get_class($e).': "'.$e->getMessage().'") for activity ID: '.$activity->id);
$profile = null;
return false;
}