forked from GNUsocial/gnu-social
Merge branch 'nightly' into singpolyma/gnu-social-events-saveObjectFromActivity
Conflicts: plugins/Event/EventPlugin.php plugins/Event/classes/RSVP.php I just fixed 'em with magic!
This commit is contained in:
commit
95d415257a
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
avatar/*
|
||||
background/*
|
||||
files/*
|
||||
file/*
|
||||
local/*
|
||||
|
14
CONFIGURE
14
CONFIGURE
@ -632,20 +632,6 @@ notify third-party servers of updates.
|
||||
notify: an array of URLs for ping endpoints. Default is the empty
|
||||
array (no notification).
|
||||
|
||||
design
|
||||
------
|
||||
|
||||
Default design (colors and background) for the site. Actual appearance
|
||||
depends on the theme. Null values mean to use the theme defaults.
|
||||
|
||||
backgroundcolor: Hex color of the site background.
|
||||
contentcolor: Hex color of the content area background.
|
||||
sidebarcolor: Hex color of the sidebar background.
|
||||
textcolor: Hex color of all non-link text.
|
||||
linkcolor: Hex color of all links.
|
||||
backgroundimage: Image to use for the background.
|
||||
disposition: Flags for whether or not to tile the background image.
|
||||
|
||||
notice
|
||||
------
|
||||
|
||||
|
10
UPGRADE
10
UPGRADE
@ -27,13 +27,13 @@ and follow this procedure:
|
||||
The upgrade script will likely take a long time because it will
|
||||
upgrade the tables to another character encoding and make other
|
||||
automated upgrades. Make sure it ends without errors. If you get
|
||||
errors, create a new task on https://bugz.foocorp.net/
|
||||
errors, create a new task on https://git.gnu.io/gnu/gnu-social/issues
|
||||
|
||||
4. Start your queue daemons again (you can run this command even if you
|
||||
do not use the queue daemons):
|
||||
$ bash scripts/startdaemons.sh
|
||||
|
||||
5. Report any issues at https://bugz.foocorp.net/ (tag GNU social)
|
||||
5. Report any issues at https://git.gnu.io/gnu/gnu-social/issues
|
||||
|
||||
If you are using ssh keys to log in to your server, you can make this
|
||||
procedure pretty painless (assuming you have automated backups already).
|
||||
@ -69,7 +69,7 @@ variant of this command (you will be prompted for the database password):
|
||||
|
||||
2. Unpack your GNU social code to a fresh directory. You can do this
|
||||
by cloning our git repository:
|
||||
$ git clone https://gitorious.org/social/mainline.git gnusocial
|
||||
$ git clone https://git.gnu.io/gnu/gnu-social.git gnusocial
|
||||
|
||||
3. Synchronize your local files to the GNU social directory. These
|
||||
will be the local files such as avatars, config and files:
|
||||
@ -91,8 +91,8 @@ variant of this command (you will be prompted for the database password):
|
||||
The upgrade script will likely take a long time because it will
|
||||
upgrade the tables to another character encoding and make other
|
||||
automated upgrades. Make sure it ends without errors. If you get
|
||||
errors, create a new task on https://bugz.foocorp.net/
|
||||
errors, create a new task on https://git.gnu.io/gnu/gnu-social/issues
|
||||
|
||||
6. Start your queue daemons: 'bash scripts/startdaemons.sh'
|
||||
|
||||
7. Report any issues at https://bugz.foocorp.net/ (tag GNU social)
|
||||
7. Report any issues at https://git.gnu.io/gnu/gnu-social/issues
|
||||
|
@ -30,7 +30,7 @@ if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Upload an image via the API. Returns a shortened URL for the image
|
||||
* to the user.
|
||||
* to the user. Apparently modelled after a former Twitpic API.
|
||||
*
|
||||
* @category API
|
||||
* @package StatusNet
|
||||
@ -91,9 +91,14 @@ class ApiMediaUploadAction extends ApiAuthAction
|
||||
function showResponse(MediaFile $upload)
|
||||
{
|
||||
$this->initDocument();
|
||||
$this->elementStart('rsp', array('stat' => 'ok'));
|
||||
$this->elementStart('rsp', array('stat' => 'ok', 'xmlns:atom'=>Activity::ATOM));
|
||||
$this->element('mediaid', null, $upload->fileRecord->id);
|
||||
$this->element('mediaurl', null, $upload->shortUrl());
|
||||
|
||||
$enclosure = $upload->fileRecord->getEnclosure();
|
||||
$this->element('atom:link', array('rel' => 'enclosure',
|
||||
'href' => $enclosure->url,
|
||||
'type' => $enclosure->mimetype));
|
||||
$this->elementEnd('rsp');
|
||||
$this->endDocument();
|
||||
}
|
||||
@ -103,7 +108,7 @@ class ApiMediaUploadAction extends ApiAuthAction
|
||||
*
|
||||
* @param String $msg an error message
|
||||
*/
|
||||
function clientError($msg)
|
||||
function clientError($msg, $code=400, $format=null)
|
||||
{
|
||||
$this->initDocument();
|
||||
$this->elementStart('rsp', array('stat' => 'fail'));
|
||||
@ -114,5 +119,6 @@ class ApiMediaUploadAction extends ApiAuthAction
|
||||
$this->element('err', $errAttr, null);
|
||||
$this->elementEnd('rsp');
|
||||
$this->endDocument();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ class ApiStatusesDestroyAction extends ApiAuthAction
|
||||
|
||||
if ($this->user->id == $this->notice->profile_id) {
|
||||
if (Event::handle('StartDeleteOwnNotice', array($this->user, $this->notice))) {
|
||||
$this->notice->delete();
|
||||
$this->notice->deleteAs($this->scoped);
|
||||
Event::handle('EndDeleteOwnNotice', array($this->user, $this->notice));
|
||||
}
|
||||
$this->showNotice();
|
||||
|
@ -236,7 +236,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction
|
||||
}
|
||||
|
||||
if (Event::handle('StartDeleteOwnNotice', array($this->auth_user, $this->notice))) {
|
||||
$this->notice->delete();
|
||||
$this->notice->deleteAs($this->scoped);
|
||||
Event::handle('EndDeleteOwnNotice', array($this->auth_user, $this->notice));
|
||||
}
|
||||
|
||||
|
@ -34,9 +34,7 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Returns the most recent notices (default 20) posted by the authenticating
|
||||
@ -115,11 +113,11 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
{
|
||||
// We'll use the shared params from the Atom stub
|
||||
// for other feed types.
|
||||
$atom = new AtomUserNoticeFeed($this->target->getUser(), $this->auth_user);
|
||||
$atom = new AtomUserNoticeFeed($this->target->getUser(), $this->scoped);
|
||||
|
||||
$link = common_local_url(
|
||||
'showstream',
|
||||
array('nickname' => $this->target->nickname)
|
||||
array('nickname' => $this->target->getNickname())
|
||||
);
|
||||
|
||||
$self = $this->getSelfUri();
|
||||
@ -127,7 +125,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
// FriendFeed's SUP protocol
|
||||
// Also added RSS and Atom feeds
|
||||
|
||||
$suplink = common_local_url('sup', null, null, $this->target->id);
|
||||
$suplink = common_local_url('sup', null, null, $this->target->getID());
|
||||
header('X-SUP-ID: ' . $suplink);
|
||||
|
||||
|
||||
@ -135,7 +133,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
$nextUrl = !empty($this->next_id)
|
||||
? common_local_url('ApiTimelineUser',
|
||||
array('format' => $this->format,
|
||||
'id' => $this->target->id),
|
||||
'id' => $this->target->getID()),
|
||||
array('max_id' => $this->next_id))
|
||||
: null;
|
||||
|
||||
@ -147,11 +145,11 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
|
||||
$prevUrl = common_local_url('ApiTimelineUser',
|
||||
array('format' => $this->format,
|
||||
'id' => $this->target->id),
|
||||
'id' => $this->target->getID()),
|
||||
$prevExtra);
|
||||
$firstUrl = common_local_url('ApiTimelineUser',
|
||||
array('format' => $this->format,
|
||||
'id' => $this->target->id));
|
||||
'id' => $this->target->getID()));
|
||||
|
||||
switch($this->format) {
|
||||
case 'xml':
|
||||
@ -206,7 +204,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
break;
|
||||
case 'as':
|
||||
header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
|
||||
$doc = new ActivityStreamJSONDocument($this->auth_user);
|
||||
$doc = new ActivityStreamJSONDocument($this->scoped);
|
||||
$doc->setTitle($atom->title);
|
||||
$doc->addLink($link, 'alternate', 'text/html');
|
||||
$doc->addItemsFromNotices($this->notices);
|
||||
@ -307,9 +305,9 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
return '"' . implode(
|
||||
':',
|
||||
array($this->arg('action'),
|
||||
common_user_cache_hash($this->auth_user),
|
||||
common_user_cache_hash($this->scoped),
|
||||
common_language(),
|
||||
$this->target->id,
|
||||
$this->target->getID(),
|
||||
strtotime($this->notices[0]->created),
|
||||
strtotime($this->notices[$last]->created))
|
||||
)
|
||||
@ -321,10 +319,10 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
|
||||
function handlePost()
|
||||
{
|
||||
if (empty($this->auth_user) ||
|
||||
$this->auth_user->id != $this->target->id) {
|
||||
if (!$this->scoped instanceof Profile ||
|
||||
!$this->target->sameAs($this->scoped)) {
|
||||
// TRANS: Client error displayed trying to add a notice to another user's timeline.
|
||||
$this->clientError(_('Only the user can add to their own timeline.'));
|
||||
$this->clientError(_('Only the user can add to their own timeline.'), 403);
|
||||
}
|
||||
|
||||
// Only handle posts for Atom
|
||||
@ -356,156 +354,28 @@ class ApiTimelineUserAction extends ApiBareAuthAction
|
||||
|
||||
$activity = new Activity($dom->documentElement);
|
||||
|
||||
$saved = null;
|
||||
common_debug('AtomPub: Ignoring right now, but this POST was made to collection: '.$activity->id);
|
||||
|
||||
if (Event::handle('StartAtomPubNewActivity', array(&$activity, $this->target->getUser(), &$saved))) {
|
||||
if ($activity->verb != ActivityVerb::POST) {
|
||||
// TRANS: Client error displayed when not using the POST verb. Do not translate POST.
|
||||
$this->clientError(_('Can only handle POST activities.'));
|
||||
}
|
||||
// Reset activity data so we can handle it in the same functions as with OStatus
|
||||
// because we don't let clients set their own UUIDs... Not sure what AtomPub thinks
|
||||
// about that though.
|
||||
$activity->id = null;
|
||||
$activity->actor = null; // not used anyway, we use $this->target
|
||||
$activity->objects[0]->id = null;
|
||||
|
||||
$note = $activity->objects[0];
|
||||
|
||||
if (!in_array($note->type, array(ActivityObject::NOTE,
|
||||
ActivityObject::BLOGENTRY,
|
||||
ActivityObject::STATUS))) {
|
||||
// TRANS: Client error displayed when using an unsupported activity object type.
|
||||
// TRANS: %s is the unsupported activity object type.
|
||||
$this->clientError(sprintf(_('Cannot handle activity object type "%s".'),
|
||||
$note->type));
|
||||
}
|
||||
|
||||
$saved = $this->postNote($activity);
|
||||
|
||||
Event::handle('EndAtomPubNewActivity', array($activity, $this->target->getUser(), $saved));
|
||||
$stored = null;
|
||||
if (Event::handle('StartAtomPubNewActivity', array($activity, $this->target, &$stored))) {
|
||||
// TRANS: Client error displayed when not using the POST verb. Do not translate POST.
|
||||
throw new ClientException(_('Could not handle this Atom Activity.'));
|
||||
}
|
||||
|
||||
if (!empty($saved)) {
|
||||
header('HTTP/1.1 201 Created');
|
||||
header("Location: " . common_local_url('ApiStatusesShow', array('id' => $saved->id,
|
||||
'format' => 'atom')));
|
||||
$this->showSingleAtomStatus($saved);
|
||||
if (!$stored instanceof Notice) {
|
||||
throw new ServerException('Server did not create a Notice object from handled AtomPub activity.');
|
||||
}
|
||||
}
|
||||
Event::handle('EndAtomPubNewActivity', array($activity, $this->target, $stored));
|
||||
|
||||
function postNote($activity)
|
||||
{
|
||||
$note = $activity->objects[0];
|
||||
|
||||
// Use summary as fallback for content
|
||||
|
||||
if (!empty($note->content)) {
|
||||
$sourceContent = $note->content;
|
||||
} else if (!empty($note->summary)) {
|
||||
$sourceContent = $note->summary;
|
||||
} else if (!empty($note->title)) {
|
||||
$sourceContent = $note->title;
|
||||
} else {
|
||||
// @fixme fetch from $sourceUrl?
|
||||
// TRANS: Client error displayed when posting a notice without content through the API.
|
||||
// TRANS: %d is the notice ID (number).
|
||||
$this->clientError(sprintf(_('No content for notice %d.'), $note->id));
|
||||
}
|
||||
|
||||
// Get (safe!) HTML and text versions of the content
|
||||
|
||||
$rendered = common_purify($sourceContent);
|
||||
$content = common_strip_html($rendered);
|
||||
|
||||
$shortened = $this->auth_user->shortenLinks($content);
|
||||
|
||||
$options = array('is_local' => Notice::LOCAL_PUBLIC,
|
||||
'rendered' => $rendered,
|
||||
'replies' => array(),
|
||||
'groups' => array(),
|
||||
'tags' => array(),
|
||||
'urls' => array());
|
||||
|
||||
// accept remote URI (not necessarily a good idea)
|
||||
|
||||
common_debug("Note ID is {$note->id}");
|
||||
|
||||
if (!empty($note->id)) {
|
||||
$notice = Notice::getKV('uri', trim($note->id));
|
||||
|
||||
if (!empty($notice)) {
|
||||
// TRANS: Client error displayed when using another format than AtomPub.
|
||||
// TRANS: %s is the notice URI.
|
||||
$this->clientError(sprintf(_('Notice with URI "%s" already exists.'), $note->id));
|
||||
}
|
||||
common_log(LOG_NOTICE, "Saving client-supplied notice URI '$note->id'");
|
||||
$options['uri'] = $note->id;
|
||||
}
|
||||
|
||||
// accept remote create time (also maybe not such a good idea)
|
||||
|
||||
if (!empty($activity->time)) {
|
||||
common_log(LOG_NOTICE, "Saving client-supplied create time {$activity->time}");
|
||||
$options['created'] = common_sql_date($activity->time);
|
||||
}
|
||||
|
||||
// Check for optional attributes...
|
||||
|
||||
if ($activity->context instanceof ActivityContext) {
|
||||
|
||||
foreach ($activity->context->attention as $uri=>$type) {
|
||||
try {
|
||||
$profile = Profile::fromUri($uri);
|
||||
if ($profile->isGroup()) {
|
||||
$options['groups'][] = $profile->id;
|
||||
} else {
|
||||
$options['replies'][] = $uri;
|
||||
}
|
||||
} catch (UnknownUriException $e) {
|
||||
common_log(LOG_WARNING, sprintf('AtomPub post with unknown attention URI %s', $uri));
|
||||
}
|
||||
}
|
||||
|
||||
// Maintain direct reply associations
|
||||
// @fixme what about conversation ID?
|
||||
|
||||
if (!empty($activity->context->replyToID)) {
|
||||
$orig = Notice::getKV('uri',
|
||||
$activity->context->replyToID);
|
||||
if (!empty($orig)) {
|
||||
$options['reply_to'] = $orig->id;
|
||||
}
|
||||
}
|
||||
|
||||
$location = $activity->context->location;
|
||||
|
||||
if ($location) {
|
||||
$options['lat'] = $location->lat;
|
||||
$options['lon'] = $location->lon;
|
||||
if ($location->location_id) {
|
||||
$options['location_ns'] = $location->location_ns;
|
||||
$options['location_id'] = $location->location_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atom categories <-> hashtags
|
||||
|
||||
foreach ($activity->categories as $cat) {
|
||||
if ($cat->term) {
|
||||
$term = common_canonical_tag($cat->term);
|
||||
if ($term) {
|
||||
$options['tags'][] = $term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atom enclosures -> attachment URLs
|
||||
foreach ($activity->enclosures as $href) {
|
||||
// @fixme save these locally or....?
|
||||
$options['urls'][] = $href;
|
||||
}
|
||||
|
||||
$saved = Notice::saveNew($this->target->id,
|
||||
$content,
|
||||
'atompub', // TODO: deal with this
|
||||
$options);
|
||||
|
||||
return $saved;
|
||||
header('HTTP/1.1 201 Created');
|
||||
header("Location: " . common_local_url('ApiStatusesShow', array('id' => $stored->getID(),
|
||||
'format' => 'atom')));
|
||||
$this->showSingleAtomStatus($stored);
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class DeletenoticeAction extends FormAction
|
||||
{
|
||||
if ($this->arg('yes')) {
|
||||
if (Event::handle('StartDeleteOwnNotice', array($this->scoped->getUser(), $this->notice))) {
|
||||
$this->notice->delete();
|
||||
$this->notice->deleteAs($this->scoped);
|
||||
Event::handle('EndDeleteOwnNotice', array($this->scoped->getUser(), $this->notice));
|
||||
}
|
||||
} else {
|
||||
|
@ -96,7 +96,7 @@ class NewnoticeAction extends FormAction
|
||||
assert($this->scoped instanceof Profile); // XXX: maybe an error instead...
|
||||
$user = $this->scoped->getUser();
|
||||
$content = $this->trimmed('status_textarea');
|
||||
$options = array();
|
||||
$options = array('source' => 'web');
|
||||
Event::handle('StartSaveNewNoticeWeb', array($this, $user, &$content, &$options));
|
||||
|
||||
if (empty($content)) {
|
||||
@ -117,31 +117,30 @@ class NewnoticeAction extends FormAction
|
||||
return;
|
||||
}
|
||||
|
||||
$content_shortened = $user->shortenLinks($content);
|
||||
if (Notice::contentTooLong($content_shortened)) {
|
||||
// TRANS: Client error displayed when the parameter "status" is missing.
|
||||
// TRANS: %d is the maximum number of character for a notice.
|
||||
$this->clientError(sprintf(_m('That\'s too long. Maximum notice size is %d character.',
|
||||
'That\'s too long. Maximum notice size is %d characters.',
|
||||
Notice::maxContent()),
|
||||
Notice::maxContent()));
|
||||
if ($this->int('inreplyto')) {
|
||||
// Throws exception if the inreplyto Notice is given but not found.
|
||||
$parent = Notice::getByID($this->int('inreplyto'));
|
||||
} else {
|
||||
$parent = null;
|
||||
}
|
||||
|
||||
$replyto = $this->int('inreplyto');
|
||||
if ($replyto) {
|
||||
$options['reply_to'] = $replyto;
|
||||
}
|
||||
$act = new Activity();
|
||||
$act->verb = ActivityVerb::POST;
|
||||
$act->time = time();
|
||||
$act->actor = $this->scoped->asActivityObject();
|
||||
|
||||
$content = $this->scoped->shortenLinks($content);
|
||||
|
||||
$upload = null;
|
||||
try {
|
||||
// throws exception on failure
|
||||
$upload = MediaFile::fromUpload('attach', $this->scoped);
|
||||
if (Event::handle('StartSaveNewNoticeAppendAttachment', array($this, $upload, &$content_shortened, &$options))) {
|
||||
$content_shortened .= ' ' . $upload->shortUrl();
|
||||
if (Event::handle('StartSaveNewNoticeAppendAttachment', array($this, $upload, &$content, &$options))) {
|
||||
$content .= ' ' . $upload->shortUrl();
|
||||
}
|
||||
Event::handle('EndSaveNewNoticeAppendAttachment', array($this, $upload, &$content_shortened, &$options));
|
||||
Event::handle('EndSaveNewNoticeAppendAttachment', array($this, $upload, &$content, &$options));
|
||||
|
||||
if (Notice::contentTooLong($content_shortened)) {
|
||||
if (Notice::contentTooLong($content)) {
|
||||
$upload->delete();
|
||||
// TRANS: Client error displayed exceeding the maximum notice length.
|
||||
// TRANS: %d is the maximum length for a notice.
|
||||
@ -150,10 +149,25 @@ class NewnoticeAction extends FormAction
|
||||
Notice::maxContent()),
|
||||
Notice::maxContent()));
|
||||
}
|
||||
|
||||
$act->enclosures[] = $upload->getEnclosure();
|
||||
} catch (NoUploadedMediaException $e) {
|
||||
// simply no attached media to the new notice
|
||||
}
|
||||
|
||||
$actobj = new ActivityObject();
|
||||
$actobj->type = ActivityObject::NOTE;
|
||||
$actobj->content = common_render_content($content, $this->scoped, $parent);
|
||||
|
||||
$act->objects[] = $actobj;
|
||||
|
||||
|
||||
$act->context = new ActivityContext();
|
||||
|
||||
if ($parent instanceof Notice) {
|
||||
$act->context->replyToID = $parent->getUri();
|
||||
$act->context->replyToUrl = $parent->getUrl(true); // maybe we don't have to send true here to force a URL?
|
||||
}
|
||||
|
||||
if ($this->scoped->shareLocation()) {
|
||||
// use browser data if checked; otherwise profile data
|
||||
@ -171,19 +185,20 @@ class NewnoticeAction extends FormAction
|
||||
$this->scoped);
|
||||
}
|
||||
|
||||
$options = array_merge($options, $locOptions);
|
||||
$act->context->location = Location::fromOptions($locOptions);
|
||||
}
|
||||
|
||||
$author_id = $this->scoped->id;
|
||||
$text = $content_shortened;
|
||||
$text = $content;
|
||||
|
||||
// Does the heavy-lifting for getting "To:" information
|
||||
|
||||
ToSelector::fillOptions($this, $options);
|
||||
|
||||
// FIXME: Make sure NoticeTitle plugin gets a change to add the title to our activityobject!
|
||||
if (Event::handle('StartNoticeSaveWeb', array($this, &$author_id, &$text, &$options))) {
|
||||
|
||||
$this->stored = Notice::saveNew($this->scoped->id, $content_shortened, 'web', $options);
|
||||
$this->stored = Notice::saveActivity($act, $this->scoped, $options);
|
||||
|
||||
if ($upload instanceof MediaFile) {
|
||||
$upload->attachToNotice($this->stored);
|
||||
@ -192,7 +207,7 @@ class NewnoticeAction extends FormAction
|
||||
Event::handle('EndNoticeSaveWeb', array($this, $this->stored));
|
||||
}
|
||||
|
||||
Event::handle('EndSaveNewNoticeWeb', array($this, $user, &$content_shortened, &$options));
|
||||
Event::handle('EndSaveNewNoticeWeb', array($this, $user, &$content, &$options));
|
||||
|
||||
if (!GNUsocial::isAjax()) {
|
||||
$url = common_local_url('shownotice', array('notice' => $this->stored->id));
|
||||
|
@ -203,14 +203,7 @@ class SearchNoticeListItem extends NoticeListItem {
|
||||
{
|
||||
// FIXME: URL, image, video, audio
|
||||
$this->out->elementStart('p', array('class' => 'e-content'));
|
||||
if ($this->notice->rendered) {
|
||||
$this->out->raw($this->highlight($this->notice->rendered, $this->terms));
|
||||
} else {
|
||||
// XXX: may be some uncooked notices in the DB,
|
||||
// we cook them right now. This should probably disappear in future
|
||||
// versions (>> 0.4.x)
|
||||
$this->out->raw($this->highlight(common_render_content($this->notice->content, $this->notice), $this->terms));
|
||||
}
|
||||
$this->out->raw($this->highlight($this->notice->getRendered(), $this->terms));
|
||||
$this->out->elementEnd('p');
|
||||
|
||||
}
|
||||
|
@ -144,22 +144,8 @@ class PasswordsettingsAction extends SettingsAction
|
||||
|
||||
if (Event::handle('StartChangePassword', array($this->scoped, $oldpassword, $newpassword))) {
|
||||
//no handler changed the password, so change the password internally
|
||||
$user = $this->scoped->getUser();
|
||||
$original = clone($user);
|
||||
$user->setPassword($newpassword);
|
||||
|
||||
$user->password = common_munge_password($newpassword, $this->scoped);
|
||||
|
||||
$val = $user->validate();
|
||||
if ($val !== true) {
|
||||
// TRANS: Form validation error on page where to change password.
|
||||
throw new ServerException(_('Error saving user; invalid.'));
|
||||
}
|
||||
|
||||
if (!$user->update($original)) {
|
||||
// TRANS: Server error displayed on page where to change password when password change
|
||||
// TRANS: could not be made because of a server error.
|
||||
throw new ServerException(_('Cannot save new password.'));
|
||||
}
|
||||
Event::handle('EndChangePassword', array($this->scoped));
|
||||
}
|
||||
|
||||
|
@ -322,16 +322,7 @@ class RecoverpasswordAction extends Action
|
||||
}
|
||||
|
||||
// OK, we're ready to go
|
||||
|
||||
$original = clone($user);
|
||||
|
||||
$user->password = common_munge_password($newpassword, $user->getProfile());
|
||||
|
||||
if (!$user->update($original)) {
|
||||
common_log_db_error($user, 'UPDATE', __FILE__);
|
||||
// TRANS: Reset password form validation error message.
|
||||
$this->serverError(_('Cannot save new password.'));
|
||||
}
|
||||
$user->setPassword($newpassword);
|
||||
|
||||
$this->clearTempUser();
|
||||
|
||||
|
@ -151,7 +151,7 @@ class RsdAction extends Action
|
||||
$this->elementStart('api', $apiAttrs);
|
||||
$this->elementStart('settings');
|
||||
$this->element('docs', null,
|
||||
'http://status.net/wiki/TwitterCompatibleAPI');
|
||||
common_local_url('doc', array('title' => 'api')));
|
||||
$this->element('setting', array('name' => 'OAuth'),
|
||||
'true');
|
||||
$this->elementEnd('settings');
|
||||
|
@ -22,97 +22,31 @@
|
||||
* @link http://status.net
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
require_once INSTALLDIR.'/lib/profileminilist.php';
|
||||
require_once INSTALLDIR.'/lib/peopletaglist.php';
|
||||
require_once INSTALLDIR.'/lib/noticelist.php';
|
||||
require_once INSTALLDIR.'/lib/feedlist.php';
|
||||
|
||||
class ShowprofiletagAction extends Action
|
||||
class ShowprofiletagAction extends ShowstreamAction
|
||||
{
|
||||
var $notice, $tagger, $peopletag, $userProfile;
|
||||
var $notice, $peopletag;
|
||||
|
||||
function isReadOnly($args)
|
||||
protected function doStreamPreparation()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
|
||||
if (common_config('singleuser', 'enabled')) {
|
||||
$tagger_arg = User::singleUserNickname();
|
||||
} else {
|
||||
$tagger_arg = $this->arg('tagger');
|
||||
}
|
||||
$tag_arg = $this->arg('tag');
|
||||
$tagger = common_canonical_nickname($tagger_arg);
|
||||
$tag = common_canonical_tag($tag_arg);
|
||||
|
||||
// Permanent redirect on non-canonical nickname
|
||||
|
||||
if ($tagger_arg != $tagger || $tag_arg != $tag) {
|
||||
$args = array('tagger' => $nickname, 'tag' => $tag);
|
||||
if ($this->page != 1) {
|
||||
$args['page'] = $this->page;
|
||||
}
|
||||
common_redirect(common_local_url('showprofiletag', $args), 301);
|
||||
}
|
||||
|
||||
if (!$tagger) {
|
||||
// TRANS: Client error displayed when a tagger is expected but not provided.
|
||||
$this->clientError(_('No tagger.'), 404);
|
||||
}
|
||||
|
||||
$user = User::getKV('nickname', $tagger);
|
||||
|
||||
if (!$user) {
|
||||
// TRANS: Client error displayed trying to perform an action related to a non-existing user.
|
||||
$this->clientError(_('No such user.'), 404);
|
||||
}
|
||||
|
||||
$this->tagger = $user->getProfile();
|
||||
$this->peopletag = Profile_list::pkeyGet(array('tagger' => $user->id, 'tag' => $tag));
|
||||
|
||||
$current = common_current_user();
|
||||
$can_see = !empty($this->peopletag) && (!$this->peopletag->private ||
|
||||
($this->peopletag->private && $this->peopletag->tagger === $current->id));
|
||||
|
||||
if (!$can_see) {
|
||||
$tag = common_canonical_tag($this->arg('tag'));
|
||||
try {
|
||||
$this->peopletag = Profile_list::getByPK(array('tagger' => $this->target->getID(), 'tag' => $tag));
|
||||
} catch (NoResultException $e) {
|
||||
// TRANS: Client error displayed trying to reference a non-existing list.
|
||||
$this->clientError(_('No such list.'), 404);
|
||||
throw new ClientException('No such list.');
|
||||
}
|
||||
|
||||
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
|
||||
$this->userProfile = Profile::current();
|
||||
|
||||
$stream = new PeopletagNoticeStream($this->peopletag, $this->userProfile);
|
||||
|
||||
$this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1);
|
||||
|
||||
if ($this->page > 1 && $this->notice->N == 0) {
|
||||
// TRANS: Client error when page not found (404).
|
||||
$this->clientError(_('No such page.'), 404);
|
||||
if ($this->peopletag->private && !$this->peopletag->getTagger()->sameAs($this->scoped)) {
|
||||
// TRANS: Client error displayed trying to reference a non-existing list.
|
||||
throw new AuthorizationException('You do not have permission to see this list.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle($args)
|
||||
public function getStream()
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
if (!$this->peopletag) {
|
||||
// TRANS: Client error displayed trying to perform an action related to a non-existing user.
|
||||
$this->clientError(_('No such user.'));
|
||||
}
|
||||
|
||||
$this->showPage();
|
||||
return new PeopletagNoticeStream($this->peopletag, $this->scoped);
|
||||
}
|
||||
|
||||
function title()
|
||||
@ -137,7 +71,7 @@ class ShowprofiletagAction extends Action
|
||||
// TRANS: %1$s is a list, %2$s is the tagger's nickname, %3$d is a page number.
|
||||
return sprintf(_('Timeline for %1$s list by %2$s, page %3$d'),
|
||||
$this->peopletag->tag,
|
||||
$this->tagger->nickname,
|
||||
$this->target->getNickname(),
|
||||
$this->page
|
||||
);
|
||||
} else {
|
||||
@ -160,7 +94,7 @@ class ShowprofiletagAction extends Action
|
||||
// TRANS: %1$s is a list, %2$s is the tagger's nickname.
|
||||
return sprintf(_('Timeline for %1$s list by %2$s'),
|
||||
$this->peopletag->tag,
|
||||
$this->tagger->nickname
|
||||
$this->target->getNickname()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -171,29 +105,29 @@ class ShowprofiletagAction extends Action
|
||||
return array(new Feed(Feed::JSON,
|
||||
common_local_url(
|
||||
'ApiTimelineList', array(
|
||||
'user' => $this->tagger->id,
|
||||
'user' => $this->target->id,
|
||||
'id' => $this->peopletag->id,
|
||||
'format' => 'as'
|
||||
)
|
||||
),
|
||||
// TRANS: Feed title.
|
||||
// TRANS: %s is tagger's nickname.
|
||||
sprintf(_('Feed for friends of %s (Activity Streams JSON)'), $this->tagger->nickname)),
|
||||
sprintf(_('Feed for friends of %s (Activity Streams JSON)'), $this->target->getNickname())),
|
||||
new Feed(Feed::RSS2,
|
||||
common_local_url(
|
||||
'ApiTimelineList', array(
|
||||
'user' => $this->tagger->id,
|
||||
'user' => $this->target->id,
|
||||
'id' => $this->peopletag->id,
|
||||
'format' => 'rss'
|
||||
)
|
||||
),
|
||||
// TRANS: Feed title.
|
||||
// TRANS: %s is tagger's nickname.
|
||||
sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->tagger->nickname)),
|
||||
sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->target->getNickname())),
|
||||
new Feed(Feed::ATOM,
|
||||
common_local_url(
|
||||
'ApiTimelineList', array(
|
||||
'user' => $this->tagger->id,
|
||||
'user' => $this->target->id,
|
||||
'id' => $this->peopletag->id,
|
||||
'format' => 'atom'
|
||||
)
|
||||
@ -201,7 +135,7 @@ class ShowprofiletagAction extends Action
|
||||
// TRANS: Feed title.
|
||||
// TRANS: %1$s is a list, %2$s is tagger's nickname.
|
||||
sprintf(_('Feed for %1$s list by %2$s (Atom)'),
|
||||
$this->peopletag->tag, $this->tagger->nickname
|
||||
$this->peopletag->tag, $this->target->getNickname()
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -219,11 +153,10 @@ class ShowprofiletagAction extends Action
|
||||
// TRANS: %1$s is a list, %2$s is a tagger's nickname.
|
||||
$message = sprintf(_('This is the timeline for %1$s list by %2$s but no one has posted anything yet.'),
|
||||
$this->peopletag->tag,
|
||||
$this->tagger->nickname) . ' ';
|
||||
$this->target->getNickname()) . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$current_user = common_current_user();
|
||||
if ($this->tagger->id == $current_user->id) {
|
||||
if ($this->target->sameAs($this->scoped)) {
|
||||
// TRANS: Additional empty list message for list timeline for currently logged in user tagged tags.
|
||||
$message .= _('Try tagging more people.');
|
||||
}
|
||||
@ -238,16 +171,15 @@ class ShowprofiletagAction extends Action
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showContent()
|
||||
protected function showContent()
|
||||
{
|
||||
$this->showPeopletag();
|
||||
$this->showNotices();
|
||||
parent::showContent();
|
||||
}
|
||||
|
||||
function showPeopletag()
|
||||
{
|
||||
$cur = common_current_user();
|
||||
$tag = new Peopletag($this->peopletag, $cur, $this);
|
||||
$tag = new Peopletag($this->peopletag, $this->scoped, $this);
|
||||
$tag->show();
|
||||
}
|
||||
|
||||
@ -267,7 +199,7 @@ class ShowprofiletagAction extends Action
|
||||
$this->page,
|
||||
'showprofiletag',
|
||||
array('tag' => $this->peopletag->tag,
|
||||
'tagger' => $this->tagger->nickname)
|
||||
'nickname' => $this->target->getNickname())
|
||||
);
|
||||
|
||||
Event::handle('EndShowProfileTagContent', array($this));
|
||||
@ -283,11 +215,6 @@ class ShowprofiletagAction extends Action
|
||||
# $this->showStatistics();
|
||||
}
|
||||
|
||||
function showPageTitle()
|
||||
{
|
||||
$this->element('h1', null, $this->title());
|
||||
}
|
||||
|
||||
function showTagged()
|
||||
{
|
||||
$profile = $this->peopletag->getTagged(0, PROFILES_PER_MINILIST + 1);
|
||||
@ -314,7 +241,7 @@ class ShowprofiletagAction extends Action
|
||||
if ($cnt > PROFILES_PER_MINILIST) {
|
||||
$this->elementStart('p');
|
||||
$this->element('a', array('href' => common_local_url('taggedprofiles',
|
||||
array('nickname' => $this->tagger->nickname,
|
||||
array('nickname' => $this->target->getNickname(),
|
||||
'profiletag' => $this->peopletag->tag)),
|
||||
'class' => 'more'),
|
||||
// TRANS: Link for more "People in list x by a user"
|
||||
@ -356,20 +283,3 @@ class ShowprofiletagAction extends Action
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
}
|
||||
|
||||
class Peopletag extends PeopletagListItem
|
||||
{
|
||||
protected $avatarSize = AVATAR_PROFILE_SIZE;
|
||||
|
||||
function showStart()
|
||||
{
|
||||
$mode = $this->peopletag->private ? 'private' : 'public';
|
||||
$this->out->elementStart('div', array('class' => 'h-entry peopletag peopletag-profile mode-'.$mode,
|
||||
'id' => 'peopletag-' . $this->peopletag->id));
|
||||
}
|
||||
|
||||
function showEnd()
|
||||
{
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ class ShowstreamAction extends NoticestreamAction
|
||||
return $stream;
|
||||
}
|
||||
|
||||
|
||||
function title()
|
||||
{
|
||||
$base = $this->target->getFancyName();
|
||||
@ -74,7 +73,7 @@ class ShowstreamAction extends NoticestreamAction
|
||||
}
|
||||
} else {
|
||||
if ($this->page == 1) {
|
||||
return $base;
|
||||
return sprintf(_('Notices by %s'), $base);
|
||||
} else {
|
||||
// TRANS: Extended page title showing tagged notices in one user's timeline.
|
||||
// TRANS: %1$s is the username, %2$d is the page number.
|
||||
@ -85,7 +84,7 @@ class ShowstreamAction extends NoticestreamAction
|
||||
}
|
||||
}
|
||||
|
||||
function showContent()
|
||||
protected function showContent()
|
||||
{
|
||||
$this->showNotices();
|
||||
}
|
||||
|
@ -62,21 +62,44 @@ class UsergroupsAction extends GalleryAction
|
||||
}
|
||||
}
|
||||
|
||||
function showPageNotice()
|
||||
{
|
||||
if ($this->scoped instanceof Profile && $this->scoped->sameAs($this->getTarget())) {
|
||||
$this->element('p', 'instructions',
|
||||
// TRANS: Page notice for page with an overview of all subscribed groups
|
||||
// TRANS: of the logged in user's own profile.
|
||||
_('These are the groups whose notices '.
|
||||
'you listen to.'));
|
||||
} else {
|
||||
$this->element('p', 'instructions',
|
||||
// TRANS: Page notice for page with an overview of all groups a user other
|
||||
// TRANS: than the logged in user. %s is the user nickname.
|
||||
sprintf(_('These are the groups whose '.
|
||||
'notices %s listens to.'),
|
||||
$this->target->getNickname()));
|
||||
}
|
||||
}
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$this->elementStart('p', array('id' => 'new_group'));
|
||||
$this->element('a', array('href' => common_local_url('newgroup'),
|
||||
'class' => 'more'),
|
||||
// TRANS: Link text on group page to create a new group.
|
||||
_('Create a new group'));
|
||||
$this->elementEnd('p');
|
||||
|
||||
$this->elementStart('p', array('id' => 'group_search'));
|
||||
$this->element('a', array('href' => common_local_url('groupsearch'),
|
||||
'class' => 'more'),
|
||||
// TRANS: Link text on group page to search for groups.
|
||||
_('Search for more groups'));
|
||||
$this->elementEnd('p');
|
||||
if ($this->scoped instanceof Profile && $this->scoped->sameAs($this->getTarget())) {
|
||||
$notice =
|
||||
// TRANS: Page notice of user's groups page.
|
||||
// TRANS: %%%%action.groupsearch%%%% and %%%%action.newgroup%%%% are URLs. Do not change them.
|
||||
// TRANS: This message contains Markdown links in the form [link text](link).
|
||||
sprintf(_('Groups let you find and talk with ' .
|
||||
'people of similar interests. ' .
|
||||
'You can [search for groups](%%%%action.groups%%%%) in your instance or ' .
|
||||
'[create a new group](%%%%action.newgroup%%%%). ' .
|
||||
'You can also follow groups ' .
|
||||
'from other GNU social instances: click on the remote button below ' .
|
||||
'and copy the group\'s link. ' .
|
||||
'You can find a list of GNU social groups [here](http://skilledtests.com/wiki/List_of_federated_GNU_social_groups)' .
|
||||
''));
|
||||
$this->elementStart('div', 'instructions');
|
||||
$this->raw(common_markup_to_html($notice));
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
if (Event::handle('StartShowUserGroupsContent', array($this))) {
|
||||
$offset = ($this->page-1) * GROUPS_PER_PAGE;
|
||||
@ -87,11 +110,13 @@ class UsergroupsAction extends GalleryAction
|
||||
if ($groups instanceof User_group) {
|
||||
$gl = new GroupList($groups, $this->getTarget(), $this);
|
||||
$cnt = $gl->show();
|
||||
$this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE,
|
||||
$this->page, 'usergroups',
|
||||
array('nickname' => $this->getTarget()->getNickname()));
|
||||
} else {
|
||||
$this->showEmptyListMessage();
|
||||
if (0 == $cnt) {
|
||||
$this->showEmptyListMessage();
|
||||
} else {
|
||||
$this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE,
|
||||
$this->page, 'usergroups',
|
||||
array('nickname' => $this->getTarget()->getNickname()));
|
||||
}
|
||||
}
|
||||
|
||||
Event::handle('EndShowUserGroupsContent', array($this));
|
||||
@ -103,15 +128,15 @@ class UsergroupsAction extends GalleryAction
|
||||
// TRANS: Text on group page for a user that is not a member of any group.
|
||||
// TRANS: %s is a user nickname.
|
||||
$message = sprintf(_('%s is not a member of any group.'), $this->getTarget()->getNickname()) . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$current_user = common_current_user();
|
||||
if ($this->scoped->sameAs($this->getTarget())) {
|
||||
// TRANS: Text on group page for a user that is not a member of any group. This message contains
|
||||
// TRANS: a Markdown link in the form [link text](link) and a variable that should not be changed.
|
||||
$message .= _('Try [searching for groups](%%action.groupsearch%%) and joining them.');
|
||||
$message = _('You are not member of any group yet. After you join a group ' .
|
||||
'you can send messages to its members using the ' .
|
||||
'syntax "!groupname".');
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->elementStart('div', 'guide');
|
||||
$this->raw(common_markup_to_html($message));
|
||||
$this->elementEnd('div');
|
||||
|
@ -17,16 +17,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Table Definition for config
|
||||
*/
|
||||
|
||||
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
||||
|
||||
class Config extends Managed_DataObject
|
||||
{
|
||||
###START_AUTOCODE
|
||||
@ -35,7 +31,7 @@ class Config extends Managed_DataObject
|
||||
public $__table = 'config'; // table name
|
||||
public $section; // varchar(32) primary_key not_null
|
||||
public $setting; // varchar(32) primary_key not_null
|
||||
public $value; // varchar(191) not 255 because utf8mb4 takes more space
|
||||
public $value; // text
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
@ -46,7 +42,7 @@ class Config extends Managed_DataObject
|
||||
'fields' => array(
|
||||
'section' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration section'),
|
||||
'setting' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration setting'),
|
||||
'value' => array('type' => 'varchar', 'length' => 191, 'description' => 'configuration value'),
|
||||
'value' => array('type' => 'text', 'description' => 'configuration value'),
|
||||
),
|
||||
'primary key' => array('section', 'setting'),
|
||||
);
|
||||
|
@ -2,13 +2,9 @@
|
||||
/**
|
||||
* Table Definition for confirm_address
|
||||
*/
|
||||
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
||||
|
||||
class Confirm_address extends Managed_DataObject
|
||||
{
|
||||
###START_AUTOCODE
|
||||
/* the code below is auto generated do not remove the above tag */
|
||||
|
||||
public $__table = 'confirm_address'; // table name
|
||||
public $code; // varchar(32) primary_key not_null
|
||||
public $user_id; // int(4) not_null
|
||||
@ -19,9 +15,6 @@ class Confirm_address extends Managed_DataObject
|
||||
public $sent; // datetime()
|
||||
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
@ -29,7 +22,7 @@ class Confirm_address extends Managed_DataObject
|
||||
'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'),
|
||||
'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who requested confirmation'),
|
||||
'address' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'address (email, xmpp, SMS, etc.)'),
|
||||
'address_extra' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'carrier ID, for SMS'),
|
||||
'address_extra' => array('type' => 'varchar', 'length' => 191, 'description' => 'carrier ID, for SMS'),
|
||||
'address_type' => array('type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'),
|
||||
'claimed' => array('type' => 'datetime', 'description' => 'date this was claimed for queueing'),
|
||||
'sent' => array('type' => 'datetime', 'description' => 'date this was sent for queueing'),
|
||||
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* Laconica - a distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, Control Yourself, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Table Definition for deleted_notice
|
||||
*/
|
||||
|
||||
class Deleted_notice extends Managed_DataObject
|
||||
{
|
||||
public $__table = 'deleted_notice'; // table name
|
||||
public $id; // int(4) primary_key not_null
|
||||
public $profile_id; // int(4) not_null
|
||||
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
|
||||
public $created; // datetime() not_null
|
||||
public $deleted; // datetime() not_null
|
||||
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
'fields' => array(
|
||||
'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity of notice'),
|
||||
'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of the notice'),
|
||||
'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'),
|
||||
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'),
|
||||
'deleted' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
'unique keys' => array(
|
||||
'deleted_notice_uri_key' => array('uri'),
|
||||
),
|
||||
'indexes' => array(
|
||||
'deleted_notice_profile_id_idx' => array('profile_id'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
141
classes/File.php
141
classes/File.php
@ -31,10 +31,10 @@ class File extends Managed_DataObject
|
||||
public $filehash; // varchar(64) indexed
|
||||
public $mimetype; // varchar(50)
|
||||
public $size; // int(4)
|
||||
public $title; // varchar(191) not 255 because utf8mb4 takes more space
|
||||
public $title; // text()
|
||||
public $date; // int(4)
|
||||
public $protected; // int(4)
|
||||
public $filename; // varchar(191) not 255 because utf8mb4 takes more space
|
||||
public $filename; // text()
|
||||
public $width; // int(4)
|
||||
public $height; // int(4)
|
||||
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
||||
@ -52,10 +52,10 @@ class File extends Managed_DataObject
|
||||
'filehash' => array('type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'),
|
||||
'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'),
|
||||
'size' => array('type' => 'int', 'description' => 'size of resource when available'),
|
||||
'title' => array('type' => 'varchar', 'length' => 191, 'description' => 'title of resource when available'),
|
||||
'title' => array('type' => 'text', 'description' => 'title of resource when available'),
|
||||
'date' => array('type' => 'int', 'description' => 'date of resource according to http query'),
|
||||
'protected' => array('type' => 'int', 'description' => 'true when URL is private (needs login)'),
|
||||
'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'if a local file, name of the file'),
|
||||
'filename' => array('type' => 'text', 'description' => 'if a local file, name of the file'),
|
||||
'width' => array('type' => 'int', 'description' => 'width in pixels, if it can be described as such and data is available'),
|
||||
'height' => array('type' => 'int', 'description' => 'height in pixels, if it can be described as such and data is available'),
|
||||
|
||||
@ -82,30 +82,45 @@ class File extends Managed_DataObject
|
||||
* @param string $given_url
|
||||
* @return File
|
||||
*/
|
||||
public static function saveNew(array $redir_data, $given_url) {
|
||||
|
||||
// I don't know why we have to keep doing this but I'm adding this last check to avoid
|
||||
// uniqueness bugs.
|
||||
|
||||
$file = File::getKV('urlhash', self::hashurl($given_url));
|
||||
|
||||
if (!$file instanceof File) {
|
||||
$file = new File;
|
||||
$file->urlhash = self::hashurl($given_url);
|
||||
$file->url = $given_url;
|
||||
if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected'];
|
||||
if (!empty($redir_data['title'])) $file->title = $redir_data['title'];
|
||||
if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type'];
|
||||
if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']);
|
||||
if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']);
|
||||
$file_id = $file->insert();
|
||||
public static function saveNew(array $redir_data, $given_url)
|
||||
{
|
||||
$file = null;
|
||||
try {
|
||||
// I don't know why we have to keep doing this but we run a last check to avoid
|
||||
// uniqueness bugs.
|
||||
$file = File::getByUrl($given_url);
|
||||
return $file;
|
||||
} catch (NoResultException $e) {
|
||||
// We don't have the file's URL since before, so let's continue.
|
||||
}
|
||||
|
||||
Event::handle('EndFileSaveNew', array($file, $redir_data, $given_url));
|
||||
assert ($file instanceof File);
|
||||
$file = new File;
|
||||
$file->url = $given_url;
|
||||
if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected'];
|
||||
if (!empty($redir_data['title'])) $file->title = $redir_data['title'];
|
||||
if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type'];
|
||||
if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']);
|
||||
if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']);
|
||||
$file->saveFile();
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function saveFile() {
|
||||
$this->urlhash = self::hashurl($this->url);
|
||||
|
||||
if (!Event::handle('StartFileSaveNew', array(&$this))) {
|
||||
throw new ServerException('File not saved due to an aborted StartFileSaveNew event.');
|
||||
}
|
||||
|
||||
$this->id = $this->insert();
|
||||
|
||||
if ($this->id === false) {
|
||||
throw new ServerException('File/URL metadata could not be saved to the database.');
|
||||
}
|
||||
|
||||
Event::handle('EndFileSaveNew', array($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Go look at a URL and possibly save data about it if it's new:
|
||||
* - follow redirect chains and store them in file_redirection
|
||||
@ -114,7 +129,6 @@ class File extends Managed_DataObject
|
||||
* - optionally save a file_to_post record
|
||||
* - return the File object with the full reference
|
||||
*
|
||||
* @fixme refactor this mess, it's gotten pretty scary.
|
||||
* @param string $given_url the URL we're looking at
|
||||
* @param Notice $notice (optional)
|
||||
* @param bool $followRedirects defaults to true
|
||||
@ -133,69 +147,30 @@ class File extends Managed_DataObject
|
||||
throw new ServerException('No canonical URL from given URL to process');
|
||||
}
|
||||
|
||||
$file = null;
|
||||
$redir = File_redirection::where($given_url);
|
||||
$file = $redir->getFile();
|
||||
|
||||
try {
|
||||
$file = File::getByUrl($given_url);
|
||||
} catch (NoResultException $e) {
|
||||
// First check if we have a lookup trace for this URL already
|
||||
try {
|
||||
$file_redir = File_redirection::getByUrl($given_url);
|
||||
$file = File::getKV('id', $file_redir->file_id);
|
||||
if (!$file instanceof File) {
|
||||
// File did not exist, let's clean up the File_redirection entry
|
||||
$file_redir->delete();
|
||||
}
|
||||
} catch (NoResultException $e) {
|
||||
// We just wanted to doublecheck whether a File_thumbnail we might've had
|
||||
// actually referenced an existing File object.
|
||||
// If we still don't have a File object, let's create one now!
|
||||
if (empty($file->id)) {
|
||||
if ($redir->url === $given_url || !$followRedirects) {
|
||||
// Save the File object based on our lookup trace
|
||||
$file->saveFile();
|
||||
} else {
|
||||
$file->saveFile();
|
||||
$redir->file_id = $file->id;
|
||||
$redir->insert();
|
||||
}
|
||||
}
|
||||
|
||||
// If we still don't have a File object, let's create one now!
|
||||
if (!$file instanceof File) {
|
||||
// @fixme for new URLs this also looks up non-redirect data
|
||||
// such as target content type, size, etc, which we need
|
||||
// for File::saveNew(); so we call it even if not following
|
||||
// new redirects.
|
||||
$redir_data = File_redirection::where($given_url);
|
||||
if (is_array($redir_data)) {
|
||||
$redir_url = $redir_data['url'];
|
||||
} elseif (is_string($redir_data)) {
|
||||
$redir_url = $redir_data;
|
||||
$redir_data = array();
|
||||
} else {
|
||||
// TRANS: Server exception thrown when a URL cannot be processed.
|
||||
throw new ServerException(sprintf(_("Cannot process URL '%s'"), $given_url));
|
||||
}
|
||||
|
||||
if ($redir_url === $given_url || !$followRedirects) {
|
||||
// Save the File object based on our lookup trace
|
||||
$file = File::saveNew($redir_data, $given_url);
|
||||
} else {
|
||||
// This seems kind of messed up... for now skipping this part
|
||||
// if we're already under a redirect, so we don't go into
|
||||
// horrible infinite loops if we've been given an unstable
|
||||
// redirect (where the final destination of the first request
|
||||
// doesn't match what we get when we ask for it again).
|
||||
//
|
||||
// Seen in the wild with clojure.org, which redirects through
|
||||
// wikispaces for auth and appends session data in the URL params.
|
||||
$file = self::processNew($redir_url, $notice, /*followRedirects*/false);
|
||||
File_redirection::saveNew($redir_data, $file->id, $given_url);
|
||||
}
|
||||
|
||||
if (!$file instanceof File) {
|
||||
// This should only happen if File::saveNew somehow did not return a File object,
|
||||
// though we have an assert for that in case the event there might've gone wrong.
|
||||
// If anything else goes wrong, there should've been an exception thrown.
|
||||
throw new ServerException('URL processing failed without new File object');
|
||||
}
|
||||
if (!$file instanceof File || empty($file->id)) {
|
||||
// This should not happen
|
||||
throw new ServerException('URL processing failed without new File object');
|
||||
}
|
||||
|
||||
if ($notice instanceof Notice) {
|
||||
File_to_post::processNew($file, $notice);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
@ -449,7 +424,11 @@ class File extends Managed_DataObject
|
||||
if (self::hashurl($url) !== $this->urlhash) {
|
||||
// For indexing purposes, in case we do a lookup on the 'url' field.
|
||||
// also we're fixing possible changes from http to https, or paths
|
||||
$this->updateUrl($url);
|
||||
try {
|
||||
$this->updateUrl($url);
|
||||
} catch (ServerException $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
@ -471,7 +450,7 @@ class File extends Managed_DataObject
|
||||
/**
|
||||
* @param string $hashstr String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)'
|
||||
*/
|
||||
static public function getByHash($hashstr, $alg=File::FILEHASH_ALG)
|
||||
static public function getByHash($hashstr)
|
||||
{
|
||||
$file = new File();
|
||||
$file->filehash = strtolower($hashstr);
|
||||
@ -681,4 +660,4 @@ class File extends Managed_DataObject
|
||||
echo "DONE.\n";
|
||||
echo "Resuming core schema upgrade...";
|
||||
}
|
||||
}
|
||||
}
|
@ -39,6 +39,8 @@ class File_redirection extends Managed_DataObject
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
protected $file; /* Cache the associated file sometimes */
|
||||
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
@ -155,41 +157,55 @@ class File_redirection extends Managed_DataObject
|
||||
*
|
||||
* @param string $in_url
|
||||
* @param boolean $discover true to attempt dereferencing the redirect if we don't know it already
|
||||
* @return mixed one of:
|
||||
* string - target URL, if this is a direct link or a known redirect
|
||||
* array - redirect info if this is an *unknown* redirect:
|
||||
* associative array with the following elements:
|
||||
* code: HTTP status code
|
||||
* redirects: count of redirects followed
|
||||
* url: URL string of final target
|
||||
* type (optional): MIME type from Content-Type header
|
||||
* size (optional): byte size from Content-Length header
|
||||
* time (optional): timestamp from Last-Modified header
|
||||
* @return File_redirection
|
||||
*/
|
||||
static function where($in_url, $discover=true) {
|
||||
// let's see if we know this...
|
||||
$redir = new File_redirection();
|
||||
$redir->url = $in_url;
|
||||
$redir->urlhash = File::hashurl($redir->url);
|
||||
$redir->redirections = 0;
|
||||
|
||||
try {
|
||||
$a = File::getByUrl($in_url);
|
||||
// this is a direct link to $a->url
|
||||
return $a->url;
|
||||
$r = File_redirection::getByUrl($in_url);
|
||||
if($r instanceof File_redirection) {
|
||||
return $r;
|
||||
}
|
||||
} catch (NoResultException $e) {
|
||||
try {
|
||||
$b = File_redirection::getByUrl($in_url);
|
||||
// this is a redirect to $b->file_id
|
||||
$a = File::getByID($b->file_id);
|
||||
return $a->url;
|
||||
$f = File::getByUrl($in_url);
|
||||
$redir->file_id = $f->id;
|
||||
$redir->file = $f;
|
||||
return $redir;
|
||||
} catch (NoResultException $e) {
|
||||
// Oh well, let's keep going
|
||||
}
|
||||
}
|
||||
|
||||
if ($discover) {
|
||||
$ret = File_redirection::lookupWhere($in_url);
|
||||
return $ret;
|
||||
$redir_info = File_redirection::lookupWhere($in_url);
|
||||
if(is_string($redir_info)) {
|
||||
$redir_info = array('url' => $redir_info);
|
||||
}
|
||||
|
||||
// Double check that we don't already have the resolved URL
|
||||
$r = self::where($redir_info['url'], false);
|
||||
if (!empty($r->file_id)) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$redir->httpcode = $redir_info['code'];
|
||||
$redir->redirections = intval($redir_info['redirects']);
|
||||
$redir->file = new File();
|
||||
$redir->file->url = $redir_info['url'];
|
||||
$redir->file->mimetype = $redir_info['type'];
|
||||
$redir->file->size = isset($redir_info['size']) ? $redir_info['size'] : null;
|
||||
$redir->file->date = isset($redir_info['time']) ? $redir_info['time'] : null;
|
||||
if (isset($redir_info['protected']) && !empty($redir_info['protected'])) {
|
||||
$redir->file->protected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// No manual dereferencing; leave the unknown URL as is.
|
||||
return $in_url;
|
||||
return $redir;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,37 +262,24 @@ class File_redirection extends Managed_DataObject
|
||||
if (!empty($short_url) && $short_url != $long_url) {
|
||||
$short_url = (string)$short_url;
|
||||
// store it
|
||||
$file = File::getKV('url', $long_url);
|
||||
if ($file instanceof File) {
|
||||
$file_id = $file->getID();
|
||||
} else {
|
||||
try {
|
||||
$file = File::getByUrl($long_url);
|
||||
} catch (NoResultException $e) {
|
||||
// Check if the target URL is itself a redirect...
|
||||
$redir_data = File_redirection::where($long_url);
|
||||
if (is_array($redir_data)) {
|
||||
// We haven't seen the target URL before.
|
||||
// Save file and embedding data about it!
|
||||
$file = File::saveNew($redir_data, $long_url);
|
||||
$file_id = $file->getID();
|
||||
} else if (is_string($redir_data)) {
|
||||
// The file is a known redirect target.
|
||||
$file = File::getKV('url', $redir_data);
|
||||
if (empty($file)) {
|
||||
// @fixme should we save a new one?
|
||||
// this case was triggering sometimes for redirects
|
||||
// with unresolvable targets; found while fixing
|
||||
// "can't linkify" bugs with shortened links to
|
||||
// SSL sites with cert issues.
|
||||
return null;
|
||||
}
|
||||
$file_id = $file->getID();
|
||||
$redir = File_redirection::where($long_url);
|
||||
$file = $redir->getFile();
|
||||
if (empty($file->id)) {
|
||||
$file->saveFile();
|
||||
}
|
||||
}
|
||||
$file_redir = File_redirection::getKV('url', $short_url);
|
||||
if (!$file_redir instanceof File_redirection) {
|
||||
$file_redir = new File_redirection;
|
||||
// Now we definitely have a File object in $file
|
||||
try {
|
||||
$file_redir = File_redirection::getByUrl($short_url);
|
||||
} catch (NoResultException $e) {
|
||||
$file_redir = new File_redirection();
|
||||
$file_redir->urlhash = File::hashurl($short_url);
|
||||
$file_redir->url = $short_url;
|
||||
$file_redir->file_id = $file_id;
|
||||
$file_redir->file_id = $file->getID();
|
||||
$file_redir->insert();
|
||||
}
|
||||
return $short_url;
|
||||
@ -336,7 +339,7 @@ class File_redirection extends Managed_DataObject
|
||||
|
||||
static function saveNew($data, $file_id, $url) {
|
||||
$file_redir = new File_redirection;
|
||||
$file_redir->urlhash = File::hashurl($short_url);
|
||||
$file_redir->urlhash = File::hashurl($url);
|
||||
$file_redir->url = $url;
|
||||
$file_redir->file_id = $file_id;
|
||||
$file_redir->redirections = intval($data['redirects']);
|
||||
@ -385,4 +388,12 @@ class File_redirection extends Managed_DataObject
|
||||
echo "DONE.\n";
|
||||
echo "Resuming core schema upgrade...";
|
||||
}
|
||||
|
||||
public function getFile() {
|
||||
if(empty($this->file) && $this->file_id) {
|
||||
$this->file = File::getKV('id', $this->file_id);
|
||||
}
|
||||
|
||||
return $this->file;
|
||||
}
|
||||
}
|
||||
|
@ -153,6 +153,26 @@ class File_thumbnail extends Managed_DataObject
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function getHtmlAttrs(array $orig=array(), $overwrite=true)
|
||||
{
|
||||
$attrs = [
|
||||
'height' => $this->getHeight(),
|
||||
'width' => $this->getWidth(),
|
||||
'src' => $this->getUrl(),
|
||||
];
|
||||
return $overwrite ? array_merge($orig, $attrs) : array_merge($attrs, $orig);
|
||||
}
|
||||
|
||||
public function delete($useWhere=false)
|
||||
{
|
||||
if (!empty($this->filename) && file_exists(File_thumbnail::path($this->filename))) {
|
||||
|
@ -76,18 +76,15 @@ class Group_join_queue extends Managed_DataObject
|
||||
|
||||
/**
|
||||
* Abort the pending group join...
|
||||
*
|
||||
* @param User_group $group
|
||||
*/
|
||||
function abort()
|
||||
{
|
||||
$profile = $this->getMember();
|
||||
$group = $this->getGroup();
|
||||
if ($request) {
|
||||
if (Event::handle('StartCancelJoinGroup', array($profile, $group))) {
|
||||
$this->delete();
|
||||
Event::handle('EndCancelJoinGroup', array($profile, $group));
|
||||
}
|
||||
|
||||
if (Event::handle('StartCancelJoinGroup', array($profile, $group))) {
|
||||
$this->delete();
|
||||
Event::handle('EndCancelJoinGroup', array($profile, $group));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,9 @@ class Group_member extends Managed_DataObject
|
||||
$member->group_id = $group_id;
|
||||
$member->profile_id = $profile_id;
|
||||
$member->created = common_sql_now();
|
||||
$member->uri = self::newURI($profile_id, $group_id, $member->created);
|
||||
$member->uri = self::newUri(Profile::getByID($profile_id),
|
||||
User_group::getByID($group_id),
|
||||
$member->created);
|
||||
|
||||
$result = $member->insert();
|
||||
|
||||
@ -166,7 +168,7 @@ class Group_member extends Managed_DataObject
|
||||
|
||||
$act = new Activity();
|
||||
|
||||
$act->id = $this->getURI();
|
||||
$act->id = $this->getUri();
|
||||
|
||||
$act->actor = $member->asActivityObject();
|
||||
$act->verb = ActivityVerb::JOIN;
|
||||
@ -201,20 +203,8 @@ class Group_member extends Managed_DataObject
|
||||
mail_notify_group_join($this->getGroup(), $this->getMember());
|
||||
}
|
||||
|
||||
function getURI()
|
||||
function getUri()
|
||||
{
|
||||
if (!empty($this->uri)) {
|
||||
return $this->uri;
|
||||
} else {
|
||||
return self::newURI($this->profile_id, $this->group_id, $this->created);
|
||||
}
|
||||
}
|
||||
|
||||
static function newURI($profile_id, $group_id, $created)
|
||||
{
|
||||
return TagURI::mint('join:%d:%d:%s',
|
||||
$profile_id,
|
||||
$group_id,
|
||||
common_date_iso8601($created));
|
||||
return $this->uri ?: self::newUri($this->getMember(), $this->getGroup()->getProfile(), $this->created);
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ abstract class Managed_DataObject extends Memcached_DataObject
|
||||
$object = new $classname();
|
||||
foreach ($pkey as $col) {
|
||||
if (!array_key_exists($col, $vals)) {
|
||||
throw new ServerException("Missing primary key column '{$col}'");
|
||||
throw new ServerException("Missing primary key column '{$col}' for ".get_called_class()." among provided keys: ".implode(',', array_keys($vals)));
|
||||
} elseif (is_null($vals[$col])) {
|
||||
throw new ServerException("NULL values not allowed in getByPK for column '{$col}'");
|
||||
}
|
||||
@ -346,6 +346,41 @@ abstract class Managed_DataObject extends Memcached_DataObject
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object by looking at given unique key columns.
|
||||
*
|
||||
* Will NOT accept NULL values for a unique key column. Ignores non-key values.
|
||||
*
|
||||
* @param array $vals All array keys which are set must be non-null.
|
||||
*
|
||||
* @return Managed_DataObject of the get_called_class() type
|
||||
* @throws NoResultException if no object with that primary key
|
||||
*/
|
||||
static function getByKeys(array $vals)
|
||||
{
|
||||
$classname = get_called_class();
|
||||
|
||||
$object = new $classname();
|
||||
|
||||
$keys = $object->keys();
|
||||
if (is_null($keys)) {
|
||||
throw new ServerException("Failed to get key columns for class '{$classname}'");
|
||||
}
|
||||
|
||||
foreach ($keys as $col) {
|
||||
if (!array_key_exists($col, $vals)) {
|
||||
continue;
|
||||
} elseif (is_null($vals[$col])) {
|
||||
throw new ServerException("NULL values not allowed in getByKeys for column '{$col}'");
|
||||
}
|
||||
$object->$col = $vals[$col];
|
||||
}
|
||||
if (!$object->find(true)) {
|
||||
throw new NoResultException($object);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
static function getByID($id)
|
||||
{
|
||||
if (empty($id)) {
|
||||
@ -453,4 +488,16 @@ abstract class Managed_DataObject extends Memcached_DataObject
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
static function newUri(Profile $actor, Managed_DataObject $object, $created=null)
|
||||
{
|
||||
if (is_null($created)) {
|
||||
$created = common_sql_now();
|
||||
}
|
||||
return TagURI::mint(strtolower(get_called_class()).':%d:%s:%d:%s',
|
||||
$actor->getID(),
|
||||
ActivityUtils::resolveUri($object->getObjectType(), true),
|
||||
$object->getID(),
|
||||
common_date_iso8601($created));
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class Notice extends Managed_DataObject
|
||||
'source' => array('type' => 'varchar', 'length' => 32, 'description' => 'source of comment, like "web", "im", or "clientname"'),
|
||||
'conversation' => array('type' => 'int', 'description' => 'id of root notice in this conversation'),
|
||||
'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'),
|
||||
'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'),
|
||||
'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => null),
|
||||
'verb' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'),
|
||||
'scope' => array('type' => 'int',
|
||||
'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = followers; null = default'),
|
||||
@ -158,43 +158,14 @@ class Notice extends Managed_DataObject
|
||||
$this->_profile[$this->profile_id] = $profile;
|
||||
}
|
||||
|
||||
public function deleteAs(Profile $actor)
|
||||
public function deleteAs(Profile $actor, $delete_event=true)
|
||||
{
|
||||
if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) {
|
||||
return $this->delete();
|
||||
}
|
||||
throw new AuthorizationException('You are not allowed to delete other user\'s notices');
|
||||
}
|
||||
|
||||
function delete($useWhere=false)
|
||||
{
|
||||
// For auditing purposes, save a record that the notice
|
||||
// was deleted.
|
||||
|
||||
// @fixme we have some cases where things get re-run and so the
|
||||
// insert fails.
|
||||
$deleted = Deleted_notice::getKV('id', $this->id);
|
||||
|
||||
if (!$deleted instanceof Deleted_notice) {
|
||||
$deleted = Deleted_notice::getKV('uri', $this->uri);
|
||||
}
|
||||
|
||||
if (!$deleted instanceof Deleted_notice) {
|
||||
$deleted = new Deleted_notice();
|
||||
|
||||
$deleted->id = $this->id;
|
||||
$deleted->profile_id = $this->profile_id;
|
||||
$deleted->uri = $this->uri;
|
||||
$deleted->created = $this->created;
|
||||
$deleted->deleted = common_sql_now();
|
||||
|
||||
$deleted->insert();
|
||||
if (!$this->getProfile()->sameAs($actor) && !$actor->hasRight(Right::DELETEOTHERSNOTICE)) {
|
||||
throw new AuthorizationException(_('You are not allowed to delete another user\'s notice.'));
|
||||
}
|
||||
|
||||
if (Event::handle('NoticeDeleteRelated', array($this))) {
|
||||
|
||||
// Clear related records
|
||||
|
||||
$this->clearReplies();
|
||||
$this->clearLocation();
|
||||
$this->clearRepeats();
|
||||
@ -202,10 +173,21 @@ class Notice extends Managed_DataObject
|
||||
$this->clearGroupInboxes();
|
||||
$this->clearFiles();
|
||||
$this->clearAttentions();
|
||||
|
||||
// NOTE: we don't clear queue items
|
||||
}
|
||||
|
||||
$result = null;
|
||||
if (!$delete_event || Event::handle('DeleteNoticeAsProfile', array($this, $actor, &$result))) {
|
||||
// If $delete_event is true, we run the event. If the Event then
|
||||
// returns false it is assumed everything was handled properly
|
||||
// and the notice was deleted.
|
||||
$result = $this->delete();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function delete($useWhere=false)
|
||||
{
|
||||
$result = parent::delete($useWhere);
|
||||
|
||||
$this->blowOnDelete();
|
||||
@ -275,6 +257,11 @@ class Notice extends Managed_DataObject
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getRendered()
|
||||
{
|
||||
return $this->rendered;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the original representation URL of this notice.
|
||||
*
|
||||
@ -298,10 +285,8 @@ class Notice extends Managed_DataObject
|
||||
}
|
||||
}
|
||||
|
||||
public function get_object_type($canonical=false) {
|
||||
return $canonical
|
||||
? ActivityObject::canonicalType($this->object_type)
|
||||
: $this->object_type;
|
||||
public function getObjectType($canonical=false) {
|
||||
return ActivityUtils::resolveUri($this->object_type, $canonical);
|
||||
}
|
||||
|
||||
public static function getByUri($uri)
|
||||
@ -636,7 +621,9 @@ class Notice extends Managed_DataObject
|
||||
if (!empty($rendered)) {
|
||||
$notice->rendered = $rendered;
|
||||
} else {
|
||||
$notice->rendered = common_render_content($final, $notice);
|
||||
$notice->rendered = common_render_content($final,
|
||||
$notice->getProfile(),
|
||||
$notice->hasParent() ? $notice->getParent() : null);
|
||||
}
|
||||
|
||||
if (empty($verb)) {
|
||||
@ -697,33 +684,33 @@ class Notice extends Managed_DataObject
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the cache for subscribed users, so they'll update at next request
|
||||
// XXX: someone clever could prepend instead of clearing the cache
|
||||
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
|
||||
// the activityverb is a POST (since stuff like repeat, favorite etc.
|
||||
// reasonably handle notifications themselves.
|
||||
if (ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST))) {
|
||||
if (isset($replies)) {
|
||||
$notice->saveKnownReplies($replies);
|
||||
} else {
|
||||
$notice->saveReplies();
|
||||
}
|
||||
|
||||
// Save per-notice metadata...
|
||||
if (isset($tags)) {
|
||||
$notice->saveKnownTags($tags);
|
||||
} else {
|
||||
$notice->saveTags();
|
||||
}
|
||||
|
||||
if (isset($replies)) {
|
||||
$notice->saveKnownReplies($replies);
|
||||
} else {
|
||||
$notice->saveReplies();
|
||||
}
|
||||
// Note: groups may save tags, so must be run after tags are saved
|
||||
// to avoid errors on duplicates.
|
||||
// Note: groups should always be set.
|
||||
|
||||
if (isset($tags)) {
|
||||
$notice->saveKnownTags($tags);
|
||||
} else {
|
||||
$notice->saveTags();
|
||||
}
|
||||
$notice->saveKnownGroups($groups);
|
||||
|
||||
// Note: groups may save tags, so must be run after tags are saved
|
||||
// to avoid errors on duplicates.
|
||||
// Note: groups should always be set.
|
||||
|
||||
$notice->saveKnownGroups($groups);
|
||||
|
||||
if (isset($urls)) {
|
||||
$notice->saveKnownUrls($urls);
|
||||
} else {
|
||||
$notice->saveUrls();
|
||||
if (isset($urls)) {
|
||||
$notice->saveKnownUrls($urls);
|
||||
} else {
|
||||
$notice->saveUrls();
|
||||
}
|
||||
}
|
||||
|
||||
if ($distribute) {
|
||||
@ -751,6 +738,7 @@ class Notice extends Managed_DataObject
|
||||
}
|
||||
|
||||
// Get ActivityObject properties
|
||||
$actobj = null;
|
||||
if (!empty($act->id)) {
|
||||
// implied object
|
||||
$options['uri'] = $act->id;
|
||||
@ -769,7 +757,7 @@ class Notice extends Managed_DataObject
|
||||
|
||||
$defaults = array(
|
||||
'groups' => array(),
|
||||
'is_local' => self::LOCAL_PUBLIC,
|
||||
'is_local' => $actor->isLocal() ? self::LOCAL_PUBLIC : self::REMOTE,
|
||||
'mentions' => array(),
|
||||
'reply_to' => null,
|
||||
'repeat_of' => null,
|
||||
@ -789,12 +777,36 @@ class Notice extends Managed_DataObject
|
||||
}
|
||||
extract($options, EXTR_SKIP);
|
||||
|
||||
// dupe check
|
||||
$stored = new Notice();
|
||||
if (!empty($uri)) {
|
||||
if (!empty($uri) && !ActivityUtils::compareVerbs($act->verb, array(ActivityVerb::DELETE))) {
|
||||
$stored->uri = $uri;
|
||||
if ($stored->find()) {
|
||||
common_debug('cannot create duplicate Notice URI: '.$stored->uri);
|
||||
throw new Exception('Notice URI already exists');
|
||||
// I _assume_ saving a Notice with a colliding URI means we're really trying to
|
||||
// save the same notice again...
|
||||
throw new AlreadyFulfilledException('Notice URI already exists');
|
||||
}
|
||||
}
|
||||
|
||||
$autosource = common_config('public', 'autosource');
|
||||
|
||||
// Sandboxed are non-false, but not 1, either
|
||||
if (!$actor->hasRight(Right::PUBLICNOTICE) ||
|
||||
($source && $autosource && in_array($source, $autosource))) {
|
||||
// FIXME: ...what about remote nonpublic? Hmmm. That is, if we sandbox remote profiles...
|
||||
$stored->is_local = Notice::LOCAL_NONPUBLIC;
|
||||
} else {
|
||||
$stored->is_local = intval($is_local);
|
||||
}
|
||||
|
||||
if (!$stored->isLocal()) {
|
||||
// Only do these checks for non-local notices. Local notices will generate these values later.
|
||||
if (!common_valid_http_url($url)) {
|
||||
common_debug('Bad notice URL: ['.$url.'], URI: ['.$uri.']. Cannot link back to original! This is normal for shared notices etc.');
|
||||
}
|
||||
if (empty($uri)) {
|
||||
throw new ServerException('No URI for remote notice. Cannot accept that.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,18 +816,24 @@ class Notice extends Managed_DataObject
|
||||
$stored->url = $url;
|
||||
$stored->verb = $act->verb;
|
||||
|
||||
// Use the local user's shortening preferences, if applicable.
|
||||
$stored->rendered = $actor->isLocal()
|
||||
? $actor->shortenLinks($act->content)
|
||||
: $act->content;
|
||||
// Notice content. We trust local users to provide HTML we like, but of course not remote users.
|
||||
// FIXME: What about local users importing feeds? Mirror functions must filter out bad HTML first...
|
||||
$content = $act->content ?: $act->summary;
|
||||
if (is_null($content) && !is_null($actobj)) {
|
||||
$content = $actobj->content ?: $actobj->summary;
|
||||
}
|
||||
$stored->rendered = $actor->isLocal() ? $content : common_purify($content);
|
||||
$stored->content = common_strip_html($stored->rendered);
|
||||
|
||||
$autosource = common_config('public', 'autosource');
|
||||
|
||||
// Sandboxed are non-false, but not 1, either
|
||||
if (!$actor->hasRight(Right::PUBLICNOTICE) ||
|
||||
($source && $autosource && in_array($source, $autosource))) {
|
||||
$stored->is_local = Notice::LOCAL_NONPUBLIC;
|
||||
// Reject notice if it is too long (without the HTML)
|
||||
// FIXME: Reject if too short (empty) too? But we have to pass the
|
||||
if ($actor->isLocal() && Notice::contentTooLong($stored->content)) {
|
||||
// TRANS: Client error displayed when the parameter "status" is missing.
|
||||
// TRANS: %d is the maximum number of character for a notice.
|
||||
throw new ClientException(sprintf(_m('That\'s too long. Maximum notice size is %d character.',
|
||||
'That\'s too long. Maximum notice size is %d characters.',
|
||||
Notice::maxContent()),
|
||||
Notice::maxContent()));
|
||||
}
|
||||
|
||||
// Maybe a missing act-time should be fatal if the actor is not local?
|
||||
@ -846,7 +864,6 @@ class Notice extends Managed_DataObject
|
||||
// If the original is private to a group, and notice has no group specified,
|
||||
// make it to the same group(s)
|
||||
if (empty($groups) && ($reply->scope & Notice::GROUP_SCOPE)) {
|
||||
$groups = array();
|
||||
$replyGroups = $reply->getGroups();
|
||||
foreach ($replyGroups as $group) {
|
||||
if ($actor->isMember($group)) {
|
||||
@ -866,7 +883,7 @@ class Notice extends Managed_DataObject
|
||||
$conv = Conversation::getKV('uri', $act->context->conversation);
|
||||
if ($conv instanceof Conversation) {
|
||||
common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time ('.$stored->created.') should maybe be compared to conversation creation time ('.$conv->created.').');
|
||||
$stored->conversation = $conv->id;
|
||||
$stored->conversation = $conv->getID();
|
||||
} else {
|
||||
// Conversation URI was not found, so we must create it. But we can't create it
|
||||
// until we have a Notice ID because of the database layout...
|
||||
@ -901,11 +918,24 @@ class Notice extends Managed_DataObject
|
||||
$urls[] = $href;
|
||||
}
|
||||
|
||||
if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) {
|
||||
if (empty($act->objects[0]->type)) {
|
||||
// Default type for the post verb is 'note', but we know it's
|
||||
// a 'comment' if it is in reply to something.
|
||||
$stored->object_type = empty($stored->reply_to) ? ActivityObject::NOTE : ActivityObject::COMMENT;
|
||||
} else {
|
||||
//TODO: Is it safe to always return a relative URI? The
|
||||
// JSON version of ActivityStreams always use it, so we
|
||||
// should definitely be able to handle it...
|
||||
$stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (Event::handle('StartNoticeSave', array(&$stored))) {
|
||||
// XXX: some of these functions write to the DB
|
||||
|
||||
try {
|
||||
$stored->insert(); // throws exception on error
|
||||
$result = $stored->insert(); // throws exception on error
|
||||
|
||||
if ($notloc instanceof Notice_location) {
|
||||
$notloc->notice_id = $stored->getID();
|
||||
@ -917,7 +947,7 @@ class Notice extends Managed_DataObject
|
||||
$object = null;
|
||||
Event::handle('StoreActivityObject', array($act, $stored, $options, &$object));
|
||||
if (empty($object)) {
|
||||
throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->uri . ': '.$act->asString());
|
||||
throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->getUri() . ': '.$act->asString());
|
||||
}
|
||||
|
||||
// If it's not part of a conversation, it's the beginning
|
||||
@ -926,7 +956,7 @@ class Notice extends Managed_DataObject
|
||||
// $act->context->conversation will be null if it was not provided
|
||||
common_debug('Creating a new conversation for stored notice ID='.$stored->getID().' with URI: '.$act->context->conversation);
|
||||
$conv = Conversation::create($stored, $act->context->conversation);
|
||||
$stored->conversation = $conv->id;
|
||||
$stored->conversation = $conv->getID();
|
||||
}
|
||||
|
||||
$stored->update($orig);
|
||||
@ -946,34 +976,39 @@ class Notice extends Managed_DataObject
|
||||
|
||||
// Save per-notice metadata...
|
||||
$mentions = array();
|
||||
$groups = array();
|
||||
$group_ids = array();
|
||||
|
||||
// This event lets plugins filter out non-local recipients (attentions we don't care about)
|
||||
// Used primarily for OStatus (and if we don't federate, all attentions would be local anyway)
|
||||
Event::handle('GetLocalAttentions', array($actor, $act->context->attention, &$mentions, &$groups));
|
||||
Event::handle('GetLocalAttentions', array($actor, $act->context->attention, &$mentions, &$group_ids));
|
||||
|
||||
if (!empty($mentions)) {
|
||||
$stored->saveKnownReplies($mentions);
|
||||
} else {
|
||||
$stored->saveReplies();
|
||||
}
|
||||
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
|
||||
// the activityverb is a POST (since stuff like repeat, favorite etc.
|
||||
// reasonably handle notifications themselves.
|
||||
if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) {
|
||||
if (!empty($mentions)) {
|
||||
$stored->saveKnownReplies($mentions);
|
||||
} else {
|
||||
$stored->saveReplies();
|
||||
}
|
||||
|
||||
if (!empty($tags)) {
|
||||
$stored->saveKnownTags($tags);
|
||||
} else {
|
||||
$stored->saveTags();
|
||||
}
|
||||
if (!empty($tags)) {
|
||||
$stored->saveKnownTags($tags);
|
||||
} else {
|
||||
$stored->saveTags();
|
||||
}
|
||||
|
||||
// Note: groups may save tags, so must be run after tags are saved
|
||||
// to avoid errors on duplicates.
|
||||
// Note: groups should always be set.
|
||||
// Note: groups may save tags, so must be run after tags are saved
|
||||
// to avoid errors on duplicates.
|
||||
// Note: groups should always be set.
|
||||
|
||||
$stored->saveKnownGroups($groups);
|
||||
$stored->saveKnownGroups($group_ids);
|
||||
|
||||
if (!empty($urls)) {
|
||||
$stored->saveKnownUrls($urls);
|
||||
} else {
|
||||
$stored->saveUrls();
|
||||
if (!empty($urls)) {
|
||||
$stored->saveKnownUrls($urls);
|
||||
} else {
|
||||
$stored->saveUrls();
|
||||
}
|
||||
}
|
||||
|
||||
if ($distribute) {
|
||||
@ -985,15 +1020,13 @@ class Notice extends Managed_DataObject
|
||||
}
|
||||
|
||||
static public function figureOutScope(Profile $actor, array $groups, $scope=null) {
|
||||
if (is_null($scope)) {
|
||||
$scope = self::defaultScope();
|
||||
}
|
||||
$scope = is_null($scope) ? self::defaultScope() : intval($scope);
|
||||
|
||||
// For private streams
|
||||
try {
|
||||
$user = $actor->getUser();
|
||||
// FIXME: We can't do bit comparison with == (Legacy StatusNet thing. Let's keep it for now.)
|
||||
if ($user->private_stream && ($scope == Notice::PUBLIC_SCOPE || $scope == Notice::SITE_SCOPE)) {
|
||||
if ($user->private_stream && ($scope === Notice::PUBLIC_SCOPE || $scope === Notice::SITE_SCOPE)) {
|
||||
$scope |= Notice::FOLLOWER_SCOPE;
|
||||
}
|
||||
} catch (NoSuchUserException $e) {
|
||||
@ -1025,8 +1058,10 @@ class Notice extends Managed_DataObject
|
||||
$this->blowStream('networkpublic');
|
||||
}
|
||||
|
||||
self::blow('notice:list-ids:conversation:%s', $this->conversation);
|
||||
self::blow('conversation:notice_count:%d', $this->conversation);
|
||||
if ($this->conversation) {
|
||||
self::blow('notice:list-ids:conversation:%s', $this->conversation);
|
||||
self::blow('conversation:notice_count:%d', $this->conversation);
|
||||
}
|
||||
|
||||
if ($this->isRepeat()) {
|
||||
// XXX: we should probably only use one of these
|
||||
@ -1320,6 +1355,10 @@ class Notice extends Managed_DataObject
|
||||
}
|
||||
} catch (NoParentNoticeException $e) {
|
||||
// Latest notice has no parent
|
||||
} catch (NoResultException $e) {
|
||||
// Notice was not found, so we can't go further up in the tree.
|
||||
// FIXME: Maybe we should do this in a more stable way where deleted
|
||||
// notices won't break conversation chains?
|
||||
}
|
||||
// No parent, or parent out of scope
|
||||
$root = $last;
|
||||
@ -1491,13 +1530,8 @@ class Notice extends Managed_DataObject
|
||||
* best with generalizations on user_group to support
|
||||
* remote groups better.
|
||||
*/
|
||||
function saveKnownGroups($group_ids)
|
||||
function saveKnownGroups(array $group_ids)
|
||||
{
|
||||
if (!is_array($group_ids)) {
|
||||
// TRANS: Server exception thrown when no array is provided to the method saveKnownGroups().
|
||||
throw new ServerException(_('Bad type provided to saveKnownGroups.'));
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
foreach (array_unique($group_ids) as $id) {
|
||||
$group = User_group::getKV('id', $id);
|
||||
@ -1573,7 +1607,7 @@ class Notice extends Managed_DataObject
|
||||
return;
|
||||
}
|
||||
|
||||
$sender = Profile::getKV($this->profile_id);
|
||||
$sender = $this->getProfile();
|
||||
|
||||
foreach (array_unique($uris) as $uri) {
|
||||
try {
|
||||
@ -1588,11 +1622,9 @@ class Notice extends Managed_DataObject
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->saveReply($profile->id);
|
||||
self::blow('reply:stream:%d', $profile->id);
|
||||
$this->saveReply($profile->getID());
|
||||
self::blow('reply:stream:%d', $profile->getID());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1607,12 +1639,6 @@ class Notice extends Managed_DataObject
|
||||
|
||||
function saveReplies()
|
||||
{
|
||||
// Don't save reply data for repeats
|
||||
|
||||
if ($this->isRepeat()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sender = $this->getProfile();
|
||||
|
||||
$replied = array();
|
||||
@ -1621,17 +1647,21 @@ class Notice extends Managed_DataObject
|
||||
try {
|
||||
$parent = $this->getParent();
|
||||
$parentauthor = $parent->getProfile();
|
||||
$this->saveReply($parentauthor->id);
|
||||
$replied[$parentauthor->id] = 1;
|
||||
self::blow('reply:stream:%d', $parentauthor->id);
|
||||
$this->saveReply($parentauthor->getID());
|
||||
$replied[$parentauthor->getID()] = 1;
|
||||
self::blow('reply:stream:%d', $parentauthor->getID());
|
||||
} catch (NoParentNoticeException $e) {
|
||||
// Not a reply, since it has no parent!
|
||||
$parent = null;
|
||||
} catch (NoResultException $e) {
|
||||
// Parent notice was probably deleted
|
||||
$parent = null;
|
||||
}
|
||||
|
||||
// @todo ideally this parser information would only
|
||||
// be calculated once.
|
||||
|
||||
$mentions = common_find_mentions($this->content, $this);
|
||||
$mentions = common_find_mentions($this->content, $sender, $parent);
|
||||
|
||||
// store replied only for first @ (what user/notice what the reply directed,
|
||||
// we assume first @ is it)
|
||||
@ -1720,7 +1750,6 @@ class Notice extends Managed_DataObject
|
||||
function sendReplyNotifications()
|
||||
{
|
||||
// Don't send reply notifications for repeats
|
||||
|
||||
if ($this->isRepeat()) {
|
||||
return array();
|
||||
}
|
||||
@ -1730,9 +1759,11 @@ class Notice extends Managed_DataObject
|
||||
require_once INSTALLDIR.'/lib/mail.php';
|
||||
|
||||
foreach ($recipientIds as $recipientId) {
|
||||
$user = User::getKV('id', $recipientId);
|
||||
if ($user instanceof User) {
|
||||
try {
|
||||
$user = User::getByID($recipientId);
|
||||
mail_notify_attn($user, $this);
|
||||
} catch (NoResultException $e) {
|
||||
// No such user
|
||||
}
|
||||
}
|
||||
Event::handle('EndNotifyMentioned', array($this, $recipientIds));
|
||||
@ -1851,6 +1882,8 @@ class Notice extends Managed_DataObject
|
||||
$ctx->replyToUrl = $reply->getUrl(true); // true for fallback to local URL, less messy
|
||||
} catch (NoParentNoticeException $e) {
|
||||
// This is not a reply to something
|
||||
} catch (NoResultException $e) {
|
||||
// Parent notice was probably deleted
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2035,6 +2068,7 @@ class Notice extends Managed_DataObject
|
||||
if (Event::handle('StartActivityObjectFromNotice', array($this, &$object))) {
|
||||
$object->type = $this->object_type ?: ActivityObject::NOTE;
|
||||
$object->id = $this->getUri();
|
||||
//FIXME: = $object->title ?: sprintf(... because we might get a title from StartActivityObjectFromNotice
|
||||
$object->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $this->getProfile()->getNickname());
|
||||
$object->content = $this->rendered;
|
||||
$object->link = $this->getUrl();
|
||||
@ -2407,7 +2441,7 @@ class Notice extends Managed_DataObject
|
||||
$this->uri = sprintf('%s%s=%d:%s=%s',
|
||||
TagURI::mint(),
|
||||
'noticeId', $this->id,
|
||||
'objectType', $this->get_object_type(true));
|
||||
'objectType', $this->getObjectType(true));
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
@ -2469,8 +2503,13 @@ class Notice extends Managed_DataObject
|
||||
|
||||
public function isLocal()
|
||||
{
|
||||
return ($this->is_local == Notice::LOCAL_PUBLIC ||
|
||||
$this->is_local == Notice::LOCAL_NONPUBLIC);
|
||||
$is_local = intval($this->is_local);
|
||||
return ($is_local === self::LOCAL_PUBLIC || $is_local === self::LOCAL_NONPUBLIC);
|
||||
}
|
||||
|
||||
public function getScope()
|
||||
{
|
||||
return intval($this->scope);
|
||||
}
|
||||
|
||||
public function isRepeat()
|
||||
@ -2663,13 +2702,9 @@ class Notice extends Managed_DataObject
|
||||
|
||||
protected function _inScope($profile)
|
||||
{
|
||||
if (!is_null($this->scope)) {
|
||||
$scope = $this->scope;
|
||||
} else {
|
||||
$scope = self::defaultScope();
|
||||
}
|
||||
$scope = is_null($this->scope) ? self::defaultScope() : $this->getScope();
|
||||
|
||||
if ($scope == 0 && !$this->getProfile()->isPrivateStream()) { // Not scoping, so it is public.
|
||||
if ($scope === 0 && !$this->getProfile()->isPrivateStream()) { // Not scoping, so it is public.
|
||||
return !$this->isHiddenSpam($profile);
|
||||
}
|
||||
|
||||
@ -2754,12 +2789,35 @@ class Notice extends Managed_DataObject
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasParent()
|
||||
{
|
||||
try {
|
||||
$this->getParent();
|
||||
} catch (NoParentNoticeException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
$reply_to_id = null;
|
||||
|
||||
if (empty($this->reply_to)) {
|
||||
throw new NoParentNoticeException($this);
|
||||
}
|
||||
return self::getByID($this->reply_to);
|
||||
|
||||
// The reply_to ID in the table Notice could exist with a number
|
||||
// however, the replied to notice might not exist in the database.
|
||||
// Thus we need to catch the exception and throw the NoParentNoticeException else
|
||||
// the timeline will not display correctly.
|
||||
try {
|
||||
$reply_to_id = self::getByID($this->reply_to);
|
||||
} catch(Exception $e){
|
||||
throw new NoParentNoticeException($this);
|
||||
}
|
||||
|
||||
return $reply_to_id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,7 +166,7 @@ class Oauth_application extends Managed_DataObject
|
||||
'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'application consumer key'),
|
||||
'name' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the application'),
|
||||
'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description of the application'),
|
||||
'icon' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'application icon'),
|
||||
'icon' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'default' => '/theme/base/default-avatar-stream.png', 'description' => 'application icon'),
|
||||
'source_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'application homepage - used for source link'),
|
||||
'organization' => array('type' => 'varchar', 'length' => 191, 'description' => 'name of the organization running the application'),
|
||||
'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'homepage for the organization'),
|
||||
|
@ -17,24 +17,21 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Table Definition for profile
|
||||
*/
|
||||
class Profile extends Managed_DataObject
|
||||
{
|
||||
###START_AUTOCODE
|
||||
/* the code below is auto generated do not remove the above tag */
|
||||
|
||||
public $__table = 'profile'; // table name
|
||||
public $id; // int(4) primary_key not_null
|
||||
public $nickname; // varchar(64) multiple_key not_null
|
||||
public $fullname; // varchar(191) multiple_key not 255 because utf8mb4 takes more space
|
||||
public $profileurl; // varchar(191) not 255 because utf8mb4 takes more space
|
||||
public $homepage; // varchar(191) multiple_key not 255 because utf8mb4 takes more space
|
||||
public $fullname; // text()
|
||||
public $profileurl; // text()
|
||||
public $homepage; // text()
|
||||
public $bio; // text() multiple_key
|
||||
public $location; // varchar(191) multiple_key not 255 because utf8mb4 takes more space
|
||||
public $location; // text()
|
||||
public $lat; // decimal(10,7)
|
||||
public $lon; // decimal(10,7)
|
||||
public $location_id; // int(4)
|
||||
@ -49,11 +46,11 @@ class Profile extends Managed_DataObject
|
||||
'fields' => array(
|
||||
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
|
||||
'nickname' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username', 'collate' => 'utf8mb4_general_ci'),
|
||||
'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name', 'collate' => 'utf8mb4_general_ci'),
|
||||
'profileurl' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'),
|
||||
'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'identifying URL', 'collate' => 'utf8mb4_general_ci'),
|
||||
'fullname' => array('type' => 'text', 'description' => 'display name', 'collate' => 'utf8mb4_general_ci'),
|
||||
'profileurl' => array('type' => 'text', 'description' => 'URL, cached so we dont regenerate'),
|
||||
'homepage' => array('type' => 'text', 'description' => 'identifying URL', 'collate' => 'utf8mb4_general_ci'),
|
||||
'bio' => array('type' => 'text', 'description' => 'descriptive biography', 'collate' => 'utf8mb4_general_ci'),
|
||||
'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'physical location', 'collate' => 'utf8mb4_general_ci'),
|
||||
'location' => array('type' => 'text', 'description' => 'physical location', 'collate' => 'utf8mb4_general_ci'),
|
||||
'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'),
|
||||
'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'),
|
||||
'location_id' => array('type' => 'int', 'description' => 'location id if possible'),
|
||||
@ -76,9 +73,6 @@ class Profile extends Managed_DataObject
|
||||
|
||||
return $def;
|
||||
}
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
public static function getByEmail($email)
|
||||
{
|
||||
@ -1242,8 +1236,9 @@ class Profile extends Managed_DataObject
|
||||
{
|
||||
// XXX: not really a pkey, but should work
|
||||
|
||||
$notice = Notice::pkeyGet(array('profile_id' => $this->id,
|
||||
'repeat_of' => $notice->id));
|
||||
$notice = Notice::pkeyGet(array('profile_id' => $this->getID(),
|
||||
'repeat_of' => $notice->getID(),
|
||||
'verb' => ActivityVerb::SHARE));
|
||||
|
||||
return !empty($notice);
|
||||
}
|
||||
@ -1438,6 +1433,11 @@ class Profile extends Managed_DataObject
|
||||
$user = User::getKV('id', $this->id);
|
||||
if ($user instanceof User) {
|
||||
$uri = $user->getUri();
|
||||
} else {
|
||||
$group = User_group::getKV('profile_id', $this->id);
|
||||
if ($group instanceof User_group) {
|
||||
$uri = $group->getUri();
|
||||
}
|
||||
}
|
||||
|
||||
Event::handle('EndGetProfileUri', array($this, &$uri));
|
||||
@ -1593,8 +1593,20 @@ class Profile extends Managed_DataObject
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sameAs(Profile $other)
|
||||
/**
|
||||
* Test whether the given profile is the same as the current class,
|
||||
* for testing identities.
|
||||
*
|
||||
* @param Profile $other The other profile, usually from Action's $this->scoped
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function sameAs(Profile $other=null)
|
||||
{
|
||||
if (is_null($other)) {
|
||||
// In case $this->scoped is null or something, i.e. not a current/legitimate profile.
|
||||
return false;
|
||||
}
|
||||
return $this->getID() === $other->getID();
|
||||
}
|
||||
|
||||
|
@ -21,20 +21,10 @@
|
||||
* @license GNU Affero General Public License http://www.gnu.org/licenses/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table Definition for profile_list
|
||||
*/
|
||||
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
class Profile_list extends Managed_DataObject
|
||||
{
|
||||
###START_AUTOCODE
|
||||
/* the code below is auto generated do not remove the above tag */
|
||||
|
||||
public $__table = 'profile_list'; // table name
|
||||
public $id; // int(4) primary_key not_null
|
||||
public $tagger; // int(4)
|
||||
@ -48,9 +38,6 @@ class Profile_list extends Managed_DataObject
|
||||
public $tagged_count; // smallint
|
||||
public $subscriber_count; // smallint
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
@ -94,7 +81,7 @@ class Profile_list extends Managed_DataObject
|
||||
|
||||
function getTagger()
|
||||
{
|
||||
return Profile::getKV('id', $this->tagger);
|
||||
return Profile::getByID($this->tagger);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +132,7 @@ class Profile_list extends Managed_DataObject
|
||||
$url = $this->mainpage;
|
||||
} else {
|
||||
$url = common_local_url('showprofiletag',
|
||||
array('tagger' => $this->getTagger()->nickname,
|
||||
array('nickname' => $this->getTagger()->nickname,
|
||||
'tag' => $this->tag));
|
||||
}
|
||||
}
|
||||
@ -659,7 +646,7 @@ class Profile_list extends Managed_DataObject
|
||||
$orig = clone($ptag);
|
||||
$user = User::getKV('id', $ptag->tagger);
|
||||
if(!empty($user)) {
|
||||
$ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'tagger' => $user->nickname));
|
||||
$ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'nickname' => $user->getNickname()));
|
||||
} else {
|
||||
$ptag->mainpage = $uri; // assume this is a remote peopletag and the uri works
|
||||
}
|
||||
|
@ -85,17 +85,17 @@ class Profile_prefs extends Managed_DataObject
|
||||
static function getAll(Profile $profile)
|
||||
{
|
||||
try {
|
||||
$prefs = self::listFind('profile_id', $profile->getID());
|
||||
$prefs = self::listFind('profile_id', array($profile->getID()));
|
||||
} catch (NoResultException $e) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$list = array();
|
||||
while ($entry = $prefs->fetch()) {
|
||||
if (!isset($list[$entry->namespace])) {
|
||||
$list[$entry->namespace] = array();
|
||||
while ($prefs->fetch()) {
|
||||
if (!isset($list[$prefs->namespace])) {
|
||||
$list[$prefs->namespace] = array();
|
||||
}
|
||||
$list[$entry->namespace][$entry->topic] = $entry->data;
|
||||
$list[$prefs->namespace][$prefs->topic] = $prefs->data;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
@ -68,16 +68,15 @@ class Queue_item extends Managed_DataObject
|
||||
// XXX: potential race condition
|
||||
// can we force it to only update if claimed is still null
|
||||
// (or old)?
|
||||
common_log(LOG_INFO, 'claiming queue item id = ' . $qi->getID() .
|
||||
' for transport ' . $qi->transport);
|
||||
common_log(LOG_INFO, 'claiming queue item id = ' . $qi->getID() . ' for transport ' . $qi->transport);
|
||||
$orig = clone($qi);
|
||||
$qi->claimed = common_sql_now();
|
||||
$result = $qi->update($orig);
|
||||
if ($result) {
|
||||
common_log(LOG_INFO, 'claim succeeded.');
|
||||
common_log(LOG_DEBUG, 'claim succeeded.');
|
||||
return $qi;
|
||||
} else {
|
||||
common_log(LOG_INFO, 'claim failed.');
|
||||
common_log(LOG_ERR, 'claim of queue item id= ' . $qi->getID() . ' for transport ' . $qi->transport . ' failed.');
|
||||
}
|
||||
}
|
||||
$qi = null;
|
||||
|
@ -159,8 +159,8 @@ class Subscription extends Managed_DataObject
|
||||
$sub->jabber = 1;
|
||||
$sub->sms = 1;
|
||||
$sub->created = common_sql_now();
|
||||
$sub->uri = self::newURI($sub->subscriber,
|
||||
$sub->subscribed,
|
||||
$sub->uri = self::newUri($subscriber,
|
||||
$other,
|
||||
$sub->created);
|
||||
|
||||
$result = $sub->insert();
|
||||
@ -267,18 +267,20 @@ class Subscription extends Managed_DataObject
|
||||
return $sub;
|
||||
}
|
||||
|
||||
public function getSubscriber()
|
||||
{
|
||||
return Profile::getByID($this->subscriber);
|
||||
}
|
||||
|
||||
public function getSubscribed()
|
||||
{
|
||||
return Profile::getByID($this->subscribed);
|
||||
}
|
||||
|
||||
function asActivity()
|
||||
{
|
||||
$subscriber = Profile::getKV('id', $this->subscriber);
|
||||
$subscribed = Profile::getKV('id', $this->subscribed);
|
||||
|
||||
if (!$subscriber instanceof Profile) {
|
||||
throw new NoProfileException($this->subscriber);
|
||||
}
|
||||
|
||||
if (!$subscribed instanceof Profile) {
|
||||
throw new NoProfileException($this->subscribed);
|
||||
}
|
||||
$subscriber = $this->getSubscriber();
|
||||
$subscribed = $this->getSubscribed();
|
||||
|
||||
$act = new Activity();
|
||||
|
||||
@ -286,7 +288,7 @@ class Subscription extends Managed_DataObject
|
||||
|
||||
// XXX: rationalize this with the URL
|
||||
|
||||
$act->id = $this->getURI();
|
||||
$act->id = $this->getUri();
|
||||
|
||||
$act->time = strtotime($this->created);
|
||||
// TRANS: Activity title when subscribing to another person.
|
||||
@ -431,20 +433,8 @@ class Subscription extends Managed_DataObject
|
||||
return parent::update($dataObject);
|
||||
}
|
||||
|
||||
function getURI()
|
||||
public function getUri()
|
||||
{
|
||||
if (!empty($this->uri)) {
|
||||
return $this->uri;
|
||||
} else {
|
||||
return self::newURI($this->subscriber, $this->subscribed, $this->created);
|
||||
}
|
||||
}
|
||||
|
||||
static function newURI($subscriber_id, $subscribed_id, $created)
|
||||
{
|
||||
return TagURI::mint('follow:%d:%d:%s',
|
||||
$subscriber_id,
|
||||
$subscribed_id,
|
||||
common_date_iso8601($created));
|
||||
return $this->uri ?: self::newUri($this->getSubscriber(), $this->getSubscribed(), $this->created);
|
||||
}
|
||||
}
|
||||
|
@ -1012,6 +1012,24 @@ class User extends Managed_DataObject
|
||||
return !empty($this->password);
|
||||
}
|
||||
|
||||
public function setPassword($password)
|
||||
{
|
||||
$orig = clone($this);
|
||||
$this->password = common_munge_password($password, $this->getProfile());
|
||||
|
||||
if ($this->validate() !== true) {
|
||||
// TRANS: Form validation error on page where to change password.
|
||||
throw new ServerException(_('Error saving user; invalid.'));
|
||||
}
|
||||
|
||||
if (!$this->update($orig)) {
|
||||
common_log_db_error($this, 'UPDATE', __FILE__);
|
||||
// TRANS: Server error displayed on page where to change password when password change
|
||||
// TRANS: could not be made because of a server error.
|
||||
throw new ServerException(_('Cannot save new password.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function delPref($namespace, $topic)
|
||||
{
|
||||
return $this->getProfile()->delPref($namespace, $topic);
|
||||
|
@ -33,6 +33,12 @@ class User_group extends Managed_DataObject
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
public function getObjectType()
|
||||
{
|
||||
return ActivityObject::GROUP;
|
||||
}
|
||||
|
||||
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
@ -214,24 +220,19 @@ class User_group extends Managed_DataObject
|
||||
*/
|
||||
function getRequests($offset=0, $limit=null)
|
||||
{
|
||||
$qry =
|
||||
'SELECT profile.* ' .
|
||||
'FROM profile JOIN group_join_queue '.
|
||||
'ON profile.id = group_join_queue.profile_id ' .
|
||||
'WHERE group_join_queue.group_id = %d ' .
|
||||
'ORDER BY group_join_queue.created DESC ';
|
||||
|
||||
if ($limit != null) {
|
||||
if (common_config('db','type') == 'pgsql') {
|
||||
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
|
||||
} else {
|
||||
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
|
||||
}
|
||||
}
|
||||
$rq = new Group_join_queue();
|
||||
$rq->group_id = $this->id;
|
||||
|
||||
$members = new Profile();
|
||||
|
||||
$members->query(sprintf($qry, $this->id));
|
||||
$members->joinAdd(['id', $rq, 'profile_id']);
|
||||
|
||||
if ($limit != null) {
|
||||
$members->limit($offset, $limit);
|
||||
}
|
||||
|
||||
$members->find();
|
||||
|
||||
return $members;
|
||||
}
|
||||
|
||||
@ -818,7 +819,7 @@ class User_group extends Managed_DataObject
|
||||
function isPrivate()
|
||||
{
|
||||
return ($this->join_policy == self::JOIN_POLICY_MODERATE &&
|
||||
$this->force_scope == 1);
|
||||
intval($this->force_scope) === 1);
|
||||
}
|
||||
|
||||
public function isLocal()
|
||||
|
@ -72,7 +72,6 @@ $classes = array('Schema_version',
|
||||
'Group_block',
|
||||
'Group_alias',
|
||||
'Session',
|
||||
'Deleted_notice',
|
||||
'Config',
|
||||
'Profile_role',
|
||||
'Location_namespace',
|
||||
|
11
doc-src/api
11
doc-src/api
@ -2,5 +2,12 @@
|
||||
<!-- Document licensed under Creative Commons Attribution 3.0 Unported. See -->
|
||||
<!-- https://creativecommons.org/licenses/by/3.0/ for details. -->
|
||||
|
||||
%%site.name%% provides an API that applications can use to interact with it.
|
||||
More information about this API can be found on the [StatusNet Wiki](http://status.net/wiki/API).
|
||||
%%site.name%% provides APIs that applications can use to interact with it:
|
||||
|
||||
* [AtomPub](atompub)
|
||||
* [Twitter-compatible API](twitterapi)
|
||||
|
||||
## API discovery
|
||||
|
||||
The base URLs for the APIs can be obtained using [Really Simple Discovery](https://en.wikipedia.org/wiki/Really_Simple_Discovery).
|
||||
|
||||
|
229
doc-src/atompub
Normal file
229
doc-src/atompub
Normal file
@ -0,0 +1,229 @@
|
||||
> The Atom Publishing Protocol (AtomPub) is an application-level
|
||||
> protocol for publishing and editing Web resources. The protocol is
|
||||
> based on HTTP transfer of Atom-formatted representations. The Atom
|
||||
> format is documented in the Atom Syndication Format.
|
||||
|
||||
You can find more information about AtomPub in [RFC5023](https://tools.ietf.org/html/rfc5023).
|
||||
|
||||
> Activity Streams is an open format specification for activity stream protocols,
|
||||
> which are used to syndicate activities taken in social web applications and
|
||||
> services.
|
||||
|
||||
You can find more information about Activity Streams at [activitystrea.ms](http://activitystrea.ms/).
|
||||
|
||||
## Authentication
|
||||
|
||||
The API supports both
|
||||
[HTTP Basic](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
||||
and [OAuth](https://en.wikipedia.org/wiki/OAuth).
|
||||
|
||||
## Service document
|
||||
|
||||
The service document for an account is found at
|
||||
`/api/statusnet/app/service/<nickname>.xml`
|
||||
|
||||
Each service document has one workspace ('Main') and four collections:
|
||||
|
||||
* **notices**: notices generated by the user
|
||||
* **subscriptions**: subscriptions by the user
|
||||
* **favorites**: the user's favorites
|
||||
* **memberships**: the user's group memberships
|
||||
|
||||
Collections are identified by the `<activity:verb>` element(s) in their
|
||||
`<collection>` element.
|
||||
|
||||
## Notices
|
||||
|
||||
Notice feeds, in reverse-chronological order, are at
|
||||
`/api/statuses/user_timeline/<id>.atom`.
|
||||
|
||||
This is a partial feed; navigation links are included in the feed to scroll forward
|
||||
and back.
|
||||
|
||||
Notices are represented as Activity Streams events with the "Post" verb and "Note" object-type:
|
||||
|
||||
<entry>
|
||||
<activity:object-type>
|
||||
http://activitystrea.ms/schema/1.0/note
|
||||
</activity:object-type>
|
||||
[...]
|
||||
<activity:verb>
|
||||
http://activitystrea.ms/schema/1.0/post
|
||||
</activity:verb>
|
||||
[...]
|
||||
</entry>
|
||||
|
||||
Repeats are be represented as Activity Streams events with the "Share" verb, and with the activity object being an entry representing a Notice:
|
||||
|
||||
<entry>
|
||||
<activity:verb>
|
||||
http://activitystrea.ms/schema/1.0/share
|
||||
</activity:verb>
|
||||
[...]
|
||||
<activity:object>
|
||||
<activity:object-type>
|
||||
http://activitystrea.ms/schema/1.0/activity
|
||||
</activity:object-type>
|
||||
[...]
|
||||
<activity:verb>
|
||||
http://activitystrea.ms/schema/1.0/post
|
||||
</activity:verb>
|
||||
[...]
|
||||
<activity:object>
|
||||
<activity:object-type>
|
||||
http://activitystrea.ms/schema/1.0/note
|
||||
</activity:object-type>
|
||||
[...]
|
||||
</activity:object>
|
||||
[...]
|
||||
</activity:object>
|
||||
[...]
|
||||
</entry>
|
||||
|
||||
Posted files will be represented by the "Post" verb and "Image, File, Video" object-type.
|
||||
|
||||
### Single-notice URL
|
||||
|
||||
Single notices are be available as an Activity Streams event at `/api/statuses/show/<notice-id>.atom`.
|
||||
|
||||
<entry>
|
||||
<activity:object-type>
|
||||
http://activitystrea.ms/schema/1.0/note
|
||||
</activity:object-type>
|
||||
[...]
|
||||
<activity:verb>
|
||||
http://activitystrea.ms/schema/1.0/post
|
||||
</activity:verb>
|
||||
<author>
|
||||
<activity:object-type>
|
||||
http://activitystrea.ms/schema/1.0/person
|
||||
</activity:object-type>
|
||||
[...]
|
||||
</author>
|
||||
</entry>
|
||||
|
||||
### Posting a notice
|
||||
|
||||
A notice can be posted by sending a POST request containing a single `<entry>`
|
||||
element to the URL of the notice feed. These should have a "Post" verb, and a "Note"
|
||||
object-type, but since these are the default values, Atom entries that aren't
|
||||
marked up as Activity Streams objects should be fine to post.
|
||||
|
||||
The resulting entry will be returned, per the APP, in Activity Streams format. The
|
||||
location of the notice can be read from the Content-Location HTTP header of the
|
||||
result or from the rel=self URL in the resulting entry.
|
||||
|
||||
### Editing a notice
|
||||
|
||||
Notices cannot be edited. PUT requests to a notice URL will fail.
|
||||
|
||||
### Deleting a notice
|
||||
|
||||
A single notice can be deleted by posting a DELETE HTTP request to the notice's
|
||||
Atom representation.
|
||||
|
||||
Example with cURL:
|
||||
|
||||
curl -u username:password -X DELETE \
|
||||
http://example.org/api/statuses/show/<notice-id>.atom
|
||||
|
||||
## Subscriptions
|
||||
|
||||
The subscriptions feed, in reverse-chronological order, is at
|
||||
`/api/statusnet/app/subscriptions/<id>.atom`.
|
||||
|
||||
This is a partial feed; it includes the navigation links necessary to scroll forward
|
||||
and back.
|
||||
|
||||
Subscriptions are represented as Activity Streams entries with the "Follow" verb and
|
||||
"Person" object-type.
|
||||
|
||||
### Subscription URL
|
||||
|
||||
A subscription has an URL at
|
||||
`/api/statusnet/app/subscriptions/<subscriber id>/<subscribed id>.atom`.
|
||||
|
||||
### Adding a new subscription
|
||||
|
||||
To add a new subscription, POST an Activity Streams `<entry>` with a "Follow" verb
|
||||
and "Person" object-type.
|
||||
|
||||
The resulting entry will be returned, per the APP, in Activity Streams format. The
|
||||
location of the subscription can be read from the Content-Location HTTP header of
|
||||
the result or from the rel=self URL in the resulting entry.
|
||||
|
||||
### Editing a subscription
|
||||
|
||||
Subscriptions cannot be edited. PUT requests to the subscription URL will result in
|
||||
an error.
|
||||
|
||||
### Deleting a subscription
|
||||
|
||||
To delete a subscription, send a DELETE HTTP request to the Subscription URL.
|
||||
|
||||
## Favorites
|
||||
|
||||
The feed of the user's favorites, in reverse-chronological order, is at
|
||||
`/api/statusnet/app/favorites/<user id>.atom`.
|
||||
|
||||
This is a partial feed; it includes the navigation links necessary to scroll forward
|
||||
and back.
|
||||
|
||||
Favorites are represented as Activity Streams entries with the "Favorite" verb and
|
||||
"Note" object-type.
|
||||
|
||||
### Favorite URL
|
||||
|
||||
Favorite entries have a self URL at
|
||||
`/api/statusnet/app/favorites/<user id>/<notice id>.atom`.
|
||||
|
||||
### Favoriting a notice
|
||||
|
||||
To favorite a notice, POST an Activity Streams `<entry>` with the "Favorite" verb and
|
||||
"Note" object-type.
|
||||
|
||||
The resulting favorite will be returned, per the APP, in Activity Streams format.
|
||||
The location of the favorite can be read from the Content-Location HTTP header of
|
||||
the result or from the rel=self URL in the resulting entry.
|
||||
|
||||
### Editing a favorite
|
||||
|
||||
Favorites cannot be edited. PUT requests to a favorite URL will fail.
|
||||
|
||||
### Deleting a favorite
|
||||
|
||||
To "unfavorite" a notice, POST a DELETE request to the URL for the favorite.
|
||||
|
||||
## Groups
|
||||
|
||||
A feed of group memberships, in reverse-chron order, is available at
|
||||
`/api/statusnet/app/memberships/<user id>.atom`.
|
||||
|
||||
This is a partial feed; it includes the navigation links necessary to scroll forward
|
||||
and back.
|
||||
|
||||
Memberships are represented as Activity Streams entries with the "Join" ber and
|
||||
"Group" object-type.
|
||||
|
||||
### Membership URL
|
||||
|
||||
Each membership has a representation at
|
||||
`/api/statusnet/app/memberships/<user id>/<group id>.atom`.
|
||||
|
||||
### Joining a group
|
||||
|
||||
To join a group, POST an activity entry with a "Join" verb and "Group" object-type to
|
||||
the memberships feed.
|
||||
|
||||
The resulting membership will be returned, per the APP, in Activity Streams format.
|
||||
The location of the membership can be read from the Content-Location HTTP header of
|
||||
the result or from the rel=self URL in the resulting entry.
|
||||
|
||||
### Editing group membership
|
||||
|
||||
Group memberships cannot be edited. PUT requests to a membership feed will fail.
|
||||
|
||||
### Leaving a group
|
||||
|
||||
To leave a group, send a DELETE request to the membership URL.
|
||||
|
430
doc-src/twitterapi
Normal file
430
doc-src/twitterapi
Normal file
@ -0,0 +1,430 @@
|
||||
## Authentication
|
||||
|
||||
### HTTP Basic authentication
|
||||
|
||||
The API uses [HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
|
||||
Note that this means that users with only an OpenID login cannot use the API; they have to add a
|
||||
password to their account using the control panel on the site.
|
||||
|
||||
### OAuth authentication
|
||||
|
||||
OAuth 1.0a authentication for API resources is also supported. Generally, StatusNet's
|
||||
UI and API are similar to Twitter's for OAuth applications (if you're new to OAuth
|
||||
check out [Beginner’s Guide to OAuth](http://hueniverse.com/oauth/)).
|
||||
|
||||
To use OAuth, you'll need to register your client application via the web interface
|
||||
and obtain a consumer key and secret. You can find the interface for application
|
||||
registration at [http://%%site.server%%/%%site.path%%settings/oauthapps](http://%%site.server%%/%%site.path%%settings/oauthapps).
|
||||
|
||||
## JSONP callbacks
|
||||
|
||||
For API methods that return [JSON](https://en.wikipedia.org/wiki/JSON), an optional
|
||||
JSONP-style callback parameter is supported. If supplied, the response will be in
|
||||
JSONP format with a callback of the given name. To make it easier for clients to
|
||||
handle error conditions, HTTP error codes are suppressed, and the errors will be
|
||||
returned in the response body when using JSONP.
|
||||
|
||||
## Rate limiting
|
||||
|
||||
There is currently no rate-limiting.
|
||||
|
||||
## Gotchas
|
||||
|
||||
Some things to remember:
|
||||
|
||||
* %%site.name%% supports the
|
||||
[OStatus federation protocol](https://en.wikipedia.org/wiki/OStatus) (as well as
|
||||
[OpenMicroBlogging](https://en.wikipedia.org/wiki/OpenMicroBlogging) for backwards
|
||||
compatibility), so many notices and friends' profiles may come from other servers.
|
||||
* User nicknames are unique, but they are not globally unique. Use the ID number
|
||||
instead.
|
||||
* Private streams are not implemented yet.
|
||||
* GNU social sites can be configured as private. In that case, all API methods
|
||||
require authentication, including the public timeline (see the 'config' method
|
||||
below).
|
||||
* If "Fancy URLs" are not enabled, urls from above need to include "index.php" at
|
||||
the root. ( e.g. http://example.org/statusnet/api becomes http://www.example.org/statusnet/index.php/api )
|
||||
* The `since_id` parameter does not work as documented by Twitter. Twitter says of
|
||||
`since_id`: "There are limits to the number of Tweets which can be accessed
|
||||
through the API. If the limit of Tweets has occured since the `since_id`, the
|
||||
`since_id` will be forced to the oldest ID available." However, GNU social will
|
||||
return the newest notices (or the newest back from max_id, if present)! Also, a
|
||||
`since_id` <= 0 will be ignored.
|
||||
|
||||
## Timeline resources
|
||||
|
||||
### statuses/public_timeline
|
||||
|
||||
Returns the 20 most recent notices, including repeats if they exist, from
|
||||
non-protected users.
|
||||
|
||||
### statuses/home_timeline
|
||||
|
||||
Returns the 20 most recent notices, including repeats if they exist, posted by the
|
||||
authenticating user and the users they follow. This is the same timeline seen by a
|
||||
user when they login to their instance. This method is identical to
|
||||
statuses/friends_timeline, except that this method always includes repeats.
|
||||
|
||||
### statuses/friends_timeline
|
||||
|
||||
Alias of statuses/home_timeline
|
||||
|
||||
### statuses/friends_timeline/:username
|
||||
|
||||
Alias of statuses/home_timeline for the specified username
|
||||
|
||||
### statuses/mentions
|
||||
|
||||
Returns the 20 most recent mentions (notices containing @username) for the
|
||||
authenticating user.
|
||||
|
||||
This method will not include repeats in the XML and JSON responses unless the
|
||||
include_rts parameter is set. The RSS and Atom responses will always include repeats
|
||||
as notices prefixed with RT.
|
||||
|
||||
### statuses/replies
|
||||
|
||||
Alias of statuses/mentions
|
||||
|
||||
### statuses/replies/:username
|
||||
|
||||
Alias of statuses/mentions for the specified username
|
||||
|
||||
### statuses/user_timeline
|
||||
|
||||
Returns the 20 most recent notices posted by the authenticating user. It is also
|
||||
possible to request another user's timeline by using the screen\_name or user_id
|
||||
parameter. The other users timeline will only be visible if they are not protected,
|
||||
or if the authenticating user's follow request was accepted by the protected user.
|
||||
|
||||
This method will not include repeats in the XML and JSON responses unless the
|
||||
include_rts parameter is set. The RSS and Atom responses will always include
|
||||
repeats as notices prefixed with RT, regardless of provided parameters.
|
||||
|
||||
### statuses/retweeted\_to_me
|
||||
|
||||
Not implemented.
|
||||
|
||||
### statuses/retweeted\_by_me
|
||||
|
||||
Not implemented.
|
||||
|
||||
### statuses/retweets\_of_me
|
||||
|
||||
Not implemented.
|
||||
|
||||
## Status resources
|
||||
|
||||
### statuses/show/:id
|
||||
|
||||
Returns a single notice, specified by the id parameter. The notice's author will be
|
||||
returned inline.
|
||||
|
||||
### statuses/update
|
||||
|
||||
Post a new notice as the authenticating user.
|
||||
|
||||
Additional 'media' parameter allows binary multimedia uploads (images, etc.). Format
|
||||
post data as multipart/form-data when using the 'media' parameter.
|
||||
|
||||
### statuses/destroy/:id
|
||||
|
||||
Destroys the notice specified by the required ID parameter. The authenticating user
|
||||
must be the author of the specified notice. Returns the destroyed notice if successful.
|
||||
|
||||
### statuses/retweet/:id
|
||||
|
||||
Repeats a notice. Returns the original notice with repeat details embedded.
|
||||
|
||||
## User resources
|
||||
|
||||
### statuses/friends
|
||||
|
||||
Returns the user's subscriptions (friends) as an array of profiles.
|
||||
|
||||
### statuses/followers
|
||||
|
||||
Returns the user's subscribers (followers) as an array of profiles.
|
||||
|
||||
### users/show
|
||||
|
||||
Returns extended information of a given user, specified by ID or screen name as per
|
||||
the required id parameter.
|
||||
|
||||
## Direct message resources
|
||||
|
||||
### direct_messages
|
||||
|
||||
Returns the 20 most recent direct messages sent to the authenticating user. The XML
|
||||
and JSON versions include detailed information about the sender and recipient user.
|
||||
|
||||
### direct_messages/sent
|
||||
|
||||
Returns the 20 most recent direct messages sent by the authenticating user. The XML
|
||||
and JSON versions include detailed information about the sender and recipient user.
|
||||
|
||||
### direct_messages/new
|
||||
|
||||
Sends a new direct message to the specified user from the authenticating user.
|
||||
Requires both the user and text parameters and must be a POST. Returns the sent
|
||||
message in the requested format if successful.
|
||||
|
||||
### direct_messages/destroy
|
||||
|
||||
Not implemented.
|
||||
|
||||
## Friendships resources
|
||||
|
||||
### friendships/create
|
||||
|
||||
Allows the authenticating users to follow the user specified in the ID parameter.
|
||||
Returns the befriended user in the requested format when successful. Returns a
|
||||
string describing the failure condition when unsuccessful.
|
||||
|
||||
If you are already friends with the user a HTTP 403 may be returned, though for
|
||||
performance reasons you may get a 200 OK message even if the friendship already
|
||||
exists.
|
||||
|
||||
Note that users cannot subscribe to remote profiles using this API.
|
||||
|
||||
### friendships/destroy
|
||||
|
||||
Allows the authenticating users to unfollow the user specified in the ID parameter.
|
||||
Returns the unfollowed user in the requested format when successful. Returns a
|
||||
string describing the failure condition when unsuccessful.
|
||||
|
||||
Users can unsubscribe to a remote profile using this API, but it's preferred to use
|
||||
numeric IDs to nicknames.
|
||||
|
||||
### friendships/exists
|
||||
|
||||
Test for the existence of friendship between two users. Will return true if user\_a
|
||||
follows user_b, otherwise will return false. Authentication is required if either
|
||||
user A or user B are protected. Additionally the authenticating user must be a
|
||||
follower of the protected user.
|
||||
|
||||
### friendships/show
|
||||
|
||||
Returns detailed information about the relationship between two users.
|
||||
|
||||
## Friends and subscribers resources
|
||||
|
||||
### friends/ids
|
||||
|
||||
Returns an array of numeric IDs for every user the specified user is subscribed to.
|
||||
This method is powerful when used in conjunction with users/lookup.
|
||||
|
||||
### followers/ids
|
||||
|
||||
Returns an array of numeric IDs for every user subscsribed to the specified user.
|
||||
This method is powerful when used in conjunction with users/lookup.
|
||||
|
||||
## Account resources
|
||||
|
||||
### account/verify_credentials
|
||||
|
||||
Returns an HTTP 200 OK response code and a representation of the requesting user if
|
||||
authentication was successful; returns a 401 status code and an error message if
|
||||
not. Use this method to test if supplied user credentials are valid.
|
||||
|
||||
### account/end_session
|
||||
|
||||
Not implemented.
|
||||
|
||||
### account/update\_delivery_device
|
||||
|
||||
Not implemented.
|
||||
|
||||
### account/rate\_limit_status
|
||||
|
||||
Returns the remaining number of API requests available to the requesting user before
|
||||
the API limit is reached.
|
||||
|
||||
We have no rate limit, so this always returns 150 hits left.
|
||||
|
||||
### account/update\_profile\_background_image
|
||||
|
||||
Updates the authenticating user's profile background image. This method can also be
|
||||
used to enable or disable the profile background image.
|
||||
|
||||
### account/update\_profile_image
|
||||
|
||||
Updates the authenticating user's profile image. Note that this method expects raw
|
||||
multipart data, not a URL to an image.
|
||||
|
||||
## Favorite resources
|
||||
|
||||
### favorites
|
||||
|
||||
Returns the 20 most recent favorite statuses for the authenticating or specified
|
||||
user in the requested format.
|
||||
|
||||
### favorites/create/:id
|
||||
|
||||
Favorites the status specified in the ID parameter as the authenticating user.
|
||||
Returns the favorite status when successful.
|
||||
|
||||
### favorites/destroy/:id
|
||||
|
||||
Un-favorites the status specified in the ID parameter as the authenticating user.
|
||||
Returns the un-favorited status in the requested format when successful.
|
||||
|
||||
## Notification resources
|
||||
|
||||
### notifications/follow
|
||||
|
||||
Not implemented.
|
||||
|
||||
### notifications/leave
|
||||
|
||||
Not implemented.
|
||||
|
||||
## Block resources
|
||||
|
||||
### blocks/create
|
||||
|
||||
Blocks the specified user from following the authenticating user. In addition the
|
||||
blocked user will not show in the authenticating users mentions or timeline (unless
|
||||
retweeted by another user). If a follow or friend relationship exists it is
|
||||
destroyed.
|
||||
|
||||
### blocks/destroy
|
||||
|
||||
Un-blocks the user specified in the ID parameter for the authenticating user.
|
||||
Returns the un-blocked user in the requested format when successful. If
|
||||
relationships existed before the block was instated, they will not be restored.
|
||||
|
||||
### blocks/exists
|
||||
|
||||
Not implemented.
|
||||
|
||||
### blocks/blocking
|
||||
|
||||
Not implemented.
|
||||
|
||||
## Help resources
|
||||
|
||||
### help/test
|
||||
|
||||
Returns the string "ok" in the requested format with a 200 OK HTTP status code. This
|
||||
method is great for sending a HEAD request to determine our servers current time.
|
||||
|
||||
## OAuth resources
|
||||
|
||||
It is strongly recommended you use HTTPS for all OAuth authorization steps.
|
||||
|
||||
### oauth/request_token
|
||||
|
||||
Allows a Consumer application to obtain an OAuth Request Token to request user
|
||||
authorization. This method fulfills Section 6.1 of the OAuth 1.0 authentication
|
||||
flow. It is strongly recommended you use HTTPS for all OAuth authorization steps.
|
||||
|
||||
### oauth/authorize
|
||||
|
||||
Allows a Consumer application to use an OAuth Request Token to request user
|
||||
authorization. This method fulfills Section 6.2 of the OAuth 1.0 authentication
|
||||
flow. Desktop applications must use this method (and cannot use GET oauth/authenticate).
|
||||
|
||||
### oauth/access_token
|
||||
|
||||
Allows a Consumer application to exchange the OAuth Request Token for an OAuth
|
||||
Access Token. This method fulfills Section 6.3 of the OAuth 1.0 authentication flow.
|
||||
The OAuth access token may also be used for xAuth operations.
|
||||
|
||||
## Search
|
||||
|
||||
The search method supports the following optional URL parameters:
|
||||
|
||||
* **callback**: if supplied when using the JSON format, the response will use the
|
||||
JSONP format with a callback of the given name.
|
||||
* **rpp**: the number of notices to return per page, up to a max of 100.
|
||||
* **page**: the page number (starting at 1) to return.
|
||||
* **since_id:**: returns notices with ids greater than the given id.
|
||||
|
||||
Note:
|
||||
|
||||
* The search does not support operators, such as "from:", "to:" and booleans.
|
||||
* Notice content is HTML-encoded.
|
||||
|
||||
### search
|
||||
|
||||
Returns relevant notices that match a specified query.
|
||||
|
||||
### Atom
|
||||
|
||||
To request search results in Atom, append your URL-encoded query as a parameter to
|
||||
the search method and specify the Atom format:
|
||||
|
||||
`%%site.server%%/%%site.path%%api/search.atom?q=<query>`
|
||||
|
||||
### JSON
|
||||
|
||||
To request search results in JSON, append your URL-encoded query as a parameter to
|
||||
the search method and specify the JSON format:
|
||||
|
||||
`%%site.server%%/%%site.path%%api/search.json?q=<query>`
|
||||
|
||||
## Additional resources
|
||||
|
||||
These are extensions to the Twitter API that expose additional functionality.
|
||||
|
||||
### Group resources
|
||||
|
||||
#### statusnet/groups/timeline
|
||||
|
||||
Shows a group's timeline. Similar to other timeline resources.
|
||||
|
||||
#### statusnet/groups/show
|
||||
|
||||
Show a groups profile.
|
||||
|
||||
#### statusnet/groups/create
|
||||
|
||||
Create a new group.
|
||||
|
||||
#### statusnet/groups/join
|
||||
|
||||
Join a group.
|
||||
|
||||
#### statusnet/groups/leave
|
||||
|
||||
Leave a group.
|
||||
|
||||
#### statusnet/groups/list
|
||||
|
||||
Show the groups a given user is a member of.
|
||||
|
||||
#### statusnet/groups/list_all
|
||||
|
||||
List all local groups.
|
||||
|
||||
#### statusnet/groups/membership
|
||||
|
||||
List the members of a given group.
|
||||
|
||||
#### statusnet/groups/is_member
|
||||
|
||||
Determine whether a given user is a member of a given group.
|
||||
|
||||
### Tag resources
|
||||
|
||||
#### statusnet/tags/timeline
|
||||
|
||||
Shows a tag's timeline. Similar to other timeline resources.
|
||||
|
||||
### Media resources
|
||||
|
||||
#### statusnet/media/upload
|
||||
|
||||
Endpoint for uploading an image. Returns a URL that can be used in a status update.
|
||||
Format post data as multipart/form-data.
|
||||
|
||||
### Configuration
|
||||
|
||||
#### statusnet/config
|
||||
|
||||
Show an instance's configuration information.
|
||||
|
||||
Of special note is the `<private>` element (config/site/private), which indicates
|
||||
whether a site is private. When a site is configured as private every other API
|
||||
method requires authentication, including the public timeline (`/api/statuses/public_timeline.format`).
|
@ -771,7 +771,7 @@ var SN = { // StatusNet
|
||||
|
||||
form
|
||||
.addClass('dialogbox')
|
||||
.append('<button class="close">×</button>')
|
||||
.append('<button class="close" title="' + SN.msg('popup_close_button') + '">×</button>')
|
||||
.closest('.notice-options')
|
||||
.addClass('opaque');
|
||||
|
||||
|
@ -460,6 +460,7 @@ class Action extends HTMLOutputter // lawsuit
|
||||
|
||||
// TRANS: Localized tooltip for '...' expansion button on overlong remote messages.
|
||||
$messages['showmore_tooltip'] = _m('TOOLTIP', 'Show more');
|
||||
$messages['popup_close_button'] = _m('TOOLTIP', 'Close popup');
|
||||
|
||||
$messages = array_merge($messages, $this->getScriptMessages());
|
||||
|
||||
@ -1489,7 +1490,7 @@ class Action extends HTMLOutputter // lawsuit
|
||||
}
|
||||
$this->initDocument('json');
|
||||
$error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
|
||||
$this->text(json_encode($error_array));
|
||||
print(json_encode($error_array));
|
||||
$this->endDocument('json');
|
||||
break;
|
||||
case 'text':
|
||||
|
@ -579,8 +579,8 @@ class Activity
|
||||
|
||||
if (!empty($this->link)) {
|
||||
$xs->element('link', array('rel' => 'alternate',
|
||||
'type' => 'text/html'),
|
||||
$this->link);
|
||||
'type' => 'text/html',
|
||||
'href' => $this->link));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -233,6 +233,9 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
protected function notifyMentioned(Notice $stored, array &$mentioned_ids)
|
||||
{
|
||||
// pass through silently by default
|
||||
|
||||
// If we want to stop any other plugin from notifying based on this activity, return false instead.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -281,10 +284,14 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
function onNoticeDeleteRelated(Notice $notice)
|
||||
public function onNoticeDeleteRelated(Notice $notice)
|
||||
{
|
||||
if ($this->isMyNotice($notice)) {
|
||||
$this->deleteRelated($notice);
|
||||
try {
|
||||
$this->deleteRelated($notice);
|
||||
} catch (AlreadyFulfilledException $e) {
|
||||
// Nothing to see here, it's obviously already gone...
|
||||
}
|
||||
}
|
||||
|
||||
// Always continue this event in our activity handling plugins.
|
||||
@ -301,10 +308,7 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->notifyMentioned($stored, $mentioned_ids);
|
||||
|
||||
// If it was _our_ notice, only we should do anything with the mentions.
|
||||
return false;
|
||||
return $this->notifyMentioned($stored, $mentioned_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -436,14 +440,13 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
/**
|
||||
* Handle object posted via AtomPub
|
||||
*
|
||||
* @param Activity &$activity Activity that was posted
|
||||
* @param Activity $activity Activity that was posted
|
||||
* @param Profile $scoped Profile of user posting
|
||||
* @param Notice &$notice Resulting notice
|
||||
*
|
||||
* @return boolean hook value
|
||||
*/
|
||||
// FIXME: Make sure we can really do strong Notice typing with a $notice===null without having =null here
|
||||
public function onStartAtomPubNewActivity(Activity &$activity, Profile $scoped, Notice &$notice)
|
||||
public function onStartAtomPubNewActivity(Activity $activity, Profile $scoped, Notice &$notice=null)
|
||||
{
|
||||
if (!$this->isMyActivity($activity)) {
|
||||
return true;
|
||||
@ -453,8 +456,6 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
|
||||
$notice = $this->saveNoticeFromActivity($activity, $scoped, $options);
|
||||
|
||||
Event::handle('EndAtomPubNewActivity', array($activity, $scoped, $notice));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -584,7 +585,8 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
try {
|
||||
$this->showNoticeListItem($nli);
|
||||
} catch (Exception $e) {
|
||||
$nli->out->element('p', 'error', 'Error showing notice: '.htmlspecialchars($e->getMessage()));
|
||||
common_log(LOG_ERR, 'Error showing notice: ' . $e->getMessage());
|
||||
$nli->out->element('p', 'error', sprintf(_('Error showing notice: %s'), $e->getMessage()));
|
||||
}
|
||||
|
||||
Event::handle('EndShowNoticeItem', array($nli));
|
||||
@ -593,17 +595,9 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
|
||||
protected function showNoticeListItem(NoticeListItem $nli)
|
||||
{
|
||||
$nli->showNotice();
|
||||
$nli->showNoticeAttachments();
|
||||
$nli->showNoticeInfo();
|
||||
$nli->showNoticeOptions();
|
||||
|
||||
$nli->showNoticeLink();
|
||||
$nli->showNoticeSource();
|
||||
$nli->showNoticeLocation();
|
||||
$nli->showPermalink();
|
||||
|
||||
$nli->showNoticeOptions();
|
||||
$nli->showNoticeHeaders();
|
||||
$nli->showContent();
|
||||
$nli->showNoticeFooter();
|
||||
}
|
||||
|
||||
public function onStartShowNoticeItemNotice(NoticeListItem $nli)
|
||||
@ -632,7 +626,11 @@ abstract class ActivityHandlerPlugin extends Plugin
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->showNoticeContent($stored, $out, $scoped);
|
||||
try {
|
||||
$this->showNoticeContent($stored, $out, $scoped);
|
||||
} catch (Exception $e) {
|
||||
$out->element('div', 'error', $e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ class ActivityMover extends QueueHandler
|
||||
$sink->postActivity($act);
|
||||
$notice = Notice::getKV('uri', $act->objects[0]->id);
|
||||
if (!empty($notice)) {
|
||||
$notice->delete();
|
||||
$notice->deleteAs($user->getProfile(), false);
|
||||
}
|
||||
break;
|
||||
case ActivityVerb::JOIN:
|
||||
|
@ -298,7 +298,7 @@ class ActivityObject
|
||||
if (!empty($guidEl)) {
|
||||
$this->id = $guidEl->textContent;
|
||||
|
||||
if ($guidEl->hasAttribute('isPermaLink')) {
|
||||
if ($guidEl->hasAttribute('isPermaLink') && $guidEl->getAttribute('isPermaLink') != 'false') {
|
||||
// overwrites <link>
|
||||
$this->link = $this->id;
|
||||
}
|
||||
|
@ -274,11 +274,11 @@ class ApiAction extends Action
|
||||
$sub = Subscription::getSubscription($this->scoped, $profile);
|
||||
// Notifications on?
|
||||
$twitter_user['following'] = true;
|
||||
$twitter_user['statusnet_blocking'] = $this->scoped->hasBlocked($profile);
|
||||
$twitter_user['notifications'] = ($sub->jabber || $sub->sms);
|
||||
} catch (NoResultException $e) {
|
||||
// well, the values are already false...
|
||||
}
|
||||
$twitter_user['statusnet_blocking'] = $this->scoped->hasBlocked($profile);
|
||||
}
|
||||
|
||||
if ($get_notice) {
|
||||
@ -330,6 +330,9 @@ class ApiAction extends Action
|
||||
$in_reply_to = $parent->id;
|
||||
} catch (NoParentNoticeException $e) {
|
||||
$in_reply_to = null;
|
||||
} catch (NoResultException $e) {
|
||||
// the in_reply_to message has probably been deleted
|
||||
$in_reply_to = null;
|
||||
}
|
||||
$twitter_status['in_reply_to_status_id'] = $in_reply_to;
|
||||
|
||||
@ -648,6 +651,11 @@ class ApiAction extends Action
|
||||
break;
|
||||
default:
|
||||
if (strncmp($element, 'statusnet_', 10) == 0) {
|
||||
if ($element === 'statusnet_in_groups' && is_array($value)) {
|
||||
// QVITTERFIX because it would cause an array to be sent as $value
|
||||
// THIS IS UNDOCUMENTED AND SHOULD NEVER BE RELIED UPON (qvitter uses json output)
|
||||
$value = json_encode($value);
|
||||
}
|
||||
$this->element('statusnet:'.substr($element, 10), null, $value);
|
||||
} else {
|
||||
$this->element($element, null, $value);
|
||||
|
@ -295,7 +295,7 @@ class ApiAuthAction extends ApiAction
|
||||
// TRANS: Client error thrown when authentication fails because a user clicked "Cancel".
|
||||
$this->clientError(_('Could not authenticate you.'), 401);
|
||||
|
||||
} elseif ($required) {
|
||||
} else {
|
||||
// $this->auth_user_nickname - i.e. PHP_AUTH_USER - will have a value since it was not empty
|
||||
|
||||
$user = common_check_user($this->auth_user_nickname,
|
||||
@ -314,10 +314,10 @@ class ApiAuthAction extends ApiAction
|
||||
$this->auth_user = null;
|
||||
}
|
||||
|
||||
// By default, basic auth users have rw access
|
||||
$this->access = self::READ_WRITE;
|
||||
|
||||
if (!$this->auth_user instanceof User) {
|
||||
if ($required && $this->auth_user instanceof User) {
|
||||
// By default, basic auth users have rw access
|
||||
$this->access = self::READ_WRITE;
|
||||
} elseif ($required) {
|
||||
$msg = sprintf(
|
||||
"basic auth nickname = %s",
|
||||
$this->auth_user_nickname
|
||||
@ -328,10 +328,10 @@ class ApiAuthAction extends ApiAction
|
||||
header('WWW-Authenticate: Basic realm="' . $realm . '"');
|
||||
// TRANS: Client error thrown when authentication fails.
|
||||
$this->clientError(_('Could not authenticate you.'), 401);
|
||||
} else {
|
||||
// all get rw access for actions that don't require auth
|
||||
$this->access = self::READ_WRITE;
|
||||
}
|
||||
} else {
|
||||
// all get rw access for actions that don't require auth
|
||||
$this->access = self::READ_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ class AttachmentList extends Widget
|
||||
*
|
||||
* @param Notice $notice stream of notices from DB_DataObject
|
||||
*/
|
||||
function __construct($notice, $out=null)
|
||||
function __construct(Notice $notice, $out=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
$this->notice = $notice;
|
||||
@ -75,7 +75,6 @@ class AttachmentList extends Widget
|
||||
function show()
|
||||
{
|
||||
$attachments = $this->notice->attachments();
|
||||
$representable = false;
|
||||
foreach ($attachments as $key=>$att) {
|
||||
// Only show attachments representable with a title
|
||||
if ($att->getTitle() === null) {
|
||||
|
@ -120,7 +120,7 @@ class AttachmentListItem extends Widget
|
||||
try {
|
||||
// Tell getThumbnail that we can show an animated image if it has one (4th arg, "force_still")
|
||||
$thumb = $this->attachment->getThumbnail(null, null, false, false);
|
||||
$this->out->element('img', array('class'=>'u-photo', 'src' => $thumb->getUrl(), 'alt' => ''));
|
||||
$this->out->element('img', $thumb->getHtmlAttrs(['class'=>'u-photo', 'alt' => '']));
|
||||
} catch (UseFileAsThumbnailException $e) {
|
||||
$this->out->element('img', array('class'=>'u-photo', 'src' => $e->file->getUrl(), 'alt' => $e->file->title));
|
||||
} catch (UnsupportedMediaException $e) {
|
||||
|
@ -140,11 +140,6 @@ $default =
|
||||
'path' => $_path . '/avatar/',
|
||||
'ssl' => null,
|
||||
'maxsize' => 300),
|
||||
'background' =>
|
||||
array('server' => null,
|
||||
'dir' => INSTALLDIR . '/background/',
|
||||
'path' => $_path . '/background/',
|
||||
'ssl' => null),
|
||||
'public' =>
|
||||
array('localonly' => false,
|
||||
'blacklist' => array(),
|
||||
@ -276,10 +271,6 @@ $default =
|
||||
'maxpeople' => 500, // maximum no. of people with the same tag by the same user
|
||||
'allow_tagging' => array('all' => true), // equivalent to array('local' => true, 'remote' => true)
|
||||
'desclimit' => null),
|
||||
'oembed' =>
|
||||
array('endpoint' => null, // 'https://noembed.com/embed/' for proxied oEmbed data
|
||||
'order' => array('built-in', 'well-known', 'service', 'discovery'),
|
||||
),
|
||||
'search' =>
|
||||
array('type' => 'like'),
|
||||
'sessions' =>
|
||||
@ -305,12 +296,13 @@ $default =
|
||||
'plugins' =>
|
||||
array('core' => array(
|
||||
'ActivityVerb' => array(),
|
||||
'ActivityVerbPost' => array(),
|
||||
'ActivityModeration' => array(),
|
||||
'AuthCrypt' => array(),
|
||||
'Cronish' => array(),
|
||||
'Favorite' => array(),
|
||||
'Share' => array(),
|
||||
'LRDD' => array(),
|
||||
'StrictTransportSecurity' => array(),
|
||||
),
|
||||
'default' => array(
|
||||
'Activity' => array(),
|
||||
|
@ -68,11 +68,7 @@ class DoFollowListItem extends NoticeListItem
|
||||
// FIXME: URL, image, video, audio
|
||||
$this->out->elementStart('article', array('class' => 'e-content'));
|
||||
|
||||
if (!empty($this->notice->rendered)) {
|
||||
$html = $this->notice->rendered;
|
||||
} else {
|
||||
$html = common_render_content($this->notice->content, $this->notice);
|
||||
}
|
||||
$html = $this->notice->getRendered();
|
||||
|
||||
if (common_config('nofollow', 'external') == 'sometimes') {
|
||||
// remove the nofollow part
|
||||
|
@ -190,9 +190,9 @@ class Form extends Widget
|
||||
return 'form';
|
||||
}
|
||||
|
||||
function li()
|
||||
function li($class=null)
|
||||
{
|
||||
$this->out->elementStart('li');
|
||||
$this->out->elementStart('li', $class);
|
||||
}
|
||||
|
||||
function unli()
|
||||
|
@ -23,7 +23,7 @@ define('GNUSOCIAL_ENGINE', 'GNU social');
|
||||
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
|
||||
|
||||
define('GNUSOCIAL_BASE_VERSION', '1.2.0');
|
||||
define('GNUSOCIAL_LIFECYCLE', 'alpha2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
|
||||
define('GNUSOCIAL_LIFECYCLE', 'beta2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
|
||||
|
||||
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
|
||||
|
||||
@ -38,6 +38,7 @@ define('PROFILES_PER_PAGE', 20);
|
||||
define('MESSAGES_PER_PAGE', 20);
|
||||
define('GROUPS_PER_PAGE', 20);
|
||||
define('APPS_PER_PAGE', 20);
|
||||
define('PEOPLETAGS_PER_PAGE', 20);
|
||||
|
||||
define('GROUPS_PER_MINILIST', 8);
|
||||
define('PROFILES_PER_MINILIST', 8);
|
||||
|
@ -383,15 +383,11 @@ class GNUsocial
|
||||
if (isset($conffile)) {
|
||||
$config_files = array($conffile);
|
||||
} else {
|
||||
$config_files = array('/etc/statusnet/statusnet.php',
|
||||
'/etc/statusnet/laconica.php',
|
||||
'/etc/laconica/laconica.php',
|
||||
'/etc/statusnet/'.$_server.'.php',
|
||||
'/etc/laconica/'.$_server.'.php');
|
||||
$config_files = array('/etc/gnusocial/config.php',
|
||||
'/etc/gnusocial/config.d/'.$_server.'.php');
|
||||
|
||||
if (strlen($_path) > 0) {
|
||||
$config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php';
|
||||
$config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php';
|
||||
$config_files[] = '/etc/gnusocial/config.d/'.$_server.'_'.$_path.'.php';
|
||||
}
|
||||
|
||||
$config_files[] = INSTALLDIR.'/config.php';
|
||||
|
@ -72,9 +72,9 @@ class GroupsNav extends MoreMenu
|
||||
|
||||
while ($this->groups instanceof User_group && $this->groups->fetch()) {
|
||||
$items[] = array('placeholder',
|
||||
array('nickname' => $this->groups->nickname,
|
||||
array('nickname' => $this->groups->getNickname(),
|
||||
'mainpage' => $this->groups->homeUrl()),
|
||||
$this->groups->getBestName(),
|
||||
$this->groups->getNickname(),
|
||||
$this->groups->getBestName()
|
||||
);
|
||||
}
|
||||
|
@ -177,8 +177,17 @@ class HTTPClient extends HTTP_Request2
|
||||
/**
|
||||
* Quick static function to GET a URL
|
||||
*/
|
||||
public static function quickGet($url, $accept=null)
|
||||
public static function quickGet($url, $accept=null, $params=array())
|
||||
{
|
||||
if (!empty($params)) {
|
||||
$params = http_build_query($params, null, '&');
|
||||
if (strpos($url, '?') === false) {
|
||||
$url .= '?' . $params;
|
||||
} else {
|
||||
$url .= '&' . $params;
|
||||
}
|
||||
}
|
||||
|
||||
$client = new HTTPClient();
|
||||
if (!is_null($accept)) {
|
||||
$client->setHeader('Accept', $accept);
|
||||
@ -191,6 +200,16 @@ class HTTPClient extends HTTP_Request2
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
public static function quickGetJson($url, $params=array())
|
||||
{
|
||||
$data = json_decode(self::quickGet($url, null, $params));
|
||||
if (is_null($data)) {
|
||||
common_debug('Could not decode JSON data from URL: '.$url);
|
||||
throw new ServerException('Could not decode JSON data from URL');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to run a GET request.
|
||||
*
|
||||
|
@ -125,7 +125,7 @@ class ImageFile
|
||||
$imgPath = null;
|
||||
$media = common_get_mime_media($file->mimetype);
|
||||
if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
|
||||
if (empty($file->filename)) {
|
||||
if (empty($file->filename) && !file_exists($imgPath)) {
|
||||
throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.'));
|
||||
}
|
||||
|
||||
|
@ -364,13 +364,16 @@ abstract class ImPlugin extends Plugin
|
||||
protected function formatNotice(Notice $notice)
|
||||
{
|
||||
$profile = $notice->getProfile();
|
||||
$nicknames = $profile->getNickname();
|
||||
|
||||
try {
|
||||
$parent = $notice->getParent();
|
||||
$orig_profile = $parent->getProfile();
|
||||
$nicknames = sprintf('%1$s => %2$s', $profile->nickname, $orig_profile->nickname);
|
||||
$nicknames = sprintf('%1$s => %2$s', $profile->getNickname(), $orig_profile->getNickname());
|
||||
} catch (NoParentNoticeException $e) {
|
||||
$nicknames = $profile->nickname;
|
||||
// Not a reply, no parent notice stored
|
||||
} catch (NoResultException $e) {
|
||||
// Parent notice was probably deleted
|
||||
}
|
||||
|
||||
return sprintf('%1$s: %2$s [%3$u]', $nicknames, $notice->content, $notice->id);
|
||||
|
@ -38,8 +38,7 @@ class ImQueueHandler extends QueueHandler
|
||||
function handle($notice)
|
||||
{
|
||||
$this->plugin->broadcastNotice($notice);
|
||||
if ($notice->is_local == Notice::LOCAL_PUBLIC ||
|
||||
$notice->is_local == Notice::LOCAL_NONPUBLIC) {
|
||||
if ($notice->isLocal()) {
|
||||
$this->plugin->publicNotice($notice);
|
||||
}
|
||||
return true;
|
||||
|
@ -27,9 +27,7 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
class InlineAttachmentList extends AttachmentList
|
||||
{
|
||||
@ -51,35 +49,3 @@ class InlineAttachmentList extends AttachmentList
|
||||
return new InlineAttachmentListItem($attachment, $this->out);
|
||||
}
|
||||
}
|
||||
|
||||
class InlineAttachmentListItem extends AttachmentListItem
|
||||
{
|
||||
function showLink() {
|
||||
$this->out->element('a', $this->linkAttr(), $this->title());
|
||||
$this->showRepresentation();
|
||||
}
|
||||
|
||||
/**
|
||||
* start a single notice.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function showStart()
|
||||
{
|
||||
// XXX: RDFa
|
||||
// TODO: add notice_type class e.g., notice_video, notice_image
|
||||
$this->out->elementStart('li', array('class' => 'inline-attachment'));
|
||||
}
|
||||
|
||||
/**
|
||||
* finish the notice
|
||||
*
|
||||
* Close the last elements in the notice list item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function showEnd()
|
||||
{
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
}
|
||||
|
62
lib/inlineattachmentlistitem.php
Normal file
62
lib/inlineattachmentlistitem.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* widget for displaying notice attachments thumbnails
|
||||
*
|
||||
* 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 UI
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 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('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
class InlineAttachmentListItem extends AttachmentListItem
|
||||
{
|
||||
function showLink() {
|
||||
$this->out->element('a', $this->linkAttr(), $this->title());
|
||||
$this->showRepresentation();
|
||||
}
|
||||
|
||||
/**
|
||||
* start a single notice.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function showStart()
|
||||
{
|
||||
// XXX: RDFa
|
||||
// TODO: add notice_type class e.g., notice_video, notice_image
|
||||
$this->out->elementStart('li', array('class' => 'inline-attachment'));
|
||||
}
|
||||
|
||||
/**
|
||||
* finish the notice
|
||||
*
|
||||
* Close the last elements in the notice list item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function showEnd()
|
||||
{
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
}
|
@ -96,8 +96,8 @@ abstract class Installer
|
||||
}
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.3.2', '<')) {
|
||||
$this->warning('Require PHP version 5.3.2 or greater.');
|
||||
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
|
||||
$this->warning('Require PHP version 5.5.0 or greater.');
|
||||
$pass = false;
|
||||
}
|
||||
|
||||
@ -133,10 +133,16 @@ abstract class Installer
|
||||
}
|
||||
|
||||
// Check the subdirs used for file uploads
|
||||
$fileSubdirs = array('avatar', 'background', 'file');
|
||||
$fileSubdirs = array('avatar', 'file');
|
||||
foreach ($fileSubdirs as $fileSubdir) {
|
||||
$fileFullPath = INSTALLDIR."/$fileSubdir/";
|
||||
if (!is_writable($fileFullPath)) {
|
||||
$fileFullPath = INSTALLDIR."/$fileSubdir";
|
||||
if (!file_exists($fileFullPath)) {
|
||||
$pass = $pass && mkdir($fileFullPath);
|
||||
} elseif (!is_dir($fileFullPath)) {
|
||||
$this->warning(sprintf('GNU social expected a directory but found something else on this path: %s', $fileFullPath),
|
||||
'Either make sure it goes to a directory or remove it and a directory will be created.');
|
||||
$pass = false;
|
||||
} elseif (!is_writable($fileFullPath)) {
|
||||
$this->warning(sprintf('Cannot write to %s directory: <code>%s</code>', $fileSubdir, $fileFullPath),
|
||||
sprintf('On your server, try this command: <code>chmod a+w %s</code>', $fileFullPath));
|
||||
$pass = false;
|
||||
|
@ -336,7 +336,8 @@ function get_all_languages() {
|
||||
'en-gb' => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
|
||||
'eo' => array('q' => 0.8, 'lang' => 'eo', 'name' => 'Esperanto', 'direction' => 'ltr'),
|
||||
'fi' => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'),
|
||||
'fr-fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'),
|
||||
'fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'),
|
||||
'fr-fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French (France)', 'direction' => 'ltr'),
|
||||
'fur' => array('q' => 0.8, 'lang' => 'fur', 'name' => 'Friulian', 'direction' => 'ltr'),
|
||||
'gl' => array('q' => 0.8, 'lang' => 'gl', 'name' => 'Galician', 'direction' => 'ltr'),
|
||||
'ka' => array('q' => 0.8, 'lang' => 'ka', 'name' => 'Georgian', 'direction' => 'ltr'),
|
||||
|
@ -66,7 +66,7 @@ class ListsNav extends MoreMenu
|
||||
while ($this->lists->fetch()) {
|
||||
$mode = $this->lists->private ? 'private' : 'public';
|
||||
$items[] = array('showprofiletag',
|
||||
array('tagger' => $this->profile->nickname,
|
||||
array('nickname' => $this->profile->getNickname(),
|
||||
'tag' => $this->lists->tag),
|
||||
$this->lists->tag,
|
||||
'');
|
||||
|
@ -56,6 +56,23 @@ class Location
|
||||
|
||||
var $names = array();
|
||||
|
||||
/**
|
||||
* Constructor that makes a Location from Notice::locationOptions(...)
|
||||
*
|
||||
* @param array $options an array for example provided by Notice::locationOptions(...)
|
||||
*
|
||||
* @return Location Location with the given options (lat, lon, id, name)
|
||||
*/
|
||||
static function fromOptions(array $options) {
|
||||
$location = new Location();
|
||||
foreach (['lat', 'lon', 'location_id', 'location_ns'] as $opt) {
|
||||
if (isset($options[$opt])) {
|
||||
$location->$opt = $options[$opt];
|
||||
}
|
||||
}
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that makes a Location from a string name
|
||||
*
|
||||
|
11
lib/mail.php
11
lib/mail.php
@ -30,9 +30,7 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
require_once 'Mail.php';
|
||||
|
||||
@ -54,7 +52,7 @@ function mail_backend()
|
||||
$backend = $mail->factory(common_config('mail', 'backend'),
|
||||
common_config('mail', 'params') ?: array());
|
||||
if ($_PEAR->isError($backend)) {
|
||||
common_server_error($backend->getMessage(), 500);
|
||||
throw new ServerException($backend->getMessage());
|
||||
}
|
||||
}
|
||||
return $backend;
|
||||
@ -71,6 +69,8 @@ function mail_backend()
|
||||
*/
|
||||
function mail_send($recipients, $headers, $body)
|
||||
{
|
||||
global $_PEAR;
|
||||
|
||||
try {
|
||||
// XXX: use Mail_Queue... maybe
|
||||
$backend = mail_backend();
|
||||
@ -81,6 +81,9 @@ function mail_send($recipients, $headers, $body)
|
||||
|
||||
assert($backend); // throws an error if it's bad
|
||||
$sent = $backend->send($recipients, $headers, $body);
|
||||
if ($_PEAR->isError($sent)) {
|
||||
throw new ServerException($sent->getMessage());
|
||||
}
|
||||
return true;
|
||||
} catch (PEAR_Exception $e) {
|
||||
common_log(
|
||||
|
@ -74,6 +74,11 @@ class MediaFile
|
||||
return $this->short_fileurl;
|
||||
}
|
||||
|
||||
function getEnclosure()
|
||||
{
|
||||
return $this->getFile()->getEnclosure();
|
||||
}
|
||||
|
||||
function delete()
|
||||
{
|
||||
$filepath = File::path($this->filename);
|
||||
|
@ -64,6 +64,7 @@ class NoticeListItem extends Widget
|
||||
protected $options = true;
|
||||
protected $maxchars = 0; // if <= 0 it means use full posts
|
||||
protected $item_tag = 'li';
|
||||
protected $pa = null;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
@ -150,7 +151,19 @@ class NoticeListItem extends Widget
|
||||
$this->elementStart('section', array('class'=>'notice-headers'));
|
||||
$this->showNoticeTitle();
|
||||
$this->showAuthor();
|
||||
if ($this->addressees) { $this->showAddressees(); }
|
||||
|
||||
if (!empty($this->notice->reply_to) || count($this->getProfileAddressees()) > 0) {
|
||||
$this->elementStart('div', array('class' => 'parents'));
|
||||
try {
|
||||
$this->showParent();
|
||||
} catch (NoParentNoticeException $e) {
|
||||
// no parent notice
|
||||
} catch (InvalidUrlException $e) {
|
||||
// parent had an invalid URL so we can't show it
|
||||
}
|
||||
if ($this->addressees) { $this->showAddressees(); }
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
$this->elementEnd('section');
|
||||
}
|
||||
|
||||
@ -166,7 +179,7 @@ class NoticeListItem extends Widget
|
||||
function showNoticeTitle()
|
||||
{
|
||||
if (Event::handle('StartShowNoticeTitle', array($this))) {
|
||||
$this->element('a', array('href' => $this->notice->getUrl(),
|
||||
$this->element('a', array('href' => $this->notice->getUrl(true),
|
||||
'class' => 'notice-title'),
|
||||
$this->notice->getTitle());
|
||||
Event::handle('EndShowNoticeTitle', array($this));
|
||||
@ -235,8 +248,9 @@ class NoticeListItem extends Widget
|
||||
function showAuthor()
|
||||
{
|
||||
$attrs = array('href' => $this->profile->profileurl,
|
||||
'class' => 'h-card p-author',
|
||||
'class' => 'h-card',
|
||||
'title' => $this->profile->getNickname());
|
||||
if(empty($this->repeat)) { $attrs['class'] .= ' p-author'; }
|
||||
|
||||
if (Event::handle('StartShowNoticeItemAuthor', array($this->profile, $this->out, &$attrs))) {
|
||||
$this->out->elementStart('a', $attrs);
|
||||
@ -247,6 +261,19 @@ class NoticeListItem extends Widget
|
||||
}
|
||||
}
|
||||
|
||||
function showParent()
|
||||
{
|
||||
$this->out->element(
|
||||
'a',
|
||||
array(
|
||||
'href' => $this->notice->getParent()->getUrl(),
|
||||
'class' => 'u-in-reply-to',
|
||||
'rel' => 'in-reply-to'
|
||||
),
|
||||
'in reply to'
|
||||
);
|
||||
}
|
||||
|
||||
function showAddressees()
|
||||
{
|
||||
$pa = $this->getProfileAddressees();
|
||||
@ -267,19 +294,20 @@ class NoticeListItem extends Widget
|
||||
|
||||
function getProfileAddressees()
|
||||
{
|
||||
$pa = array();
|
||||
if($this->pa) { return $this->pa; }
|
||||
$this->pa = array();
|
||||
|
||||
$attentions = $this->getReplyProfiles();
|
||||
|
||||
foreach ($attentions as $attn) {
|
||||
$class = $attn->isGroup() ? 'group' : 'account';
|
||||
$pa[] = array('href' => $attn->profileurl,
|
||||
'title' => $attn->getNickname(),
|
||||
'class' => "addressee {$class}",
|
||||
'text' => $attn->getStreamName());
|
||||
$this->pa[] = array('href' => $attn->profileurl,
|
||||
'title' => $attn->getNickname(),
|
||||
'class' => "addressee {$class}",
|
||||
'text' => $attn->getStreamName());
|
||||
}
|
||||
|
||||
return $pa;
|
||||
return $this->pa;
|
||||
}
|
||||
|
||||
function getReplyProfiles()
|
||||
@ -317,13 +345,8 @@ class NoticeListItem extends Widget
|
||||
if (Event::handle('StartShowNoticeContent', array($this->notice, $this->out, $this->out->getScoped()))) {
|
||||
if ($this->maxchars > 0 && mb_strlen($this->notice->content) > $this->maxchars) {
|
||||
$this->out->text(mb_substr($this->notice->content, 0, $this->maxchars) . '[…]');
|
||||
} elseif ($this->notice->rendered) {
|
||||
$this->out->raw($this->notice->rendered);
|
||||
} else {
|
||||
// XXX: may be some uncooked notices in the DB,
|
||||
// we cook them right now. This should probably disappear in future
|
||||
// versions (>> 0.4.x)
|
||||
$this->out->raw(common_render_content($this->notice->content, $this->notice));
|
||||
$this->out->raw($this->notice->getRendered());
|
||||
}
|
||||
Event::handle('EndShowNoticeContent', array($this->notice, $this->out, $this->out->getScoped()));
|
||||
}
|
||||
@ -351,7 +374,6 @@ class NoticeListItem extends Widget
|
||||
'href' => Conversation::getUrlFromNotice($this->notice)));
|
||||
$this->out->element('time', array('class' => 'dt-published',
|
||||
'datetime' => common_date_iso8601($this->notice->created),
|
||||
// TRANS: Timestamp title (tooltip text) for NoticeListItem
|
||||
'title' => common_exact_date($this->notice->created)),
|
||||
common_date_string($this->notice->created));
|
||||
$this->out->elementEnd('a');
|
||||
@ -516,9 +538,22 @@ class NoticeListItem extends Widget
|
||||
if (!$this->notice->isLocal()) {
|
||||
$class .= ' external';
|
||||
}
|
||||
|
||||
try {
|
||||
if($this->repeat) {
|
||||
$this->out->element('a',
|
||||
array('href' => $this->repeat->getUrl(),
|
||||
'class' => 'u-url'),
|
||||
'');
|
||||
$class = str_replace('u-url', 'u-repost-of', $class);
|
||||
}
|
||||
} catch (InvalidUrlException $e) {
|
||||
// no permalink available
|
||||
}
|
||||
|
||||
try {
|
||||
$this->out->element('a',
|
||||
array('href' => $this->notice->getUrl(),
|
||||
array('href' => $this->notice->getUrl(true),
|
||||
'class' => $class),
|
||||
// TRANS: Addition in notice list item for single-notice view.
|
||||
_('permalink'));
|
||||
|
@ -9,6 +9,9 @@ abstract class NoticestreamAction extends ProfileAction
|
||||
protected function prepare(array $args=array()) {
|
||||
parent::prepare($args);
|
||||
|
||||
// In case we need more info than ProfileAction->doPreparation() gives us
|
||||
$this->doStreamPreparation();
|
||||
|
||||
// fetch the actual stream stuff
|
||||
$stream = $this->getStream();
|
||||
$this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
|
||||
@ -21,6 +24,11 @@ abstract class NoticestreamAction extends ProfileAction
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function doStreamPreparation()
|
||||
{
|
||||
// pass by default
|
||||
}
|
||||
|
||||
// this fetches the NoticeStream
|
||||
abstract public function getStream();
|
||||
}
|
||||
|
20
lib/peopletag.php
Normal file
20
lib/peopletag.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
class Peopletag extends PeopletagListItem
|
||||
{
|
||||
protected $avatarSize = AVATAR_PROFILE_SIZE;
|
||||
|
||||
function showStart()
|
||||
{
|
||||
$mode = $this->peopletag->private ? 'private' : 'public';
|
||||
$this->out->elementStart('div', array('class' => 'h-entry peopletag peopletag-profile mode-'.$mode,
|
||||
'id' => 'peopletag-' . $this->peopletag->id));
|
||||
}
|
||||
|
||||
function showEnd()
|
||||
{
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
}
|
@ -76,7 +76,7 @@ class PeopletagGroupNav extends Widget
|
||||
{
|
||||
$user = null;
|
||||
|
||||
// FIXME: we should probably pass this in
|
||||
// FIXME: we should probably pass this in and check when PeopletagGroupNav is actually loaded etc.
|
||||
|
||||
$action = $this->action->trimmed('action');
|
||||
|
||||
@ -107,7 +107,7 @@ class PeopletagGroupNav extends Widget
|
||||
|
||||
if (Event::handle('StartPeopletagGroupNav', array($this))) {
|
||||
// People tag timeline
|
||||
$this->out->menuItem(common_local_url('showprofiletag', array('tagger' => $user_profile->nickname,
|
||||
$this->out->menuItem(common_local_url('showprofiletag', array('nickname' => $user_profile->nickname,
|
||||
'tag' => $tag->tag)),
|
||||
// TRANS: Menu item in list navigation panel.
|
||||
_m('MENU','List'),
|
||||
|
@ -27,13 +27,7 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once INSTALLDIR.'/lib/widget.php';
|
||||
|
||||
define('PEOPLETAGS_PER_PAGE', 20);
|
||||
if (!defined('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
/**
|
||||
* Widget to show a list of peopletags
|
||||
@ -88,201 +82,3 @@ class PeopletagList extends Widget
|
||||
$ptag->show();
|
||||
}
|
||||
}
|
||||
|
||||
class PeopletagListItem extends Widget
|
||||
{
|
||||
var $peopletag = null;
|
||||
var $current = null;
|
||||
var $profile = null;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* Also initializes the owner attribute.
|
||||
*
|
||||
* @param Notice $notice The notice we'll display
|
||||
*/
|
||||
function __construct($peopletag, $current, $out=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
$this->peopletag = $peopletag;
|
||||
$this->current = $current;
|
||||
$this->profile = Profile::getKV('id', $this->peopletag->tagger);
|
||||
}
|
||||
|
||||
/**
|
||||
* recipe function for displaying a single peopletag.
|
||||
*
|
||||
* This uses all the other methods to correctly display a notice. Override
|
||||
* it or one of the others to fine-tune the output.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function url()
|
||||
{
|
||||
return $this->peopletag->homeUrl();
|
||||
}
|
||||
|
||||
function show()
|
||||
{
|
||||
if (empty($this->peopletag)) {
|
||||
common_log(LOG_WARNING, "Trying to show missing peopletag; skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Event::handle('StartShowPeopletagItem', array($this))) {
|
||||
$this->showStart();
|
||||
$this->showPeopletag();
|
||||
$this->showStats();
|
||||
$this->showEnd();
|
||||
Event::handle('EndShowPeopletagItem', array($this));
|
||||
}
|
||||
}
|
||||
|
||||
function showStart()
|
||||
{
|
||||
$mode = ($this->peopletag->private) ? 'private' : 'public';
|
||||
$this->out->elementStart('li', array('class' => 'h-entry peopletag mode-' . $mode,
|
||||
'id' => 'peopletag-' . $this->peopletag->id));
|
||||
}
|
||||
|
||||
function showEnd()
|
||||
{
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
|
||||
function showPeopletag()
|
||||
{
|
||||
$this->showCreator();
|
||||
$this->showTag();
|
||||
$this->showPrivacy();
|
||||
$this->showUpdated();
|
||||
$this->showActions();
|
||||
$this->showDescription();
|
||||
}
|
||||
|
||||
function showStats()
|
||||
{
|
||||
$this->out->elementStart('div', 'entry-summary entity_statistics');
|
||||
$this->out->elementStart('span', 'tagged-count');
|
||||
$this->out->element('a',
|
||||
array('href' => common_local_url('peopletagged',
|
||||
array('tagger' => $this->profile->nickname,
|
||||
'tag' => $this->peopletag->tag))),
|
||||
// TRANS: Link description for link to list of users tagged with a tag (so part of a list).
|
||||
_('Listed'));
|
||||
$this->out->raw($this->peopletag->taggedCount());
|
||||
$this->out->elementEnd('span');
|
||||
|
||||
$this->out->elementStart('span', 'subscriber-count');
|
||||
$this->out->element('a',
|
||||
array('href' => common_local_url('peopletagsubscribers',
|
||||
array('tagger' => $this->profile->nickname,
|
||||
'tag' => $this->peopletag->tag))),
|
||||
// TRANS: Link description for link to list of users subscribed to a tag.
|
||||
_('Subscribers'));
|
||||
$this->out->raw($this->peopletag->subscriberCount());
|
||||
$this->out->elementEnd('span');
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
function showOwnerOptions()
|
||||
{
|
||||
$this->out->elementStart('li', 'entity_edit');
|
||||
$this->out->element('a', array('href' =>
|
||||
common_local_url('editpeopletag', array('tagger' => $this->profile->nickname,
|
||||
'tag' => $this->peopletag->tag)),
|
||||
// TRANS: Title for link to edit list settings.
|
||||
'title' => _('Edit list settings.')),
|
||||
// TRANS: Text for link to edit list settings.
|
||||
_('Edit'));
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
|
||||
function showSubscribeForm()
|
||||
{
|
||||
$this->out->elementStart('li');
|
||||
|
||||
if (Event::handle('StartSubscribePeopletagForm', array($this->out, $this->peopletag))) {
|
||||
if ($this->current) {
|
||||
if ($this->peopletag->hasSubscriber($this->current->id)) {
|
||||
$form = new UnsubscribePeopletagForm($this->out, $this->peopletag);
|
||||
$form->show();
|
||||
} else {
|
||||
$form = new SubscribePeopletagForm($this->out, $this->peopletag);
|
||||
$form->show();
|
||||
}
|
||||
}
|
||||
Event::handle('EndSubscribePeopletagForm', array($this->out, $this->peopletag));
|
||||
}
|
||||
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
|
||||
function showCreator()
|
||||
{
|
||||
$attrs = array();
|
||||
$attrs['href'] = $this->profile->profileurl;
|
||||
$attrs['class'] = 'h-card p-author nickname p-name';
|
||||
$attrs['rel'] = 'contact';
|
||||
$attrs['title'] = $this->profile->getFancyName();
|
||||
|
||||
$this->out->elementStart('a', $attrs);
|
||||
$this->showAvatar($this->profile);
|
||||
$this->out->text($this->profile->getNickname());
|
||||
$this->out->elementEnd('a');
|
||||
}
|
||||
|
||||
function showUpdated()
|
||||
{
|
||||
if (!empty($this->peopletag->modified)) {
|
||||
$this->out->element('abbr',
|
||||
array('title' => common_date_w3dtf($this->peopletag->modified),
|
||||
'class' => 'updated'),
|
||||
common_date_string($this->peopletag->modified));
|
||||
}
|
||||
}
|
||||
|
||||
function showPrivacy()
|
||||
{
|
||||
if ($this->peopletag->private) {
|
||||
$this->out->elementStart('a',
|
||||
array('href' => common_local_url('peopletagsbyuser',
|
||||
array('nickname' => $this->profile->nickname, 'private' => 1))));
|
||||
// TRANS: Privacy mode text in list list item for private list.
|
||||
$this->out->element('span', 'privacy_mode', _m('MODE','Private'));
|
||||
$this->out->elementEnd('a');
|
||||
}
|
||||
}
|
||||
|
||||
function showTag()
|
||||
{
|
||||
$this->out->elementStart('span', 'entry-title tag');
|
||||
$this->out->element('a',
|
||||
array('rel' => 'bookmark',
|
||||
'href' => $this->url()),
|
||||
htmlspecialchars($this->peopletag->tag));
|
||||
$this->out->elementEnd('span');
|
||||
}
|
||||
|
||||
function showActions()
|
||||
{
|
||||
$this->out->elementStart('div', 'entity_actions');
|
||||
$this->out->elementStart('ul');
|
||||
|
||||
if (!$this->peopletag->private) {
|
||||
$this->showSubscribeForm();
|
||||
}
|
||||
|
||||
if (!empty($this->current) && $this->profile->id == $this->current->id) {
|
||||
$this->showOwnerOptions();
|
||||
}
|
||||
$this->out->elementEnd('ul');
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
function showDescription()
|
||||
{
|
||||
$this->out->element('div', 'e-content description', $this->peopletag->description);
|
||||
}
|
||||
}
|
||||
|
228
lib/peopletaglistitem.php
Normal file
228
lib/peopletaglistitem.php
Normal file
@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Widget to show a list of peopletags
|
||||
*
|
||||
* 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 Public
|
||||
* @package StatusNet
|
||||
* @author Shashi Gowda <connect2shashi@gmail.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('GNUSOCIAL')) { exit(1); }
|
||||
|
||||
class PeopletagListItem extends Widget
|
||||
{
|
||||
var $peopletag = null;
|
||||
var $current = null;
|
||||
var $profile = null;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* Also initializes the owner attribute.
|
||||
*
|
||||
* @param Notice $notice The notice we'll display
|
||||
*/
|
||||
function __construct($peopletag, $current, $out=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
$this->peopletag = $peopletag;
|
||||
$this->current = $current;
|
||||
$this->profile = Profile::getKV('id', $this->peopletag->tagger);
|
||||
}
|
||||
|
||||
/**
|
||||
* recipe function for displaying a single peopletag.
|
||||
*
|
||||
* This uses all the other methods to correctly display a notice. Override
|
||||
* it or one of the others to fine-tune the output.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function url()
|
||||
{
|
||||
return $this->peopletag->homeUrl();
|
||||
}
|
||||
|
||||
function show()
|
||||
{
|
||||
if (empty($this->peopletag)) {
|
||||
common_log(LOG_WARNING, "Trying to show missing peopletag; skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Event::handle('StartShowPeopletagItem', array($this))) {
|
||||
$this->showStart();
|
||||
$this->showPeopletag();
|
||||
$this->showStats();
|
||||
$this->showEnd();
|
||||
Event::handle('EndShowPeopletagItem', array($this));
|
||||
}
|
||||
}
|
||||
|
||||
function showStart()
|
||||
{
|
||||
$mode = ($this->peopletag->private) ? 'private' : 'public';
|
||||
$this->out->elementStart('li', array('class' => 'h-entry peopletag mode-' . $mode,
|
||||
'id' => 'peopletag-' . $this->peopletag->id));
|
||||
}
|
||||
|
||||
function showEnd()
|
||||
{
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
|
||||
function showPeopletag()
|
||||
{
|
||||
$this->showCreator();
|
||||
$this->showTag();
|
||||
$this->showPrivacy();
|
||||
$this->showUpdated();
|
||||
$this->showActions();
|
||||
$this->showDescription();
|
||||
}
|
||||
|
||||
function showStats()
|
||||
{
|
||||
$this->out->elementStart('div', 'entry-summary entity_statistics');
|
||||
$this->out->elementStart('span', 'tagged-count');
|
||||
$this->out->element('a',
|
||||
array('href' => common_local_url('peopletagged',
|
||||
array('tagger' => $this->profile->nickname,
|
||||
'tag' => $this->peopletag->tag))),
|
||||
// TRANS: Link description for link to list of users tagged with a tag (so part of a list).
|
||||
_('Listed'));
|
||||
$this->out->raw($this->peopletag->taggedCount());
|
||||
$this->out->elementEnd('span');
|
||||
|
||||
$this->out->elementStart('span', 'subscriber-count');
|
||||
$this->out->element('a',
|
||||
array('href' => common_local_url('peopletagsubscribers',
|
||||
array('tagger' => $this->profile->nickname,
|
||||
'tag' => $this->peopletag->tag))),
|
||||
// TRANS: Link description for link to list of users subscribed to a tag.
|
||||
_('Subscribers'));
|
||||
$this->out->raw($this->peopletag->subscriberCount());
|
||||
$this->out->elementEnd('span');
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
function showOwnerOptions()
|
||||
{
|
||||
$this->out->elementStart('li', 'entity_edit');
|
||||
$this->out->element('a', array('href' =>
|
||||
common_local_url('editpeopletag', array('tagger' => $this->profile->nickname,
|
||||
'tag' => $this->peopletag->tag)),
|
||||
// TRANS: Title for link to edit list settings.
|
||||
'title' => _('Edit list settings.')),
|
||||
// TRANS: Text for link to edit list settings.
|
||||
_('Edit'));
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
|
||||
function showSubscribeForm()
|
||||
{
|
||||
$this->out->elementStart('li');
|
||||
|
||||
if (Event::handle('StartSubscribePeopletagForm', array($this->out, $this->peopletag))) {
|
||||
if ($this->current) {
|
||||
if ($this->peopletag->hasSubscriber($this->current->id)) {
|
||||
$form = new UnsubscribePeopletagForm($this->out, $this->peopletag);
|
||||
$form->show();
|
||||
} else {
|
||||
$form = new SubscribePeopletagForm($this->out, $this->peopletag);
|
||||
$form->show();
|
||||
}
|
||||
}
|
||||
Event::handle('EndSubscribePeopletagForm', array($this->out, $this->peopletag));
|
||||
}
|
||||
|
||||
$this->out->elementEnd('li');
|
||||
}
|
||||
|
||||
function showCreator()
|
||||
{
|
||||
$attrs = array();
|
||||
$attrs['href'] = $this->profile->profileurl;
|
||||
$attrs['class'] = 'h-card p-author nickname p-name';
|
||||
$attrs['rel'] = 'contact';
|
||||
$attrs['title'] = $this->profile->getFancyName();
|
||||
|
||||
$this->out->elementStart('a', $attrs);
|
||||
$this->showAvatar($this->profile);
|
||||
$this->out->text($this->profile->getNickname());
|
||||
$this->out->elementEnd('a');
|
||||
}
|
||||
|
||||
function showUpdated()
|
||||
{
|
||||
if (!empty($this->peopletag->modified)) {
|
||||
$this->out->element('abbr',
|
||||
array('title' => common_date_w3dtf($this->peopletag->modified),
|
||||
'class' => 'updated'),
|
||||
common_date_string($this->peopletag->modified));
|
||||
}
|
||||
}
|
||||
|
||||
function showPrivacy()
|
||||
{
|
||||
if ($this->peopletag->private) {
|
||||
$this->out->elementStart('a',
|
||||
array('href' => common_local_url('peopletagsbyuser',
|
||||
array('nickname' => $this->profile->nickname, 'private' => 1))));
|
||||
// TRANS: Privacy mode text in list list item for private list.
|
||||
$this->out->element('span', 'privacy_mode', _m('MODE','Private'));
|
||||
$this->out->elementEnd('a');
|
||||
}
|
||||
}
|
||||
|
||||
function showTag()
|
||||
{
|
||||
$this->out->elementStart('span', 'entry-title tag');
|
||||
$this->out->element('a',
|
||||
array('rel' => 'bookmark',
|
||||
'href' => $this->url()),
|
||||
htmlspecialchars($this->peopletag->tag));
|
||||
$this->out->elementEnd('span');
|
||||
}
|
||||
|
||||
function showActions()
|
||||
{
|
||||
$this->out->elementStart('div', 'entity_actions');
|
||||
$this->out->elementStart('ul');
|
||||
|
||||
if (!$this->peopletag->private) {
|
||||
$this->showSubscribeForm();
|
||||
}
|
||||
|
||||
if (!empty($this->current) && $this->profile->id == $this->current->id) {
|
||||
$this->showOwnerOptions();
|
||||
}
|
||||
$this->out->elementEnd('ul');
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
function showDescription()
|
||||
{
|
||||
$this->out->element('div', 'e-content description', $this->peopletag->description);
|
||||
}
|
||||
}
|
@ -315,7 +315,7 @@ abstract class ProfileAction extends ManagedAction
|
||||
$url = $lists->mainpage;
|
||||
} else {
|
||||
$url = common_local_url('showprofiletag',
|
||||
array('tagger' => $this->target->getNickname(),
|
||||
array('nickname' => $this->target->getNickname(),
|
||||
'tag' => $lists->tag));
|
||||
}
|
||||
if (!$first) {
|
||||
|
@ -921,6 +921,7 @@ class Router
|
||||
|
||||
$m->connect('all/:tag',
|
||||
array('action' => 'showprofiletag',
|
||||
'nickname' => $nickname,
|
||||
'tag' => self::REGEX_TAG));
|
||||
|
||||
foreach (array('subscriptions', 'subscribers') as $a) {
|
||||
@ -1003,9 +1004,9 @@ class Router
|
||||
'tagger_id' => '[0-9]+',
|
||||
'id' => '[0-9]+'));
|
||||
|
||||
$m->connect(':tagger/all/:tag',
|
||||
array('action' => 'showprofiletag',
|
||||
'tagger' => Nickname::DISPLAY_FMT,
|
||||
$m->connect(':nickname/all/:tag',
|
||||
array('action' => 'showprofiletag'),
|
||||
array('nickname' => Nickname::DISPLAY_FMT,
|
||||
'tag' => self::REGEX_TAG));
|
||||
|
||||
foreach (array('subscriptions', 'subscribers') as $a) {
|
||||
|
@ -54,7 +54,7 @@ class SearchEngine
|
||||
'nickname_desc sort mode can only be use when searching profile.'
|
||||
);
|
||||
} else {
|
||||
return $this->target->orderBy('nickname DESC');
|
||||
return $this->target->orderBy(sprintf('%1$s.nickname DESC', $this->table));
|
||||
}
|
||||
break;
|
||||
case 'nickname_asc':
|
||||
@ -63,7 +63,7 @@ class SearchEngine
|
||||
'nickname_desc sort mode can only be use when searching profile.'
|
||||
);
|
||||
} else {
|
||||
return $this->target->orderBy('nickname ASC');
|
||||
return $this->target->orderBy(sprintf('%1$s.nickname ASC', $this->table));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -112,11 +112,13 @@ class MySQLLikeSearch extends SearchEngine
|
||||
function query($q)
|
||||
{
|
||||
if ('profile' === $this->table) {
|
||||
$qry = sprintf('(nickname LIKE "%%%1$s%%" OR '.
|
||||
' fullname LIKE "%%%1$s%%" OR '.
|
||||
' location LIKE "%%%1$s%%" OR '.
|
||||
' bio LIKE "%%%1$s%%" OR '.
|
||||
' homepage LIKE "%%%1$s%%")', $this->target->escape($q, true));
|
||||
$qry = sprintf('(%2$s.nickname LIKE "%%%1$s%%" OR '.
|
||||
' %2$s.fullname LIKE "%%%1$s%%" OR '.
|
||||
' %2$s.location LIKE "%%%1$s%%" OR '.
|
||||
' %2$s.bio LIKE "%%%1$s%%" OR '.
|
||||
' %2$s.homepage LIKE "%%%1$s%%")',
|
||||
$this->target->escape($q, true),
|
||||
$this->table);
|
||||
} else if ('notice' === $this->table) {
|
||||
$qry = sprintf('content LIKE "%%%1$s%%"', $this->target->escape($q, true));
|
||||
} else {
|
||||
|
@ -45,7 +45,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
|
||||
class ServerException extends Exception
|
||||
{
|
||||
public function __construct($message = null, $code = 400) {
|
||||
public function __construct($message = null, $code = 500) {
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ abstract class SpawningDaemon extends Daemon
|
||||
for ($i = 1; $i <= $this->threads; $i++) {
|
||||
$pid = pcntl_fork();
|
||||
if ($pid < 0) {
|
||||
$this->log(LOG_ERROR, "Couldn't fork for thread $i; aborting\n");
|
||||
$this->log(LOG_ERR, "Couldn't fork for thread $i; aborting\n");
|
||||
exit(1);
|
||||
} else if ($pid == 0) {
|
||||
$this->initAndRunChild($i);
|
||||
@ -113,7 +113,7 @@ abstract class SpawningDaemon extends Daemon
|
||||
|
||||
$pid = pcntl_fork();
|
||||
if ($pid < 0) {
|
||||
$this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n");
|
||||
$this->log(LOG_ERR, "Couldn't fork to respawn thread $i; aborting thread.\n");
|
||||
} else if ($pid == 0) {
|
||||
$this->initAndRunChild($i);
|
||||
} else {
|
||||
@ -141,7 +141,7 @@ abstract class SpawningDaemon extends Daemon
|
||||
$this->parentWriter = $sockets[0];
|
||||
$this->parentReader = $sockets[1];
|
||||
} else {
|
||||
$this->log(LOG_ERROR, "Couldn't create inter-process sockets");
|
||||
$this->log(LOG_ERR, "Couldn't create inter-process sockets");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
101
lib/util.php
101
lib/util.php
@ -606,14 +606,15 @@ function common_remove_unicode_formatting($text)
|
||||
/**
|
||||
* Partial notice markup rendering step: build links to !group references.
|
||||
*
|
||||
* @param string $text partially rendered HTML
|
||||
* @param Notice $notice in whose context we're working
|
||||
* @param string $text partially rendered HTML
|
||||
* @param Profile $author the Profile that is composing the current notice
|
||||
* @param Notice $parent the Notice this is sent in reply to, if any
|
||||
* @return string partially rendered HTML
|
||||
*/
|
||||
function common_render_content($text, Notice $notice)
|
||||
function common_render_content($text, Profile $author, Notice $parent=null)
|
||||
{
|
||||
$text = common_render_text($text);
|
||||
$text = common_linkify_mentions($text, $notice);
|
||||
$text = common_linkify_mentions($text, $author, $parent);
|
||||
return $text;
|
||||
}
|
||||
|
||||
@ -623,13 +624,14 @@ function common_render_content($text, Notice $notice)
|
||||
*
|
||||
* Should generally not be called except from common_render_content().
|
||||
*
|
||||
* @param string $text partially-rendered HTML
|
||||
* @param Notice $notice in-progress or complete Notice object for context
|
||||
* @param string $text partially-rendered HTML
|
||||
* @param Profile $author the Profile that is composing the current notice
|
||||
* @param Notice $parent the Notice this is sent in reply to, if any
|
||||
* @return string partially-rendered HTML
|
||||
*/
|
||||
function common_linkify_mentions($text, Notice $notice)
|
||||
function common_linkify_mentions($text, Profile $author, Notice $parent=null)
|
||||
{
|
||||
$mentions = common_find_mentions($text, $notice);
|
||||
$mentions = common_find_mentions($text, $author, $parent);
|
||||
|
||||
// We need to go through in reverse order by position,
|
||||
// so our positions stay valid despite our fudging with the
|
||||
@ -648,7 +650,7 @@ function common_linkify_mentions($text, Notice $notice)
|
||||
|
||||
$linkText = common_linkify_mention($mention);
|
||||
|
||||
$text = substr_replace($text, $linkText, $position, mb_strlen($mention['text']));
|
||||
$text = substr_replace($text, $linkText, $position, $mention['length']);
|
||||
}
|
||||
|
||||
return $text;
|
||||
@ -687,33 +689,25 @@ function common_linkify_mention(array $mention)
|
||||
* Note the return data format is internal, to be used for building links and
|
||||
* such. Should not be used directly; rather, call common_linkify_mentions().
|
||||
*
|
||||
* @param string $text
|
||||
* @param Notice $notice notice in whose context we're building links
|
||||
* @param string $text
|
||||
* @param Profile $sender the Profile that is sending the current text
|
||||
* @param Notice $parent the Notice this text is in reply to, if any
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function common_find_mentions($text, Notice $notice)
|
||||
function common_find_mentions($text, Profile $sender, Notice $parent=null)
|
||||
{
|
||||
// The getProfile call throws NoProfileException on failure
|
||||
$sender = $notice->getProfile();
|
||||
|
||||
$mentions = array();
|
||||
|
||||
if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
|
||||
// Get the context of the original notice, if any
|
||||
$origAuthor = null;
|
||||
$origNotice = null;
|
||||
$origMentions = array();
|
||||
|
||||
// Is it a reply?
|
||||
|
||||
try {
|
||||
$origNotice = $notice->getParent();
|
||||
$origAuthor = $origNotice->getProfile();
|
||||
|
||||
$ids = $origNotice->getReplies();
|
||||
// Does it have a parent notice for context?
|
||||
if ($parent instanceof Notice) {
|
||||
$ids = $parent->getReplies(); // replied-to _profile ids_
|
||||
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
@ -723,8 +717,6 @@ function common_find_mentions($text, Notice $notice)
|
||||
// continue foreach
|
||||
}
|
||||
}
|
||||
} catch (NoParentNoticeException $e) {
|
||||
// It wasn't a reply to anything, so we can't harvest nickname-relations.
|
||||
}
|
||||
|
||||
$matches = common_find_mentions_raw($text);
|
||||
@ -741,34 +733,33 @@ function common_find_mentions($text, Notice $notice)
|
||||
// Start with conversation context, then go to
|
||||
// sender context.
|
||||
|
||||
if ($origAuthor instanceof Profile && $origAuthor->nickname == $nickname) {
|
||||
$mentioned = $origAuthor;
|
||||
if ($parent instanceof Notice && $parent->getProfile()->getNickname() === $nickname) {
|
||||
$mentioned = $parent->getProfile();
|
||||
} else if (!empty($origMentions) &&
|
||||
array_key_exists($nickname, $origMentions)) {
|
||||
$mentioned = $origMentions[$nickname];
|
||||
} else {
|
||||
// sets to null if no match
|
||||
$mentioned = common_relative_profile($sender, $nickname);
|
||||
}
|
||||
|
||||
if ($mentioned instanceof Profile) {
|
||||
$user = User::getKV('id', $mentioned->id);
|
||||
|
||||
if ($user instanceof User) {
|
||||
$url = common_local_url('userbyid', array('id' => $user->id));
|
||||
} else {
|
||||
$url = $mentioned->profileurl;
|
||||
try {
|
||||
$url = $mentioned->getUrl();
|
||||
} catch (InvalidUrlException $e) {
|
||||
$url = common_local_url('userbyid', array('id' => $mentioned->getID()));
|
||||
}
|
||||
|
||||
$mention = array('mentioned' => array($mentioned),
|
||||
'type' => 'mention',
|
||||
'text' => $match[0],
|
||||
'position' => $match[1],
|
||||
'length' => mb_strlen($match[0]),
|
||||
'title' => $mentioned->getFullname(),
|
||||
'url' => $url);
|
||||
|
||||
if (!empty($mentioned->fullname)) {
|
||||
$mention['title'] = $mentioned->fullname;
|
||||
}
|
||||
|
||||
$mentions[] = $mention;
|
||||
}
|
||||
}
|
||||
@ -779,20 +770,21 @@ function common_find_mentions($text, Notice $notice)
|
||||
$text, $hmatches, PREG_OFFSET_CAPTURE);
|
||||
foreach ($hmatches[1] as $hmatch) {
|
||||
$tag = common_canonical_tag($hmatch[0]);
|
||||
$plist = Profile_list::getByTaggerAndTag($sender->id, $tag);
|
||||
$plist = Profile_list::getByTaggerAndTag($sender->getID(), $tag);
|
||||
if (!$plist instanceof Profile_list || $plist->private) {
|
||||
continue;
|
||||
}
|
||||
$tagged = $sender->getTaggedSubscribers($tag);
|
||||
|
||||
$url = common_local_url('showprofiletag',
|
||||
array('tagger' => $sender->nickname,
|
||||
array('nickname' => $sender->getNickname(),
|
||||
'tag' => $tag));
|
||||
|
||||
$mentions[] = array('mentioned' => $tagged,
|
||||
'type' => 'list',
|
||||
'text' => $hmatch[0],
|
||||
'position' => $hmatch[1],
|
||||
'length' => mb_strlen($hmatch[0]),
|
||||
'url' => $url);
|
||||
}
|
||||
|
||||
@ -812,6 +804,7 @@ function common_find_mentions($text, Notice $notice)
|
||||
'type' => 'group',
|
||||
'text' => $hmatch[0],
|
||||
'position' => $hmatch[1],
|
||||
'length' => mb_strlen($hmatch[0]),
|
||||
'url' => $group->permalink(),
|
||||
'title' => $group->getFancyName());
|
||||
}
|
||||
@ -875,7 +868,7 @@ function common_replace_urls_callback($text, $callback, $arg = null) {
|
||||
'(?:'.
|
||||
'(?:'. //Known protocols
|
||||
'(?:'.
|
||||
'(?:(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://)'.
|
||||
'(?:(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|ircs?)://)'.
|
||||
'|'.
|
||||
'(?:(?:mailto|aim|tel|xmpp):)'.
|
||||
')'.
|
||||
@ -985,20 +978,9 @@ function common_linkify($url) {
|
||||
$canon = "mailto:$url";
|
||||
$longurl = "mailto:$url";
|
||||
} else {
|
||||
|
||||
$canon = File_redirection::_canonUrl($url);
|
||||
|
||||
$longurl_data = File_redirection::where($canon, common_config('attachments', 'process_links'));
|
||||
if (is_array($longurl_data)) {
|
||||
$longurl = $longurl_data['url'];
|
||||
} elseif (is_string($longurl_data)) {
|
||||
$longurl = $longurl_data;
|
||||
} else {
|
||||
// Unable to reach the server to verify contents, etc
|
||||
// Just pass the link on through for now.
|
||||
common_log(LOG_ERR, "Can't linkify url '$url'");
|
||||
$longurl = $url;
|
||||
}
|
||||
$longurl = $longurl_data->url;
|
||||
}
|
||||
|
||||
$attrs = array('href' => $canon, 'title' => $longurl);
|
||||
@ -1554,14 +1536,24 @@ function common_root_url($ssl=false)
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns $bytes bytes of raw random data
|
||||
*/
|
||||
function common_random_rawstr($bytes)
|
||||
{
|
||||
$rawstr = @file_exists('/dev/urandom')
|
||||
? common_urandom($bytes)
|
||||
: common_mtrand($bytes);
|
||||
|
||||
return $rawstr;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns $bytes bytes of random data as a hexadecimal string
|
||||
*/
|
||||
function common_random_hexstr($bytes)
|
||||
{
|
||||
$str = @file_exists('/dev/urandom')
|
||||
? common_urandom($bytes)
|
||||
: common_mtrand($bytes);
|
||||
$str = common_random_rawstr($bytes);
|
||||
|
||||
$hexstr = '';
|
||||
for ($i = 0; $i < $bytes; $i++) {
|
||||
@ -1866,6 +1858,7 @@ function common_get_mime_media($type)
|
||||
return strtolower($tmp[0]);
|
||||
}
|
||||
|
||||
// Get only the mimetype and not additional info (separated from bare mime with semi-colon)
|
||||
function common_bare_mime($mimetype)
|
||||
{
|
||||
$mimetype = mb_strtolower($mimetype);
|
||||
|
0
local/.gitignore
vendored
0
local/.gitignore
vendored
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Afrikaans (http://www.transifex.com/projects/p/gnu-social/language/af/)\n"
|
||||
"Language-Team: Afrikaans (http://www.transifex.com/gnu-social/gnu-social/language/af/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Arabic (http://www.transifex.com/projects/p/gnu-social/language/ar/)\n"
|
||||
"Language-Team: Arabic (http://www.transifex.com/gnu-social/gnu-social/language/ar/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Arabic (Egypt) (http://www.transifex.com/projects/p/gnu-social/language/ar_EG/)\n"
|
||||
"Language-Team: Arabic (Egypt) (http://www.transifex.com/gnu-social/gnu-social/language/ar_EG/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-05 17:29+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Asturian (http://www.transifex.com/projects/p/gnu-social/language/ast/)\n"
|
||||
"Language-Team: Asturian (http://www.transifex.com/gnu-social/gnu-social/language/ast/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,12 +10,12 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Belarusian (Tarask) (http://www.transifex.com/projects/p/gnu-social/language/be@tarask/)\n"
|
||||
"Language-Team: Belarusian (Tarask) (http://www.transifex.com/gnu-social/gnu-social/language/be@tarask/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: be@tarask\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#. TRANS: Database error message.
|
||||
#: index.php:118
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/projects/p/gnu-social/language/bg/)\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/gnu-social/gnu-social/language/bg/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
11135
locale/bn_IN/LC_MESSAGES/statusnet.po
Normal file
11135
locale/bn_IN/LC_MESSAGES/statusnet.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Breton (http://www.transifex.com/projects/p/gnu-social/language/br/)\n"
|
||||
"Language-Team: Breton (http://www.transifex.com/gnu-social/gnu-social/language/br/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Catalan (http://www.transifex.com/projects/p/gnu-social/language/ca/)\n"
|
||||
"Language-Team: Catalan (http://www.transifex.com/gnu-social/gnu-social/language/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -11,7 +11,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Czech (http://www.transifex.com/projects/p/gnu-social/language/cs/)\n"
|
||||
"Language-Team: Czech (http://www.transifex.com/gnu-social/gnu-social/language/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Danish (http://www.transifex.com/projects/p/gnu-social/language/da/)\n"
|
||||
"Language-Team: Danish (http://www.transifex.com/gnu-social/gnu-social/language/da/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -11,7 +11,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-18 23:41+0000\n"
|
||||
"Last-Translator: D P\n"
|
||||
"Language-Team: German (http://www.transifex.com/projects/p/gnu-social/language/de/)\n"
|
||||
"Language-Team: German (http://www.transifex.com/gnu-social/gnu-social/language/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Greek (http://www.transifex.com/projects/p/gnu-social/language/el/)\n"
|
||||
"Language-Team: Greek (http://www.transifex.com/gnu-social/gnu-social/language/el/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,14 +3,15 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Manuel Ortega <manuel@elarte.coop>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: GNU social\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Esperanto (http://www.transifex.com/projects/p/gnu-social/language/eo/)\n"
|
||||
"PO-Revision-Date: 2015-06-13 14:41+0000\n"
|
||||
"Last-Translator: Manuel Ortega <manuel@elarte.coop>\n"
|
||||
"Language-Team: Esperanto (http://www.transifex.com/gnu-social/gnu-social/language/eo/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@ -30,7 +31,7 @@ msgstr ""
|
||||
#. TRANS: Error message.
|
||||
#: index.php:137
|
||||
msgid "An error occurred."
|
||||
msgstr ""
|
||||
msgstr "Eraro okazis."
|
||||
|
||||
#. TRANS: Error message displayed when there is no StatusNet configuration
|
||||
#. file.
|
||||
@ -1029,7 +1030,7 @@ msgstr ""
|
||||
#. having the right to do so.
|
||||
#: actions/apilistmembers.php:46
|
||||
msgid "You are not allowed to add members to this list."
|
||||
msgstr ""
|
||||
msgstr "Vi ne rajtas aldoni membrojn al tiu ĉi listo."
|
||||
|
||||
#. TRANS: Client error displayed when trying to modify list members without
|
||||
#. specifying them.
|
||||
|
@ -9,9 +9,9 @@ msgstr ""
|
||||
"Project-Id-Version: GNU social\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-27 15:36+0000\n"
|
||||
"PO-Revision-Date: 2015-02-28 20:31+0000\n"
|
||||
"Last-Translator: Juan Riquelme González <soulchainer@gmail.com>\n"
|
||||
"Language-Team: Spanish (http://www.transifex.com/projects/p/gnu-social/language/es/)\n"
|
||||
"Language-Team: Spanish (http://www.transifex.com/gnu-social/gnu-social/language/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@ -4241,7 +4241,7 @@ msgstr "Confirmar"
|
||||
#: actions/passwordsettings.php:121 actions/recoverpassword.php:257
|
||||
#: actions/register.php:420
|
||||
msgid "Same as password above."
|
||||
msgstr "Repetir la contraseña de arriba."
|
||||
msgstr "Repite la contraseña anterior."
|
||||
|
||||
#. TRANS: Button text on page where to change password.
|
||||
#: actions/passwordsettings.php:126
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Basque (http://www.transifex.com/projects/p/gnu-social/language/eu/)\n"
|
||||
"Language-Team: Basque (http://www.transifex.com/gnu-social/gnu-social/language/eu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Persian (http://www.transifex.com/projects/p/gnu-social/language/fa/)\n"
|
||||
"Language-Team: Persian (http://www.transifex.com/gnu-social/gnu-social/language/fa/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Finnish (http://www.transifex.com/projects/p/gnu-social/language/fi/)\n"
|
||||
"Language-Team: Finnish (http://www.transifex.com/gnu-social/gnu-social/language/fi/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
|
||||
"PO-Revision-Date: 2015-02-07 14:32+0000\n"
|
||||
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
|
||||
"Language-Team: Friulian (http://www.transifex.com/projects/p/gnu-social/language/fur/)\n"
|
||||
"Language-Team: Friulian (http://www.transifex.com/gnu-social/gnu-social/language/fur/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user