avatar/*
-background/*
files/*
file/*
local/*
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
------
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)) {
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.
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
$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);
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));
{
// 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');
}
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));
}
}
// 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();
* 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
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
'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'),
);
/**
* 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
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(
'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'),
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
'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'),
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;
}
echo "DONE.\n";
echo "Resuming core schema upgrade...";
}
-}
+}
\ No newline at end of file
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) {
+ try {
+ $file = File::getByUrl($long_url);
+ } catch (NoResultException $e) {
// Check if the target URL is itself a redirect...
$redir = File_redirection::where($long_url);
$file = $redir->getFile();
$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;
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))) {
if (!array_key_exists($col, $vals)) {
continue;
} elseif (is_null($vals[$col])) {
- throw new ServerException("NULL values not allowed in getByPK for column '{$col}'");
+ throw new ServerException("NULL values not allowed in getByKeys for column '{$col}'");
}
$object->$col = $vals[$col];
}
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)) {
}
}
- // Clear the cache for subscribed users, so they'll update at next request
- // XXX: someone clever could prepend instead of clearing the cache
-
- // Save per-notice metadata...
-
- if (isset($replies)) {
- $notice->saveKnownReplies($replies);
- } else {
- $notice->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($notice->verb, array(ActivityVerb::POST))) {
+ if (isset($replies)) {
+ $notice->saveKnownReplies($replies);
+ } else {
+ $notice->saveReplies();
+ }
- if (isset($tags)) {
- $notice->saveKnownTags($tags);
- } else {
- $notice->saveTags();
- }
+ if (isset($tags)) {
+ $notice->saveKnownTags($tags);
+ } else {
+ $notice->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.
- $notice->saveKnownGroups($groups);
+ $notice->saveKnownGroups($groups);
- if (isset($urls)) {
- $notice->saveKnownUrls($urls);
- } else {
- $notice->saveUrls();
+ if (isset($urls)) {
+ $notice->saveKnownUrls($urls);
+ } else {
+ $notice->saveUrls();
+ }
}
if ($distribute) {
}
// Get ActivityObject properties
+ $actobj = null;
if (!empty($act->id)) {
// implied object
$options['uri'] = $act->id;
$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');
}
}
$stored->url = $url;
$stored->verb = $act->verb;
- // Use the local user's shortening preferences, if applicable.
- $stored->rendered = $actor->isLocal()
- ? $actor->shortenLinks($act->content)
- : common_purify($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);
+ // 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?
if (!empty($act->time)) {
$stored->created = common_sql_date($act->time);
// 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, &$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($group_ids);
+ $stored->saveKnownGroups($group_ids);
- if (!empty($urls)) {
- $stored->saveKnownUrls($urls);
- } else {
- $stored->saveUrls();
+ if (!empty($urls)) {
+ $stored->saveKnownUrls($urls);
+ } else {
+ $stored->saveUrls();
+ }
}
if ($distribute) {
$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
return;
}
- $sender = Profile::getKV($this->profile_id);
+ $sender = $this->getProfile();
foreach (array_unique($uris) as $uri) {
try {
continue;
}
- $this->saveReply($profile->id);
- self::blow('reply:stream:%d', $profile->id);
+ $this->saveReply($profile->getID());
+ self::blow('reply:stream:%d', $profile->getID());
}
-
- return;
}
/**
function saveReplies()
{
- // Don't save reply data for repeats
-
- if ($this->isRepeat()) {
- return array();
- }
-
$sender = $this->getProfile();
$replied = array();
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)
function sendReplyNotifications()
{
// Don't send reply notifications for repeats
-
if ($this->isRepeat()) {
return array();
}
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));
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();
return false;
}
+ public function hasParent()
+ {
+ try {
+ $this->getParent();
+ } catch (NoParentNoticeException $e) {
+ return false;
+ }
+ return true;
+ }
+
public function getParent()
{
- $reply_to_id = null;
+ $reply_to_id = null;
if (empty($this->reply_to)) {
throw new NoParentNoticeException($this);
}
- // 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);
- }
-
+ // 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;
}
* 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)
'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'),
return $def;
}
-
- /* the code above is auto generated do not remove the tag below */
- ###END_AUTOCODE
public static function getByEmail($email)
{
$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));
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;
}
// 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;
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);
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;
}
/**
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);
}
/**
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));
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) {
'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(),
// 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
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';
}
// 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;
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
*
* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
require_once 'Mail.php';
$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;
*/
function mail_send($recipients, $headers, $body)
{
+ global $_PEAR;
+
try {
// XXX: use Mail_Queue... maybe
$backend = mail_backend();
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(
return $this->short_fileurl;
}
+ function getEnclosure()
+ {
+ return $this->getFile()->getEnclosure();
+ }
+
function delete()
{
$filepath = File::path($this->filename);
if (!empty($this->notice->reply_to) || count($this->getProfileAddressees()) > 0) {
$this->elementStart('div', array('class' => 'parents'));
- if (!empty($this->notice->reply_to)) { $this->showParent(); }
+ try {
+ $this->showParent();
+ } catch (NoParentNoticeException $e) {
+ // no parent notice
+ }
if ($this->addressees) { $this->showAddressees(); }
$this->elementEnd('div');
}
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()));
}
class ServerException extends Exception
{
- public function __construct($message = null, $code = 400) {
+ public function __construct($message = null, $code = 500) {
parent::__construct($message, $code);
}
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);
$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 {
$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);
}
}
/**
* 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;
}
*
* 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
* 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 {
// continue foreach
}
}
- } catch (NoParentNoticeException $e) {
- // It wasn't a reply to anything, so we can't harvest nickname-relations.
- } catch (NoResultException $e) {
- // The parent notice was deleted.
}
$matches = common_find_mentions_raw($text);
// 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),
'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;
}
}
$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;
}
server {
- # Ports
listen 80;
- # Uncomment the following line
- # to enable HTTPS
- #listen 443 ssl;
+ listen [::]:80;
+
+ # FIXME: change domain name here (and also make sure you do the same in the next 'server' section)
+ server_name social.example.org;
+
+ # redirect all traffic to HTTPS
+ rewrite ^ https://$server_name$request_uri? permanent;
+}
+
+server {
+ # Use HTTPS. Seriously. Set it up with a cert (any cert) before you run the install.
+ listen 443 ssl;
# Server name
- # Change "example.org" to your domain name
- server_name example.org;
+ # Change "social.example.org" to your site's domain name
+ server_name social.example.org;
# SSL
# Uncomment and change the paths to setup
# your SSL key/cert. See https://cipherli.st/
# for more information
- #ssl_certificate /path/to/ssl.cert;
- #ssl_certificate_key /path/to/ssl.key;
+ ssl_certificate ssl/certs/social.example.org.crt;
+ ssl_certificate_key ssl/private/social.example.org.key;
# Logs
# Uncomment and change the paths to setup
# PHP
location ~ \.php {
- fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
+ include snippets/fastcgi-php.conf;
+
+ # This should be the same value as in your (optional) /etc/php5/fpm/pool.d/$server.conf
+ fastcgi_pass unix:/var/run/php5-fpm.sock;
+
# Remove the "fastcgi_pass" line above and uncomment
# the one below to use TCP sockets instead of Unix sockets
#fastcgi_pass 127.0.0.1:9000;
- fastcgi_index index.php;
- include fastcgi.conf;
}
# Location
location @gnusocial {
rewrite ^(.*)$ /index.php?p=$1 last;
}
+
+ # Restrict access that is unnecessary anyway
+ location ~ /\.(ht|git) {
+ deny all;
+ }
}
}
//$stored->content = $stored->content ?: _('Notice deleted.');
//$stored->rendered = $stored->rendered ?: $stored->rendered;
- common_debug('DELETENOTICE: Replacement notice has been prepared: '.var_export($stored, true));
// Let's see if this has been deleted already.
- $deleted = Deleted_notice::getKV('uri', $stored->getUri());
- if ($deleted instanceof Deleted_notice) {
+ try {
+ $deleted = Deleted_notice::getByKeys( ['uri' => $stored->getUri()] );
return $deleted;
- }
-
- $deleted = new Deleted_notice();
+ } catch (NoResultException $e) {
+ $deleted = new Deleted_notice();
- $deleted->id = $target->getID();
- $deleted->profile_id = $actor->getID();
- $deleted->uri = $stored->getUri();
- $deleted->act_uri = $stored->getUri();
- $deleted->act_created = $stored->created;
- $deleted->created = common_sql_now();
+ $deleted->id = $target->getID();
+ $deleted->profile_id = $actor->getID();
+ $deleted->uri = $stored->getUri();
+ $deleted->act_created = $stored->created;
+ $deleted->created = common_sql_now();
- $result = $deleted->insert();
- if ($result === false) {
- throw new ServerException('Could not insert Deleted_notice entry into database!');
+ // throws exception on error
+ $result = $deleted->insert();
}
// Now we delete the original notice, leaving the id and uri free.
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 $act_uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $act_created; // datetime() not_null
public $created; // datetime() not_null
'id' => array('type' => 'int', 'not null' => true, 'description' => 'notice ID'),
'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile that deleted the notice'),
'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the deleted notice'),
- 'act_uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'),
'act_created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was deleted'),
),
'primary key' => array('id'),
'unique keys' => array(
'deleted_notice_uri_key' => array('uri'),
- 'deleted_notice_act_uri_key' => array('act_uri'),
),
'indexes' => array(
'deleted_notice_profile_id_idx' => array('profile_id'),
static public function fromStored(Notice $stored)
{
$class = get_called_class();
- return self::getByPK(array('uri' => $stored->getUri()));
+ return self::getByKeys( ['uri' => $stored->getUri()] );
}
// The one who deleted the notice, not the notice's author
$schema = Schema::get();
$schemadef = $schema->getTableDef($table);
- // 2015-10-03 We change the meaning of the 'uri' field and move its
- // content to the 'act_uri' for the deleted activity. act_created is
- // added too.
- if (isset($schemadef['fields']['act_uri'])) {
- // We already have the act_uri field, so no need to migrate to it.
+ // 2015-12-31 If we have the act_uri field we want to remove it
+ // since there's no difference in delete verbs and the original URI
+ // but the act_created field stays.
+ if (!isset($schemadef['fields']['act_uri']) && isset($schemadef['fields']['act_created'])) {
+ // We don't have an act_uri field, and we also have act_created, so no need to migrate.
return;
+ } elseif (isset($schemadef['fields']['act_uri']) && !isset($schemadef['fields']['act_created'])) {
+ throw new ServerException('Something is wrong with your database, you have the act_uri field but NOT act_created in deleted_notice!');
}
- echo "\nFound old $table table, upgrading it to contain 'act_uri' and 'act_created' field...";
- $schemadef['fields']['act_uri'] = array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the deleted notice');
- $schemadef['fields']['act_created'] = array('type' => 'datetime', 'not null' => true, 'description' => 'datetime the notice record was created');
- unset($schemadef['unique keys']);
- $schema->ensureTable($table, $schemadef);
+ if (!isset($schemadef['fields']['act_created'])) {
+ // this is a "normal" upgrade from StatusNet for example
+ echo "\nFound old $table table, upgrading it to add 'act_created' field...";
- $deleted = new Deleted_notice();
- $result = $deleted->find();
- if ($result === false) {
- print "\nFound no deleted_notice entries, continuing...";
- return true;
- }
- print "\nFound $result deleted_notice entries, aligning with new database layout: ";
- while($deleted->fetch()) {
- $orig = clone($deleted);
- $deleted->act_uri = $deleted->uri;
- // this is a fake URI just to have something to put there to avoid NULL. crc32 of uri is to avoid collisions
- $deleted->uri = TagURI::mint(strtolower(get_called_class()).':%d:%s:%s:%s:crc32=%x',
- $deleted->profile_id,
- ActivityUtils::resolveUri(self::getObjectType(), true),
- 'unknown',
- common_date_iso8601($deleted->created),
- crc32($deleted->act_uri)
- );
- $deleted->act_created = $deleted->created; // we don't actually know when the notice was created
- $deleted->updateWithKeys($orig, 'id');
- print ".";
+ $schemadef['fields']['act_created'] = array('type' => 'datetime', 'not null' => true, 'description' => 'datetime the notice record was created');
+ $schema->ensureTable($table, $schemadef);
+
+ $deleted = new Deleted_notice();
+ // we don't actually know when the notice was created for the old ones
+ $deleted->query('UPDATE deleted_notice SET act_created=created;');
+ } else {
+ // 2015-10-03 For a while we had act_uri and act_created fields which
+ // apparently wasn't necessary.
+ echo "\nFound old $table table, upgrading it to remove 'act_uri' field...";
+
+ // we stored what should be in 'uri' in the 'act_uri' field for some night-coding reason.
+ $deleted = new Deleted_notice();
+ $deleted->query('UPDATE deleted_notice SET uri=act_uri;');
}
print "DONE.\n";
print "Resuming core schema upgrade...";
}
+ function insert()
+ {
+ $result = parent::insert();
+
+ if ($result === false) {
+ common_log_db_error($this, 'INSERT', __FILE__);
+ // TRANS: Server exception thrown when a stored object entry cannot be saved.
+ throw new ServerException('Could not save Deleted_notice');
+ }
+ }
}
* @link http://status.net/
*/
-if (!defined('GNUSOCIAL') && !defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* List users for autocompletion
foreach($this->profiles as $profile){
$avatarUrl = $profile->avatarUrl(AVATAR_MINI_SIZE);
$acct = $profile->getAcctUri();
- $identifier = split(':', $profile->getAcctUri(), 2)[1];
+ $identifier = explode(':', $profile->getAcctUri(), 2)[1];
$results[] = array(
'value' => '@'.$identifier,
'nickname' => $profile->getNickname(),
);
}
foreach($this->groups as $group){
+ $profile = $group->getProfile();
// sigh.... encapsulate this upstream!
if ($group->mini_logo) {
$avatarUrl = $group->mini_logo;
$avatarUrl = User_group::defaultLogo(AVATAR_MINI_SIZE);
}
$acct = $profile->getAcctUri();
- $identifier = split(':', $profile->getAcctUri(), 2)[1];
+ $identifier = explode(':', $profile->getAcctUri(), 2)[1];
$results[] = array(
'value' => '!'.$group->getNickname(),
'nickname' => $group->getNickname(),
return true;
}
+ public function onBeforePluginCheckSchema()
+ {
+ RSVP::beforeSchemaUpdate();
+ return true;
+ }
+
/**
* Map URLs to actions
*
throw new ClientException(_m('No such RSVP.'));
}
- $this->event = Happening::getKV('id', $this->rsvp->event_id);
+ $this->event = Happening::getKV('uri', $this->rsvp->event_uri);
if (empty($this->event)) {
// TRANS: Client exception thrown when referring to a non-existing event.
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Data class for happenings
$options);
if (!array_key_exists('uri', $options)) {
- $options['uri'] = $ev->uri;
+ $options['uri'] = $ev->getUri();
}
if (!empty($url)) {
$options['urls'] = array($url);
}
- $saved = Notice::saveNew($profile->id,
+ $saved = Notice::saveNew($profile->getID(),
$content,
array_key_exists('source', $options) ?
$options['source'] : 'web',
return $this->url;
}
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
function getNotice()
{
- return Notice::getKV('uri', $this->uri);
+ return Notice::getKV('uri', $this->getUri());
}
static function fromNotice(Notice $notice)
{
- return Happening::getKV('uri', $notice->uri);
+ return Happening::getKV('uri', $notice->getUri());
}
function getRSVPs()
function getRSVP($profile)
{
- return RSVP::pkeyGet(array('profile_id' => $profile->id,
- 'event_id' => $this->id));
+ return RSVP::pkeyGet(array('profile_id' => $profile->getID(),
+ 'event_uri' => $this->getUri()));
}
}
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Data class for event RSVPs
public $id; // varchar(36) UUID
public $uri; // varchar(191) not 255 because utf8mb4 takes more space
public $profile_id; // int
- public $event_id; // varchar(36) UUID
+ public $event_uri; // varchar(191) not 255 because utf8mb4 takes more space
public $response; // tinyint
public $created; // datetime
- /**
- * Add the compound profile_id/event_id index to our cache keys
- * since the DB_DataObject stuff doesn't understand compound keys
- * except for the primary.
- *
- * @return array
- */
- function _allCacheKeys() {
- $keys = parent::_allCacheKeys();
- $keys[] = self::multicacheKey('RSVP', array('profile_id' => $this->profile_id,
- 'event_id' => $this->event_id));
- return $keys;
- }
-
/**
* The One True Thingy that must be defined and declared.
*/
'length' => 191,
'not null' => true),
'profile_id' => array('type' => 'int'),
- 'event_id' => array('type' => 'char',
- 'length' => 36,
+ 'event_uri' => array('type' => 'varchar',
+ 'length' => 191,
'not null' => true,
- 'description' => 'UUID'),
+ 'description' => 'Event URI'),
'response' => array('type' => 'char',
'length' => '1',
'description' => 'Y, N, or ? for three-state yes, no, maybe'),
'primary key' => array('id'),
'unique keys' => array(
'rsvp_uri_key' => array('uri'),
- 'rsvp_profile_event_key' => array('profile_id', 'event_id'),
+ 'rsvp_profile_event_key' => array('profile_id', 'event_uri'),
),
- 'foreign keys' => array('rsvp_event_id_key' => array('event', array('event_id' => 'id')),
+ 'foreign keys' => array('rsvp_event_uri_key' => array('happening', array('event_uri' => 'uri')),
'rsvp_profile_id__key' => array('profile', array('profile_id' => 'id'))),
'indexes' => array('rsvp_created_idx' => array('created')),
);
}
+ static public function beforeSchemaUpdate()
+ {
+ $table = strtolower(get_called_class());
+ $schema = Schema::get();
+ $schemadef = $schema->getTableDef($table);
+
+ // 2015-12-31 RSVPs refer to Happening by event_uri now, not event_id. Let's migrate!
+ if (isset($schemadef['fields']['event_uri'])) {
+ // We seem to have already migrated, good!
+ return;
+ }
+
+ // this is a "normal" upgrade from StatusNet for example
+ echo "\nFound old $table table, upgrading it to add 'event_uri' field...";
+
+ $schemadef['fields']['event_uri'] = array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'Event URI');
+ $schema->ensureTable($table, $schemadef);
+
+ $rsvp = new RSVP();
+ $rsvp->find();
+ while ($rsvp->fetch()) {
+ $event = Happening::getKV('id', $rsvp->event_id);
+ if (!$event instanceof Happening) {
+ continue;
+ }
+ $orig = clone($rsvp);
+ $rsvp->event_uri = $event->uri;
+ $rsvp->updateWithKeys($orig);
+ }
+ print "DONE.\n";
+ print "Resuming core schema upgrade...";
+ }
+
function saveNew(Profile $profile, $event, $verb, array $options = array())
{
if (array_key_exists('uri', $options)) {
}
}
- $other = RSVP::pkeyGet(array('profile_id' => $profile->id,
- 'event_id' => $event->id));
-
- if (!empty($other)) {
+ try {
+ $other = RSVP::getByKeys( [ 'profile_id' => $profile->getID(),
+ 'event_uri' => $event->getUri(),
+ ] );
// TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
- throw new ClientException(_m('RSVP already exists.'));
+ throw new AlreadyFulfilledException(_m('RSVP already exists.'));
+ } catch (NoResultException $e) {
+ // No previous RSVP, so go ahead and add.
}
$rsvp = new RSVP();
$rsvp->id = UUID::gen();
- $rsvp->profile_id = $profile->id;
- $rsvp->event_id = $event->id;
+ $rsvp->profile_id = $profile->getID();
+ $rsvp->event_uri = $event->getUri();
$rsvp->response = self::codeFor($verb);
if (array_key_exists('created', $options)) {
$rsvp->insert();
- self::blow('rsvp:for-event:%s', $event->id);
+ self::blow('rsvp:for-event:%s', $event->getUri());
// XXX: come up with something sexier
$eventNotice = $event->getNotice();
if (!empty($eventNotice)) {
- $options['reply_to'] = $eventNotice->id;
+ $options['reply_to'] = $eventNotice->getID();
}
- $saved = Notice::saveNew($profile->id,
+ $saved = Notice::saveNew($profile->getID(),
$content,
array_key_exists('source', $options) ?
$options['source'] : 'web',
static function forEvent(Happening $event)
{
- $keypart = sprintf('rsvp:for-event:%s', $event->id);
+ $keypart = sprintf('rsvp:for-event:%s', $event->getUri());
$idstr = self::cacheGet($keypart);
$rsvp->selectAdd();
$rsvp->selectAdd('id');
- $rsvp->event_id = $event->id;
+ $rsvp->event_uri = $event->getUri();
if ($rsvp->find()) {
while ($rsvp->fetch()) {
function getEvent()
{
- $event = Happening::getKV('id', $this->event_id);
+ $event = Happening::getKV('uri', $this->event_uri);
if (empty($event)) {
// TRANS: Exception thrown when requesting a non-existing event.
// TRANS: %s is the ID of the non-existing event.
- throw new Exception(sprintf(_m('No event with ID %s.'),$this->event_id));
+ throw new Exception(sprintf(_m('No event with URI %s.'),$this->event_uri));
}
return $event;
}
function asHTML()
{
- $event = Happening::getKV('id', $this->event_id);
-
return self::toHTML($this->getProfile(),
- $event,
+ $this->getEvent(),
$this->response);
}
function asString()
{
- $event = Happening::getKV('id', $this->event_id);
-
return self::toString($this->getProfile(),
- $event,
+ $this->getEvent(),
$this->response);
}
class RawEventsNoticeStream extends NoticeStream
{
- protected $target;
- protected $own;
-
- function __construct(Profile $target)
- {
- $this->target = $target;
- }
-
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
$notice = new Notice();
$qry = 'SELECT notice.* FROM notice ';
$qry .= 'INNER JOIN happening ON happening.uri = notice.uri ';
- $qry .= 'WHERE happening.profile_id = ' . $this->target->getID() . ' ';
$qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' ';
if ($since_id != 0) {
$qry .= 'AND notice.id <= ' . $max_id . ' ';
}
- // NOTE: we sort by bookmark time, not by notice time!
- $qry .= 'ORDER BY created DESC ';
+ // NOTE: we sort by event time, not by notice time!
+ $qry .= 'ORDER BY happening.created DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
}
class EventsNoticeStream extends ScopingNoticeStream
{
- function __construct(Profile $target, Profile $scoped=null)
+ // possible values of RSVP in our database
+ protected $rsvp = ['Y', 'N', '?'];
+ protected $target = null;
+
+ function __construct(Profile $target, Profile $scoped=null, array $rsvp=array())
{
- $stream = new RawEventsNoticeStream($target);
+ $stream = new RawEventsNoticeStream();
if ($target->sameAs($scoped)) {
- $key = 'bookmark:ids_by_user_own:'.$target->getID();
+ $key = 'happening:ids_for_user_own:'.$target->getID();
} else {
- $key = 'bookmark:ids_by_user:'.$target->getID();
+ $key = 'happening:ids_for_user:'.$target->getID();
}
+ // Match RSVP against our possible values, given in the class variable
+ // and if no RSVPs are given is empty, assume we want all events, even
+ // without RSVPs from this profile.
+ $this->rsvp = array_intersect($this->rsvp, $rsvp);
+ $this->target = $target;
+
parent::__construct(new CachingNoticeStream($stream, $key), $scoped);
}
+
+ function filter($notice)
+ {
+ if (!parent::filter($notice)) {
+ // if not in our scope, return false
+ return false;
+ }
+
+ if (empty($this->rsvp)) {
+ // Don't filter on RSVP (for only events with RSVP if no responses
+ // are given (give ['Y', 'N', '?'] for only RSVP'd events!).
+ return true;
+ }
+
+ $rsvp = new RSVP();
+ $rsvp->profile_id = $this->target->getID();
+ $rsvp->event_uri = $notice->getUri();
+ $rsvp->whereAddIn('response', $this->rsvp, $rsvp->columnType('response'));
+
+ // filter out if no RSVP match was found
+ return $rsvp->N > 0;
+ }
}
}
}
- $this->saveNotice();
+ try {
+ $this->saveNotice();
+ } catch (AlreadyFulfilledException $e) {
+ return;
+ }
}
/**
throw new ClientException(_m('Not to anyone in reply to anything.'));
}
- $existing = Notice::getKV('uri', $this->activity->objects[0]->id);
- if ($existing instanceof Notice) {
- common_log(LOG_ERR, "Not saving notice with duplicate URI '".$existing->getUri()."' (seems it already exists).");
+ try {
+ $this->saveNotice();
+ } catch (AlreadyFulfilledException $e) {
return;
}
-
- $this->saveNotice();
}
/**
return;
}
- for ($i = 0; $i < $entries->length; $i++) {
- $entry = $entries->item($i);
- $this->processEntry($entry, $feed, $source);
- }
+ $this->processEntries($entries, $feed, $source);
}
public function processRssFeed(DOMElement $rss, $source)
$items = $channel->getElementsByTagName('item');
- for ($i = 0; $i < $items->length; $i++) {
- $item = $items->item($i);
- $this->processEntry($item, $channel, $source);
+ $this->processEntries($items, $channel, $source);
+ }
+
+ public function processEntries(DOMNodeList $entries, DOMElement $feed, $source)
+ {
+ for ($i = 0; $i < $entries->length; $i++) {
+ $entry = $entries->item($i);
+ try {
+ $this->processEntry($entry, $feed, $source);
+ } catch (AlreadyFulfilledException $e) {
+ common_debug('We already had this entry: '.$e->getMessage());
+ }
}
}
$msg = "Failed PuSH to $sub->callback for $sub->topic: " .
$e->getMessage();
if ($retries > 0) {
- common_log(LOG_ERR, "$msg; scheduling for $retries more tries");
+ common_log(LOG_INFO, "$msg; scheduling for $retries more tries");
// @fixme when we have infrastructure to schedule a retry
// after a delay, use it.
'title'=>'oEmbed'),null);
break;
case 'shownotice':
+ if (!$action->notice->isLocal()) {
+ break;
+ }
try {
$action->element('link',array('rel'=>'alternate',
'type'=>'application/json+oembed',
'version' => array('type' => 'varchar', 'length' => 20, 'description' => 'oEmbed spec. version'),
'type' => array('type' => 'varchar', 'length' => 20, 'description' => 'oEmbed type: photo, video, link, rich'),
'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'),
- 'provider' => array('type' => 'varchar', 'length' => 50, 'description' => 'name of this oEmbed provider'),
- 'provider_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL of this oEmbed provider'),
+ 'provider' => array('type' => 'text', 'description' => 'name of this oEmbed provider'),
+ 'provider_url' => array('type' => 'text', 'description' => 'URL of this oEmbed provider'),
'width' => array('type' => 'int', 'description' => 'width of oEmbed resource when available'),
'height' => array('type' => 'int', 'description' => 'height of oEmbed resource when available'),
'html' => array('type' => 'text', 'description' => 'html representation of this oEmbed resource when applicable'),
- 'title' => array('type' => 'varchar', 'length' => 191, 'description' => 'title of oEmbed resource when available'),
- 'author_name' => array('type' => 'varchar', 'length' => 50, 'description' => 'author name for this oEmbed resource'),
- 'author_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'author URL for this oEmbed resource'),
- 'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL for this oEmbed resource when applicable (photo, link)'),
+ 'title' => array('type' => 'text', 'description' => 'title of oEmbed resource when available'),
+ 'author_name' => array('type' => 'text', 'description' => 'author name for this oEmbed resource'),
+ 'author_url' => array('type' => 'text', 'description' => 'author URL for this oEmbed resource'),
+ 'url' => array('type' => 'text', 'description' => 'URL for this oEmbed resource when applicable (photo, link)'),
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
),
'primary key' => array('file_id'),
function showContent()
{
$this->out->elementStart('p', array('class' => 'e-content answer-content'));
- if ($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());
if (!empty($this->answer)) {
$form = new QnashowanswerForm($this->out, $this->answer);
$reply->modified = $notice->created;
common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}");
$id = $reply->insert();
+ } catch (NoSuchUserException $e) {
+ common_log(LOG_WARNING, 'No local user found for Foreign_link with id: '.$mention->id);
} catch (NoResultException $e) {
- common_log(LOG_WARNING, 'No local user found for Foreign_link with local User id: '.$flink->user_id);
+ common_log(LOG_WARNING, 'No foreign link or profile found for Foreign_link with id: '.$mention->id);
}
}
}
// Parent notice was probably deleted.
$xs->text(": ");
}
- if (!empty($notice->rendered)) {
- $notice->rendered = str_replace("\t", "", $notice->rendered);
- $xs->raw($notice->rendered);
- } else {
- $xs->raw(common_render_content($notice->content, $notice));
- }
+ // FIXME: Why do we replace \t with ''? is it just to make it pretty? shouldn't whitespace be handled well...?
+ $xs->raw(str_replace("\t", "", $notice->getRendered()));
$xs->text(" ");
$xs->element('a', array(
'href'=>common_local_url('conversation',
common_log(LOG_INFO, 'Getting tags for notice #' . $notice->id);
$notice->saveTags();
$original = clone($notice);
- $notice->rendered = common_render_content($notice->content, $notice);
+ $notice->rendered = common_render_content($notice->content,
+ $notice->getProfile(),
+ $notice->hasParent() ? $notice->getParent() : null);
$result = $notice->update($original);
if (!$result) {
common_log_db_error($notice, 'UPDATE', __FILE__);
} catch (Exception $e) {
$filename = '(remote file or no filename)';
}
- print "About to PERMANENTLY delete file ($filename) ({$file->id}). Are you sure? [y/N] ";
+ print "About to PERMANENTLY delete file ($filename) with id=={$file->id}) AND its related notices. Are you sure? [y/N] ";
$response = fgets(STDIN);
if (strtolower(trim($response)) != 'y') {
print "Aborting.\n";
exit(1);
}
-$user = User::getKV('nickname', $nickname);
-
-if (!$user) {
- print "No such user '$nickname'.\n";
+try {
+ $user = User::getByNickname($nickname);
+ $user->setPassword($password);
+} catch (NoSuchUserException $e) {
+ print $e->getMessage();
exit(1);
}
-$original = clone($user);
-
-$user->password = common_munge_password($password, $user->getProfile());
-
-if (!$user->update($original)) {
- print "Error updating user '$nickname'.\n";
- exit(1);
-} else {
- print "Password for user '$nickname' updated.\n";
- exit(0);
-}
+print "Password for user '$nickname' updated.\n";
while ($notice->fetch()) {
$original = clone($notice);
- $notice->rendered = common_render_content($notice->content, $notice);
+ $notice->rendered = common_render_content($notice->content,
+ $notice->getProfile(),
+ $notice->hasParent() ? $notice->getParent() : null);
$notice->update($original);
}
$mem->query(sprintf('update group_member set uri = "%s" '.
'where profile_id = %d ' .
'and group_id = %d ',
- Group_member::newURI($mem->profile_id, $mem->group_id, $mem->created),
+ Group_member::newUri(Profile::getByID($mem->profile_id), User_group::getByID($mem->group_id), $mem->created),
$mem->profile_id,
$mem->group_id));
} catch (Exception $e) {