/** * Factory method for creating a new conversation. * * Use this for locally initiated conversations. Remote notices should * preferrably supply their own conversation URIs in the OStatus feed. * * @return Conversation the new conversation DO */ static function create(Notice $notice, $uri = null) { if (empty($notice->id)) { throw new ServerException(_('Tried to create conversation for not yet inserted notice')); } $conv = new Conversation(); $conv->created = common_sql_now(); $conv->id = $notice->id; $conv->uri = $uri ?: sprintf('%s%s=%d:%s=%s:%s=%x', TagURI::mint(), 'noticeId', $notice->id, 'objectType', 'thread', 'crc32', crc32($notice->content)); $result = $conv->insert(); if ($result === false) { common_log_db_error($conv, 'INSERT', __FILE__); throw new ServerException(_('Failed to create conversation for notice')); } return $conv; }
function asActivity() { $notice = Notice::staticGet('id', $this->notice_id); $profile = Profile::staticGet('id', $this->user_id); $act = new Activity(); $act->verb = ActivityVerb::FAVORITE; // FIXME: rationalize this with URL below $act->id = TagURI::mint('favor:%d:%d:%s', $profile->id, $notice->id, common_date_iso8601($this->modified)); $act->time = strtotime($this->modified); // TRANS: Activity title when marking a notice as favorite. $act->title = _("Favor"); // TRANS: Ntofication given when a user marks a notice as favorite. // TRANS: %1$s is a user nickname or full name, %2$s is a notice URI. $act->content = sprintf(_('%1$s marked notice %2$s as a favorite.'), $profile->getBestName(), $notice->uri); $act->actor = ActivityObject::fromProfile($profile); $act->objects[] = ActivityObject::fromNotice($notice); $url = common_local_url('AtomPubShowFavorite', array('profile' => $this->user_id, 'notice' => $this->notice_id)); $act->selfLink = $url; $act->editLink = $url; return $act; }
static function newURI($profile_id, $notice_id, $modified) { return TagURI::mint('favor:%d:%d:%s', $profile_id, $notice_id, common_date_iso8601($modified)); }
function onEndLeaveGroup($group, $profile) { // Only do this if config is enabled if (!$this->LeaveGroup) { return true; } if (!$profile->isLocal()) { return true; } // TRANS: Text for "left group" item in activity plugin. // TRANS: %1$s is a profile URL, %2$s is a profile name, // TRANS: %3$s is a group URL, %4$s is a group name. $rendered = sprintf(_m('<a href="%1$s">%2$s</a> left the group <a href="%3$s">%4$s</a>.'), $profile->getUrl(), $profile->getBestName(), $group->homeUrl(), $group->getBestName()); // TRANS: Text for "left group" item in activity plugin. // TRANS: %1$s is a profile name, %2$s is a profile URL, // TRANS: %3$s is a group name, %4$s is a group URL. $content = sprintf(_m('%1$s (%2$s) left the group %3$s (%4$s).'), $profile->getBestName(), $profile->getUrl(), $group->getBestName(), $group->homeUrl()); $uri = TagURI::mint('leave:%d:%d:%s', $profile->id, $group->id, common_date_iso8601(common_sql_now())); $notice = Notice::saveNew($profile->id, $content, ActivityPlugin::SOURCE, array('rendered' => $rendered, 'urls' => array(), 'groups' => array($group->id), 'uri' => $uri, 'verb' => ActivityVerb::LEAVE, 'object_type' => ActivityObject::GROUP)); return true; }
/** * Ping remote profiles with updates to this profile. * Salmon pings are queued for background processing. */ function onEndBroadcastProfile(Profile $profile) { $user = User::staticGet('id', $profile->id); // Find foreign accounts I'm subscribed to that support Salmon pings. // // @fixme we could run updates through the PuSH feed too, // in which case we can skip Salmon pings to folks who // are also subscribed to me. $sql = "SELECT * FROM ostatus_profile " . "WHERE profile_id IN " . "(SELECT subscribed FROM subscription WHERE subscriber=%d) " . "OR group_id IN " . "(SELECT group_id FROM group_member WHERE profile_id=%d)"; $oprofile = new Ostatus_profile(); $oprofile->query(sprintf($sql, $profile->id, $profile->id)); if ($oprofile->N == 0) { common_log(LOG_DEBUG, "No OStatus remote subscribees for {$profile->nickname}"); return true; } $act = new Activity(); $act->verb = ActivityVerb::UPDATE_PROFILE; $act->id = TagURI::mint('update-profile:%d:%s', $profile->id, common_date_iso8601(time())); $act->time = time(); // TRANS: Title for activity. $act->title = _m('Profile update'); // TRANS: Ping text for remote profile update through OStatus. // TRANS: %s is user that updated their profile. $act->content = sprintf(_m('%s has updated their profile page.'), $profile->getBestName()); $act->actor = ActivityObject::fromProfile($profile); $act->object = $act->actor; while ($oprofile->fetch()) { $oprofile->notifyDeferred($act, $profile); } return true; }
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)); }
/** * Factory method for creating a new conversation. * * Use this for locally initiated conversations. Remote notices should * preferrably supply their own conversation URIs in the OStatus feed. * * @return Conversation the new conversation DO */ static function create($uri = null, $created = null) { // Be aware that the Notice does not have an id yet since it's not inserted! $conv = new Conversation(); $conv->created = $created ?: common_sql_now(); $conv->uri = $uri ?: sprintf('%s%s=%s:%s=%s', TagURI::mint(), 'objectType', 'thread', 'nonce', common_random_hexstr(8)); // This insert throws exceptions on failure $conv->insert(); return $conv; }
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 Notice'); } // Profile::hasRepeated() abuses pkeyGet(), so we // have to clear manually if (!empty($this->repeat_of)) { $c = self::memcache(); if (!empty($c)) { $ck = self::multicacheKey('Notice', array('profile_id' => $this->profile_id, 'repeat_of' => $this->repeat_of)); $c->delete($ck); } } // Update possibly ID-dependent columns: URI, conversation // (now that INSERT has added the notice's local id) $orig = clone $this; $changed = false; // We can only get here if it's a local notice, since remote notices // should've bailed out earlier due to lacking a URI. if (empty($this->uri)) { $this->uri = sprintf('%s%s=%d:%s=%s', TagURI::mint(), 'noticeId', $this->id, 'objectType', $this->getObjectType(true)); $changed = true; } if ($changed && $this->update($orig) === false) { common_log_db_error($notice, 'UPDATE', __FILE__); // TRANS: Server exception thrown when a notice cannot be updated. throw new ServerException(_('Problem saving notice.')); } $this->blowOnInsert(); return $result; }
function asActivity() { $member = $this->getMember(); $group = $this->getGroup(); $act = new Activity(); $act->id = TagURI::mint('join:%d:%d:%s', $member->id, $group->id, common_date_iso8601($this->created)); $act->actor = ActivityObject::fromProfile($member); $act->verb = ActivityVerb::JOIN; $act->objects[] = ActivityObject::fromGroup($group); $act->time = strtotime($this->created); // TRANS: Activity title. $act->title = _("Join"); // TRANS: Success message for subscribe to group attempt through OStatus. // TRANS: %1$s is the member name, %2$s is the subscribed group's name. $act->content = sprintf(_('%1$s has joined group %2$s.'), $member->getBestName(), $group->getBestName()); return $act; }
/** * Convert a notice into an activity for export. * * @param User $cur Current user * * @return Activity activity object representing this Notice. */ function asActivity($cur) { $act = self::cacheGet(Cache::codeKey('notice:as-activity:' . $this->id)); if (!empty($act)) { return $act; } $act = new Activity(); if (Event::handle('StartNoticeAsActivity', array($this, &$act))) { $act->id = TagURI::mint("post:" . $this->id); $act->time = strtotime($this->created); $act->content = common_xml_safe_str($this->rendered); $profile = $this->getProfile(); $act->actor = ActivityObject::fromProfile($profile); $act->actor->extra[] = $profile->profileInfo($cur); $act->verb = $this->verb; if ($this->repeat_of) { $repeated = Notice::staticGet('id', $this->repeat_of); if (!empty($repeated)) { $act->objects[] = $repeated->asActivity($cur); } } else { $act->objects[] = ActivityObject::fromNotice($this); } // XXX: should this be handled by default processing for object entry? // Categories $tags = $this->getTags(); foreach ($tags as $tag) { $cat = new AtomCategory(); $cat->term = $tag; $act->categories[] = $cat; } // Enclosures // XXX: use Atom Media and/or File activity objects instead $attachments = $this->attachments(); foreach ($attachments as $attachment) { // Save local attachments if (!empty($attachment->filename)) { $act->attachments[] = ActivityObject::fromFile($attachment); } } $ctx = new ActivityContext(); if (!empty($this->reply_to)) { $reply = Notice::staticGet('id', $this->reply_to); if (!empty($reply)) { $ctx->replyToID = $reply->uri; $ctx->replyToUrl = $reply->bestUrl(); } } $ctx->location = $this->getLocation(); $conv = null; if (!empty($this->conversation)) { $conv = Conversation::staticGet('id', $this->conversation); if (!empty($conv)) { $ctx->conversation = $conv->uri; } } $reply_ids = $this->getReplies(); foreach ($reply_ids as $id) { $rprofile = Profile::staticGet('id', $id); if (!empty($rprofile)) { $ctx->attention[] = $rprofile->getUri(); $ctx->attentionType[$rprofile->getUri()] = ActivityObject::PERSON; } } $groups = $this->getGroups(); foreach ($groups as $group) { $ctx->attention[] = $group->getUri(); $ctx->attentionType[$group->getUri()] = ActivityObject::GROUP; } switch ($this->scope) { case Notice::PUBLIC_SCOPE: $ctx->attention[] = "http://activityschema.org/collection/public"; $ctx->attentionType["http://activityschema.org/collection/public"] = ActivityObject::COLLECTION; break; case Notice::FOLLOWER_SCOPE: $surl = common_local_url("subscribers", array('nickname' => $profile->nickname)); $ctx->attention[] = $surl; $ctx->attentionType[$surl] = ActivityObject::COLLECTION; break; } // XXX: deprecated; use ActivityVerb::SHARE instead $repeat = null; if (!empty($this->repeat_of)) { $repeat = Notice::staticGet('id', $this->repeat_of); if (!empty($repeat)) { $ctx->forwardID = $repeat->uri; $ctx->forwardUrl = $repeat->bestUrl(); } } $act->context = $ctx; $source = $this->getSource(); if ($source) { $act->generator = ActivityObject::fromNoticeSource($source); } // Source $atom_feed = $profile->getAtomFeed(); if (!empty($atom_feed)) { $act->source = new ActivitySource(); // XXX: we should store the actual feed ID $act->source->id = $atom_feed; // XXX: we should store the actual feed title $act->source->title = $profile->getBestName(); $act->source->links['alternate'] = $profile->profileurl; $act->source->links['self'] = $atom_feed; $act->source->icon = $profile->avatarUrl(AVATAR_PROFILE_SIZE); $notice = $profile->getCurrentNotice(); if (!empty($notice)) { $act->source->updated = self::utcDate($notice->created); } $user = User::staticGet('id', $profile->id); if (!empty($user)) { $act->source->links['license'] = common_config('license', 'url'); } } if ($this->isLocal()) { $act->selfLink = common_local_url('ApiStatusesShow', array('id' => $this->id, 'format' => 'atom')); $act->editLink = $act->selfLink; } Event::handle('EndNoticeAsActivity', array($this, &$act)); } self::cacheSet(Cache::codeKey('notice:as-activity:' . $this->id), $act); return $act; }
function asActivity() { $notice = Notice::staticGet('id', $this->notice_id); $profile = Profile::staticGet('id', $this->user_id); $act = new Activity(); $act->verb = ActivityVerb::FAVORITE; $act->id = TagURI::mint('favor:%d:%d:%s', $profile->id, $notice->id, common_date_iso8601($this->modified)); $act->time = strtotime($this->modified); // TRANS: Activity title when marking a notice as favorite. $act->title = _("Favor"); // TRANS: Ntofication given when a user marks a notice as favorite. // TRANS: %1$s is a user nickname or full name, %2$s is a notice URI. $act->content = sprintf(_('%1$s marked notice %2$s as a favorite.'), $profile->getBestName(), $notice->uri); $act->actor = ActivityObject::fromProfile($profile); $act->objects[] = ActivityObject::fromNotice($notice); return $act; }
function asActivity() { $act = new Activity(); if (Event::handle('StartMessageAsActivity', array($this, &$act))) { $act->id = TagURI::mint(sprintf('activity:message:%d', $this->id)); $act->time = strtotime($this->created); $act->link = $this->url; $profile = Profile::getKV('id', $this->from_profile); if (empty($profile)) { throw new Exception(sprintf("Sender profile not found: %d", $this->from_profile)); } $act->actor = $profile->asActivityObject(); $act->actor->extra[] = $profile->profileInfo(); $act->verb = ActivityVerb::POST; $act->objects[] = ActivityObject::fromMessage($this); $ctx = new ActivityContext(); $rprofile = Profile::getKV('id', $this->to_profile); if (empty($rprofile)) { throw new Exception(sprintf("Receiver profile not found: %d", $this->to_profile)); } $ctx->attention[$rprofile->getUri()] = ActivityObject::PERSON; $act->context = $ctx; $source = $this->getSource(); if ($source instanceof Notice_source) { $act->generator = ActivityObject::fromNoticeSource($source); } Event::handle('EndMessageAsActivity', array($this, &$act)); } return $act; }
static function newURI($subscriber_id, $subscribed_id, $created) { return TagURI::mint('follow:%d:%d:%s', $subscriber_id, $subscribed_id, common_date_iso8601($created)); }
static function newURI($profile_id, $group_id, $created) { return TagURI::mint('join:%d:%d:%s', $profile_id, $group_id, common_date_iso8601($created)); }
function registrationActivity() { $profile = $this->getProfile(); $service = new ActivityObject(); $service->type = ActivityObject::SERVICE; $service->title = common_config('site', 'name'); $service->link = common_root_url(); $service->id = $service->link; $act = new Activity(); $act->actor = ActivityObject::fromProfile($profile); $act->verb = ActivityVerb::JOIN; $act->objects[] = $service; $act->id = TagURI::mint('user:register:%d', $this->id); $act->time = strtotime($this->created); $act->title = _("Register"); $act->content = sprintf(_('%1$s joined %2$s.'), $profile->getBestName(), $service->title); return $act; }
/** * Send an Activity Streams notification to the remote Salmon endpoint, * if so configured. * * @param Profile $actor Actor who did the activity * @param string $verb Activity::SUBSCRIBE or Activity::JOIN * @param Object $object object of the action; must define asActivityNoun($tag) */ public function notify($actor, $verb, $object = null, $target = null) { if (!$actor instanceof Profile) { $type = gettype($actor); if ($type == 'object') { $type = get_class($actor); } // TRANS: Server exception. // TRANS: %1$s is the method name the exception occured in, %2$s is the actor type. throw new ServerException(sprintf(_m('Invalid actor passed to %1$s: %2$s.'), __METHOD__, $type)); } if ($object == null) { $object = $this; } if ($this->salmonuri) { $text = 'update'; $id = TagURI::mint('%s:%s:%s', $verb, $actor->getURI(), common_date_iso8601(time())); // @todo FIXME: Consolidate all these NS settings somewhere. $attributes = array('xmlns' => Activity::ATOM, 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/', 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0', 'xmlns:georss' => 'http://www.georss.org/georss', 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0', 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0', 'xmlns:media' => 'http://purl.org/syndication/atommedia'); $entry = new XMLStringer(); $entry->elementStart('entry', $attributes); $entry->element('id', null, $id); $entry->element('title', null, $text); $entry->element('summary', null, $text); $entry->element('published', null, common_date_w3dtf(common_sql_now())); $entry->element('activity:verb', null, $verb); $entry->raw($actor->asAtomAuthor()); $entry->raw($actor->asActivityActor()); $entry->raw($object->asActivityNoun('object')); if ($target != null) { $entry->raw($target->asActivityNoun('target')); } $entry->elementEnd('entry'); $xml = $entry->getString(); common_log(LOG_INFO, "Posting to Salmon endpoint {$this->salmonuri}: {$xml}"); $salmon = new Salmon(); // ? return $salmon->post($this->salmonuri, $xml, $actor); } return false; }
function asActivity() { $subscriber = Profile::staticGet('id', $this->subscriber); $subscribed = Profile::staticGet('id', $this->subscribed); $act = new Activity(); $act->verb = ActivityVerb::FOLLOW; // XXX: rationalize this with the URL $act->id = TagURI::mint('follow:%d:%d:%s', $subscriber->id, $subscribed->id, common_date_iso8601($this->created)); $act->time = strtotime($this->created); // TRANS: Activity title when subscribing to another person. $act->title = _m('TITLE', 'Follow'); // TRANS: Notification given when one person starts following another. // TRANS: %1$s is the subscriber, %2$s is the subscribed. $act->content = sprintf(_('%1$s is now following %2$s.'), $subscriber->getBestName(), $subscribed->getBestName()); $act->actor = ActivityObject::fromProfile($subscriber); $act->objects[] = ActivityObject::fromProfile($subscribed); $url = common_local_url('AtomPubShowSubscription', array('subscriber' => $subscriber->id, 'subscribed' => $subscribed->id)); $act->selfLink = $url; $act->editLink = $url; return $act; }
/** * Delete any notifications tied to deleted notices and un-repeats * * @return boolean hook flag */ public function onNoticeDeleteRelated($notice) { $notif = new QvitterNotification(); // unrepeats if ($notice->isRepeat()) { $repeated_notice = Notice::getKV('id', $notice->repeat_of); $notif->notice_id = $repeated_notice->id; $notif->from_profile_id = $notice->profile_id; } else { $notif->notice_id = $notice->id; } $notif->delete(); // outputs an activity notice that this notice was deleted $profile = $notice->getProfile(); // don't delete if this is a user is being deleted // because that creates an infinite loop of deleting and creating notices... $user_is_deleted = false; $user = User::getKV('id', $profile->id); if ($user instanceof User && $user->hasRole(Profile_role::DELETED)) { $user_is_deleted = true; } if (!$user_is_deleted && class_exists('StatusNet') && !array_key_exists('ActivityModeration', StatusNet::getActivePlugins())) { $rendered = sprintf(_m('<a href="%1$s">%2$s</a> deleted notice <a href="%3$s">{{%4$s}}</a>.'), htmlspecialchars($profile->getUrl()), htmlspecialchars($profile->getBestName()), htmlspecialchars($notice->getUrl()), htmlspecialchars($notice->uri)); $text = sprintf(_m('%1$s deleted notice {{%2$s}}.'), $profile->getBestName(), $notice->uri); $uri = TagURI::mint('delete-notice:%d:%d:%s', $notice->profile_id, $notice->id, common_date_iso8601(common_sql_now())); $notice = Notice::saveNew($notice->profile_id, $text, ActivityPlugin::SOURCE, array('rendered' => $rendered, 'urls' => array(), 'uri' => $uri, 'verb' => 'qvitter-delete-notice', 'object_type' => ActivityObject::ACTIVITY)); } return true; }
static function fromMessage(Message $message) { $object = new ActivityObject(); if (Event::handle('StartActivityObjectFromMessage', array($message, &$object))) { $object->type = ActivityObject::NOTE; $object->id = $message->uri ? $message->uri : ($message->url ? $message->url : TagURI::mint(sprintf("message:%d", $message->id))); $object->content = $message->rendered; $object->date = $message->created; if ($message->url) { $object->link = $message->url; } else { $object->link = common_local_url('showmessage', array('message' => $message->id)); } $object->extra[] = array('status_net', array('message_id' => $message->id)); Event::handle('EndActivityObjectFromMessage', array($message, &$object)); } return $object; }
/** * Send an Activity Streams notification to the remote Salmon endpoint, * if so configured. * * @param Profile $actor Actor who did the activity * @param string $verb Activity::SUBSCRIBE or Activity::JOIN * @param Object $object object of the action; must define asActivityNoun($tag) */ public function notify(Profile $actor, $verb, $object = null, $target = null) { if ($object == null) { $object = $this; } if (empty($this->salmonuri)) { return false; } $text = 'update'; $id = TagURI::mint('%s:%s:%s', $verb, $actor->getURI(), common_date_iso8601(time())); // @todo FIXME: Consolidate all these NS settings somewhere. $attributes = array('xmlns' => Activity::ATOM, 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/', 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0', 'xmlns:georss' => 'http://www.georss.org/georss', 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0', 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0', 'xmlns:media' => 'http://purl.org/syndication/atommedia'); $entry = new XMLStringer(); $entry->elementStart('entry', $attributes); $entry->element('id', null, $id); $entry->element('title', null, $text); $entry->element('summary', null, $text); $entry->element('published', null, common_date_w3dtf(common_sql_now())); $entry->element('activity:verb', null, $verb); $entry->raw($actor->asAtomAuthor()); $entry->raw($actor->asActivityActor()); $entry->raw($object->asActivityNoun('object')); if ($target != null) { $entry->raw($target->asActivityNoun('target')); } $entry->elementEnd('entry'); $xml = $entry->getString(); common_log(LOG_INFO, "Posting to Salmon endpoint {$this->salmonuri}: {$xml}"); Salmon::post($this->salmonuri, $xml, $actor); }
function asActivity() { $subscriber = Profile::staticGet('id', $this->subscriber); $subscribed = Profile::staticGet('id', $this->subscribed); $act = new Activity(); $act->verb = ActivityVerb::FOLLOW; $act->id = TagURI::mint('follow:%d:%d:%s', $subscriber->id, $subscribed->id, common_date_iso8601($this->created)); $act->time = strtotime($this->created); // TRANS: Activity tile when subscribing to another person. $act->title = _("Follow"); // TRANS: Notification given when one person starts following another. // TRANS: %1$s is the subscriber, %2$s is the subscribed. $act->content = sprintf(_('%1$s is now following %2$s.'), $subscriber->getBestName(), $subscribed->getBestName()); $act->actor = ActivityObject::fromProfile($subscriber); $act->objects[] = ActivityObject::fromProfile($subscribed); return $act; }