/**
  * Show the item
  *
  * @return void
  */
 function show()
 {
     $group = $this->gm->getGroup();
     $sender = $this->gm->getSender();
     $this->out->elementStart('li', array('class' => 'hentry notice message group-message', 'id' => 'message-' . $this->gm->id));
     $this->out->elementStart('div', 'entry-title');
     $this->out->elementStart('span', 'vcard author');
     $this->out->elementStart('a', array('href' => $sender->profileurl, 'class' => 'url'));
     $avatar = $sender->getAvatar(AVATAR_STREAM_SIZE);
     $this->out->element('img', array('src' => $avatar ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE), 'width' => AVATAR_STREAM_SIZE, 'height' => AVATAR_STREAM_SIZE, 'class' => 'photo avatar', 'alt' => $sender->getBestName()));
     $this->out->element('span', array('class' => 'nickname fn'), $sender->nickname);
     $this->out->elementEnd('a');
     $this->out->elementEnd('span');
     $this->out->elementStart('p', array('class' => 'entry-content message-content'));
     $this->out->raw($this->gm->rendered);
     $this->out->elementEnd('p');
     $this->out->elementEnd('div');
     $this->out->elementStart('div', 'entry-content');
     $this->out->elementStart('a', array('rel' => 'bookmark', 'class' => 'timestamp', 'href' => $this->gm->url));
     $dt = common_date_iso8601($this->gm->created);
     $this->out->element('abbr', array('class' => 'published', 'title' => $dt), common_date_string($this->gm->created));
     $this->out->elementEnd('a');
     $this->out->elementEnd('div');
     $this->out->elementEnd('li');
 }
 /**
  * Show the widget
  *
  * @return void
  */
 function show()
 {
     $this->out->elementStart('li', array('class' => 'h-entry notice', 'id' => 'message-' . $this->message->id));
     $profile = $this->getMessageProfile();
     $this->out->elementStart('a', array('href' => $profile->profileurl, 'class' => 'p-author'));
     $avatarUrl = $profile->avatarUrl(AVATAR_STREAM_SIZE);
     $this->out->element('img', array('src' => $avatarUrl, 'class' => 'avatar u-photo', 'width' => AVATAR_STREAM_SIZE, 'height' => AVATAR_STREAM_SIZE, 'alt' => $profile->getBestName()));
     $this->out->element('span', array('class' => 'nickname fn'), $profile->getNickname());
     $this->out->elementEnd('a');
     // FIXME: URL, image, video, audio
     $this->out->elementStart('div', array('class' => 'e-content'));
     $this->out->raw($this->message->rendered);
     $this->out->elementEnd('div');
     $messageurl = common_local_url('showmessage', array('message' => $this->message->id));
     // XXX: we need to figure this out better. Is this right?
     if (strcmp($this->message->uri, $messageurl) != 0 && preg_match('/^http/', $this->message->uri)) {
         $messageurl = $this->message->uri;
     }
     $this->out->elementStart('div', 'entry-metadata');
     $this->out->elementStart('a', array('rel' => 'bookmark', 'class' => 'timestamp', 'href' => $messageurl));
     $dt = common_date_iso8601($this->message->created);
     $this->out->element('time', array('class' => 'dt-published', 'datetime' => common_date_iso8601($this->message->created), 'title' => common_exact_date($this->message->created)), common_date_string($this->message->created));
     $this->out->elementEnd('a');
     if ($this->message->source) {
         $this->out->elementStart('span', 'source');
         // FIXME: bad i18n. Device should be a parameter (from %s).
         // TRANS: Followed by notice source (usually the client used to send the notice).
         $this->out->text(_('from'));
         $this->showSource($this->message->source);
         $this->out->elementEnd('span');
     }
     $this->out->elementEnd('div');
     $this->out->elementEnd('li');
 }
Example #3
0
 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;
 }
 /**
  * Handle the request
  *
  * Return some Twitter-ish data about API limits
  *
  * @param array $args $_REQUEST data (unused)
  *
  * @return void
  */
 protected function handle()
 {
     parent::handle();
     if (!in_array($this->format, array('xml', 'json'))) {
         $this->clientError(_('API method not found.'), 404, $this->format);
     }
     $reset = new DateTime();
     $reset->modify('+1 hour');
     $this->initDocument($this->format);
     if ($this->format == 'xml') {
         $this->elementStart('hash');
         $this->element('remaining-hits', array('type' => 'integer'), 150);
         $this->element('hourly-limit', array('type' => 'integer'), 150);
         $this->element('reset-time', array('type' => 'datetime'), common_date_iso8601($reset->format('r')));
         $this->element('reset_time_in_seconds', array('type' => 'integer'), strtotime('+1 hour'));
         $this->elementEnd('hash');
     } elseif ($this->format == 'json') {
         $out = array('reset_time_in_seconds' => strtotime('+1 hour'), 'remaining_hits' => 150, 'hourly_limit' => 150, 'reset_time' => common_date_rfc2822($reset->format('r')));
         print json_encode($out);
     }
     $this->endDocument($this->format);
 }
Example #5
0
 function showNoticeLink()
 {
     $noticeurl = common_local_url('shownotice', array('notice' => $this->notice->id));
     // XXX: we need to figure this out better. Is this right?
     if (strcmp($this->notice->uri, $noticeurl) != 0 && preg_match('/^http/', $this->notice->uri)) {
         $noticeurl = $this->notice->uri;
     }
     $this->out->elementStart('dl', 'timestamp');
     $this->out->element('dt', null, _('Published'));
     $this->out->elementStart('dd', null);
     $this->out->elementStart('a', array('rel' => 'bookmark', 'href' => $noticeurl));
     $dt = common_date_iso8601($this->notice->created);
     $this->out->element('abbr', array('class' => 'published', 'title' => $dt), common_date_string($this->notice->created));
     $this->out->elementEnd('a');
     $this->out->elementEnd('dd');
     $this->out->elementEnd('dl');
 }
Example #6
0
 /**
  * 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);
 }
Example #7
0
 /**
  * Shows a list of direct messages as Atom entries
  *
  * @return void
  */
 function showAtomDirectMessages()
 {
     $this->initDocument('atom');
     $this->element('title', null, $this->title);
     $this->element('id', null, $this->id);
     $selfuri = common_root_url() . 'api/direct_messages.atom';
     $this->element('link', array('href' => $this->link, 'rel' => 'alternate', 'type' => 'text/html'), null);
     $this->element('link', array('href' => $this->selfuri_base . '.atom', 'rel' => 'self', 'type' => 'application/atom+xml'), null);
     $this->element('updated', null, common_date_iso8601('now'));
     $this->element('subtitle', null, $this->subtitle);
     foreach ($this->messages as $m) {
         $entry = $this->rssDirectMessageArray($m);
         $this->showTwitterAtomEntry($entry);
     }
     $this->endDocument('atom');
 }
 function showContent()
 {
     $notice = $this->nli->notice;
     $out = $this->nli->out;
     $profile = $notice->getProfile();
     $event = Happening::fromNotice($notice);
     if (empty($event)) {
         // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
         $out->element('p', null, _m('Deleted.'));
         return;
     }
     $out->elementStart('div', 'vevent event');
     // VEVENT IN
     $out->elementStart('h3');
     // VEVENT/H3 IN
     if (!empty($event->url)) {
         $out->element('a', array('href' => $event->url, 'class' => 'event-title entry-title summary'), $event->title);
     } else {
         $out->text($event->title);
     }
     $out->elementEnd('h3');
     // VEVENT/H3 OUT
     $startDate = strftime("%x", strtotime($event->start_time));
     $startTime = strftime("%R", strtotime($event->start_time));
     $endDate = strftime("%x", strtotime($event->end_time));
     $endTime = strftime("%R", strtotime($event->end_time));
     // FIXME: better dates
     $out->elementStart('div', 'event-times');
     // VEVENT/EVENT-TIMES IN
     // TRANS: Field label for event description.
     $out->element('strong', null, _m('Time:'));
     $out->element('abbr', array('class' => 'dtstart', 'title' => common_date_iso8601($event->start_time)), $startDate . ' ' . $startTime);
     $out->text(' - ');
     if ($startDate == $endDate) {
         $out->element('span', array('class' => 'dtend', 'title' => common_date_iso8601($event->end_time)), $endTime);
     } else {
         $out->element('span', array('class' => 'dtend', 'title' => common_date_iso8601($event->end_time)), $endDate . ' ' . $endTime);
     }
     $out->elementEnd('div');
     // VEVENT/EVENT-TIMES OUT
     if (!empty($event->location)) {
         $out->elementStart('div', 'event-location');
         // TRANS: Field label for event description.
         $out->element('strong', null, _m('Location:'));
         $out->element('span', 'location', $event->location);
         $out->elementEnd('div');
     }
     if (!empty($event->description)) {
         $out->elementStart('div', 'event-description');
         // TRANS: Field label for event description.
         $out->element('strong', null, _m('Description:'));
         $out->element('span', 'description', $event->description);
         $out->elementEnd('div');
     }
     $rsvps = $event->getRSVPs();
     $out->elementStart('div', 'event-rsvps');
     // TRANS: Field label for event description.
     $out->element('strong', null, _m('Attending:'));
     $out->element('span', 'event-rsvps', sprintf(_m('Yes: %1$d No: %2$d Maybe: %3$d'), count($rsvps[RSVP::POSITIVE]), count($rsvps[RSVP::NEGATIVE]), count($rsvps[RSVP::POSSIBLE])));
     $out->elementEnd('div');
     $user = common_current_user();
     if (!empty($user)) {
         $rsvp = $event->getRSVP($user->getProfile());
         if (empty($rsvp)) {
             $form = new RSVPForm($event, $out);
         } else {
             $form = new CancelRSVPForm($rsvp, $out);
         }
         $form->show();
     }
     $out->elementEnd('div');
     // vevent out
 }
Example #9
0
 protected function showEvent(Notice $stored, HTMLOutputter $out, Profile $scoped = null)
 {
     $profile = $stored->getProfile();
     $event = Happening::fromNotice($stored);
     if (!$event instanceof Happening) {
         // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
         $out->element('p', null, _m('Deleted.'));
         return;
     }
     $out->elementStart('div', 'h-event');
     $out->elementStart('h3', 'p-summary p-name');
     try {
         $out->element('a', array('href' => $event->getUrl()), $event->title);
     } catch (InvalidUrlException $e) {
         $out->text($event->title);
     }
     $out->elementEnd('h3');
     $now = new DateTime();
     $startDate = new DateTime($event->start_time);
     $endDate = new DateTime($event->end_time);
     $userTz = new DateTimeZone(common_timezone());
     // Localize the time for the observer
     $now->setTimeZone($userTz);
     $startDate->setTimezone($userTz);
     $endDate->setTimezone($userTz);
     $thisYear = $now->format('Y');
     $startYear = $startDate->format('Y');
     $endYear = $endDate->format('Y');
     $dateFmt = 'D, F j, ';
     // e.g.: Mon, Aug 31
     if ($startYear != $thisYear || $endYear != $thisYear) {
         $dateFmt .= 'Y,';
         // append year if we need to think about years
     }
     $startDateStr = $startDate->format($dateFmt);
     $endDateStr = $endDate->format($dateFmt);
     $timeFmt = 'g:ia';
     $startTimeStr = $startDate->format($timeFmt);
     $endTimeStr = $endDate->format("{$timeFmt} (T)");
     $out->elementStart('div', 'event-times');
     // VEVENT/EVENT-TIMES IN
     // TRANS: Field label for event description.
     $out->element('strong', null, _m('Time:'));
     $out->element('time', array('class' => 'dt-start', 'datetime' => common_date_iso8601($event->start_time)), $startDateStr . ' ' . $startTimeStr);
     $out->text(' โ€“ ');
     $out->element('time', array('class' => 'dt-end', 'datetime' => common_date_iso8601($event->end_time)), $startDateStr != $endDateStr ? "{$endDateStr} {$endTimeStr}" : $endTimeStr);
     $out->elementEnd('div');
     // VEVENT/EVENT-TIMES OUT
     if (!empty($event->location)) {
         $out->elementStart('div', 'event-location');
         // TRANS: Field label for event description.
         $out->element('strong', null, _m('Location:'));
         $out->element('span', 'p-location', $event->location);
         $out->elementEnd('div');
     }
     if (!empty($event->description)) {
         $out->elementStart('div', 'event-description');
         // TRANS: Field label for event description.
         $out->element('strong', null, _m('Description:'));
         $out->element('div', 'p-description', $event->description);
         $out->elementEnd('div');
     }
     $rsvps = $event->getRSVPs();
     $out->elementStart('div', 'event-rsvps');
     // TRANS: Field label for event description.
     $out->element('strong', null, _m('Attending:'));
     $out->elementStart('ul', 'attending-list');
     foreach ($rsvps as $verb => $responses) {
         $out->elementStart('li', 'rsvp-list');
         switch ($verb) {
             case RSVP::POSITIVE:
                 $out->text(_('Yes:'));
                 break;
             case RSVP::NEGATIVE:
                 $out->text(_('No:'));
                 break;
             case RSVP::POSSIBLE:
                 $out->text(_('Maybe:'));
                 break;
         }
         $ids = array();
         foreach ($responses as $response) {
             $ids[] = $response->profile_id;
         }
         $ids = array_slice($ids, 0, ProfileMiniList::MAX_PROFILES + 1);
         $minilist = new ProfileMiniList(Profile::multiGet('id', $ids), $out);
         $minilist->show();
         $out->elementEnd('li');
     }
     $out->elementEnd('ul');
     $out->elementEnd('div');
     if ($scoped instanceof Profile) {
         $rsvp = $event->getRSVP($scoped);
         if (empty($rsvp)) {
             $form = new RSVPForm($event, $out);
         } else {
             $form = new CancelRSVPForm($rsvp, $out);
         }
         $form->show();
     }
     $out->elementEnd('div');
 }
Example #10
0
 /**
  * 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;
 }
Example #11
0
 static function newURI($profile_id, $notice_id, $modified)
 {
     return TagURI::mint('favor:%d:%d:%s', $profile_id, $notice_id, common_date_iso8601($modified));
 }
Example #12
0
 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;
 }
Example #13
0
 /**
  * show the link to the main page for the notice
  *
  * Displays a link to the page for a notice, with "relative" time. Tries to
  * get remote notice URLs correct, but doesn't always succeed.
  *
  * @return void
  */
 function showNoticeLink()
 {
     $noticeurl = $this->notice->bestUrl();
     // above should always return an URL
     assert(!empty($noticeurl));
     $dt = common_date_iso8601($this->notice->created);
     $this->out->element('div', array('class' => 'published', 'title' => $dt), common_date_string($this->notice->created));
 }
Example #14
0
 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;
 }
Example #15
0
 /**
  * show the link to the main page for the notice
  *
  * Displays a local link to the rendered notice, with "relative" time.
  *
  * @return void
  */
 function showNoticeLink()
 {
     $this->out->elementStart('a', array('rel' => 'bookmark', 'class' => 'timestamp', 'href' => Conversation::getUrlFromNotice($this->notice)));
     $this->out->element('time', array('class' => 'dt-published', 'datetime' => common_date_iso8601($this->notice->created), 'title' => common_exact_date($this->notice->created)), common_date_string($this->notice->created));
     $this->out->elementEnd('a');
 }
Example #16
0
 function showContent()
 {
     $notice = $this->nli->notice;
     $out = $this->nli->out;
     $profile = $notice->getProfile();
     $event = Happening::fromNotice($notice);
     if (empty($event)) {
         // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
         $out->element('p', null, _m('Deleted.'));
         return;
     }
     $out->elementStart('div', 'vevent event');
     // VEVENT IN
     $out->elementStart('h3');
     // VEVENT/H3 IN
     if (!empty($event->url)) {
         $out->element('a', array('href' => $event->url, 'class' => 'event-title entry-title summary'), $event->title);
     } else {
         $out->text($event->title);
     }
     $out->elementEnd('h3');
     // VEVENT/H3 OUT
     $now = new DateTime();
     $startDate = new DateTime($event->start_time);
     $endDate = new DateTime($event->end_time);
     $userTz = new DateTimeZone(common_timezone());
     // Localize the time for the observer
     $now->setTimeZone($userTz);
     $startDate->setTimezone($userTz);
     $endDate->setTimezone($userTz);
     $thisYear = $now->format('Y');
     $startYear = $startDate->format('Y');
     $endYear = $endDate->format('Y');
     $dateFmt = 'D, F j, ';
     // e.g.: Mon, Aug 31
     if ($startYear != $thisYear || $endYear != $thisYear) {
         $dateFmt .= 'Y,';
         // append year if we need to think about years
     }
     $startDateStr = $startDate->format($dateFmt);
     $endDateStr = $endDate->format($dateFmt);
     $timeFmt = 'g:ia';
     $startTimeStr = $startDate->format($timeFmt);
     $endTimeStr = $endDate->format("{$timeFmt} (T)");
     $out->elementStart('div', 'event-times');
     // VEVENT/EVENT-TIMES IN
     // TRANS: Field label for event description.
     $out->element('strong', null, _m('Time:'));
     $out->element('abbr', array('class' => 'dtstart', 'title' => common_date_iso8601($event->start_time)), $startDateStr . ' ' . $startTimeStr);
     $out->text(' โ€“ ');
     if ($startDateStr == $endDateStr) {
         $out->element('span', array('class' => 'dtend', 'title' => common_date_iso8601($event->end_time)), $endTimeStr);
     } else {
         $out->element('span', array('class' => 'dtend', 'title' => common_date_iso8601($event->end_time)), $endDateStr . ' ' . $endTimeStr);
     }
     $out->elementEnd('div');
     // VEVENT/EVENT-TIMES OUT
     if (!empty($event->location)) {
         $out->elementStart('div', 'event-location');
         // TRANS: Field label for event description.
         $out->element('strong', null, _m('Location:'));
         $out->element('span', 'location', $event->location);
         $out->elementEnd('div');
     }
     if (!empty($event->description)) {
         $out->elementStart('div', 'event-description');
         // TRANS: Field label for event description.
         $out->element('strong', null, _m('Description:'));
         $out->element('span', 'description', $event->description);
         $out->elementEnd('div');
     }
     $rsvps = $event->getRSVPs();
     $out->elementStart('div', 'event-rsvps');
     // TRANS: Field label for event description.
     $out->text(_m('Attending:'));
     $out->elementStart('ul', 'attending-list');
     foreach ($rsvps as $verb => $responses) {
         $out->elementStart('li', 'rsvp-list');
         switch ($verb) {
             case RSVP::POSITIVE:
                 $out->text(_m('Yes:'));
                 break;
             case RSVP::NEGATIVE:
                 $out->text(_('No:'));
                 break;
             case RSVP::POSSIBLE:
                 $out->text(_m('Maybe:'));
                 break;
         }
         $ids = array();
         foreach ($responses as $response) {
             $ids[] = $response->profile_id;
         }
         $ids = array_slice($ids, 0, ProfileMiniList::MAX_PROFILES + 1);
         $profiles = Profile::pivotGet('id', $ids);
         $profile = new ArrayWrapper(array_values($profiles));
         $minilist = new ProfileMiniList($profile, $out);
         $minilist->show();
         $out->elementEnd('li');
     }
     $out->elementEnd('ul');
     $out->elementEnd('div');
     $user = common_current_user();
     if (!empty($user)) {
         $rsvp = $event->getRSVP($user->getProfile());
         if (empty($rsvp)) {
             $form = new RSVPForm($event, $out);
         } else {
             $form = new CancelRSVPForm($rsvp, $out);
         }
         $form->show();
     }
     $out->elementEnd('div');
     // vevent out
 }
Example #17
0
 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;
 }
 /**
  * Send a summary email to the user
  *
  * @param mixed $object
  * @return boolean true on success, false on failure
  */
 function handle($user_id)
 {
     // Skip if they've asked not to get summaries
     $ess = Email_summary_status::staticGet('user_id', $user_id);
     if (!empty($ess) && !$ess->send_summary) {
         common_log(LOG_INFO, sprintf('Not sending email summary for user %s by request.', $user_id));
         return true;
     }
     $since_id = null;
     if (!empty($ess)) {
         $since_id = $ess->last_summary_id;
     }
     $user = User::staticGet('id', $user_id);
     if (empty($user)) {
         common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no such user.', $user_id));
         return true;
     }
     if (empty($user->email)) {
         common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no email address.', $user_id));
         return true;
     }
     $profile = $user->getProfile();
     if (empty($profile)) {
         common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no profile.', $user_id));
         return true;
     }
     $stream = new InboxNoticeStream($user, $user->getProfile());
     $notice = $stream->getNotices(0, self::MAX_NOTICES, $since_id);
     if (empty($notice) || $notice->N == 0) {
         common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no notices.', $user_id));
         return true;
     }
     // XXX: This is risky fingerpoken in der objektvars, but I didn't feel like
     // figuring out a better way. -ESP
     $new_top = null;
     if ($notice instanceof ArrayWrapper) {
         $new_top = $notice->_items[0]->id;
     }
     // TRANS: Subject for e-mail.
     $subject = sprintf(_m('Your latest updates from %s'), common_config('site', 'name'));
     $out = new XMLStringer(true);
     $out->elementStart('html');
     $out->elementStart('head');
     $out->element('title', null, $subject);
     $out->elementEnd('head');
     $out->elementStart('body');
     $out->elementStart('div', array('width' => '100%', 'style' => 'background-color: #ffffff; border: 4px solid #4c609a; padding: 10px;'));
     $out->elementStart('div', array('style' => 'color: #ffffff; background-color: #4c609a; font-weight: bold; margin-bottom: 10px; padding: 4px;'));
     // TRANS: Text in e-mail summary.
     // TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
     $out->raw(sprintf(_m('Recent updates from %1$s for %2$s:'), common_config('site', 'name'), $profile->getBestName()));
     $out->elementEnd('div');
     $out->elementStart('table', array('width' => '550px', 'style' => 'border: none; border-collapse: collapse;', 'cellpadding' => '6'));
     while ($notice->fetch()) {
         $profile = Profile::staticGet('id', $notice->profile_id);
         if (empty($profile)) {
             continue;
         }
         $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
         $out->elementStart('tr');
         $out->elementStart('td', array('width' => AVATAR_STREAM_SIZE, 'height' => AVATAR_STREAM_SIZE, 'align' => 'left', 'valign' => 'top', 'style' => 'border-bottom: 1px dotted #C5CEE3; padding: 10px 6px 10px 6px;'));
         $out->element('img', array('src' => $avatar ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE), 'width' => AVATAR_STREAM_SIZE, 'height' => AVATAR_STREAM_SIZE, 'alt' => $profile->getBestName()));
         $out->elementEnd('td');
         $out->elementStart('td', array('align' => 'left', 'valign' => 'top', 'style' => 'border-bottom: 1px dotted #C5CEE3; padding: 10px 6px 10px 6px;'));
         $out->element('a', array('href' => $profile->profileurl), $profile->nickname);
         $out->text(' ');
         $out->raw($notice->rendered);
         $out->elementStart('div', array('style' => 'font-size: 0.8em; padding-top: 4px;'));
         $noticeurl = $notice->bestUrl();
         // above should always return an URL
         assert(!empty($noticeurl));
         $out->elementStart('a', array('rel' => 'bookmark', 'href' => $noticeurl));
         $dt = common_date_iso8601($notice->created);
         $out->element('abbr', array('style' => 'border-bottom: none;', 'title' => $dt), common_date_string($notice->created));
         $out->elementEnd('a');
         if ($notice->hasConversation()) {
             $conv = Conversation::staticGet('id', $notice->conversation);
             $convurl = $conv->uri;
             if (!empty($convurl)) {
                 $out->text(' ');
                 $out->element('a', array('href' => $convurl . '#notice-' . $notice->id), _m('in context'));
             }
         }
         $out->elementEnd('div');
         $out->elementEnd('td');
         $out->elementEnd('tr');
     }
     $out->elementEnd('table');
     // TRANS: Link text for link to e-mail settings.
     // TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
     $out->raw("<p>" . sprintf(_m('<a href="%1$s">change your email settings for %2$s</a>'), common_local_url('emailsettings'), common_config('site', 'name')) . "</p>");
     $out->elementEnd('div');
     $out->elementEnd('body');
     $out->elementEnd('html');
     $body = $out->getString();
     // FIXME: do something for people who don't like HTML email
     mail_to_user($user, $subject, $body, array('Content-Type' => 'text/html; charset=utf-8', 'Mime-Version' => '1.0'));
     if (empty($ess)) {
         $ess = new Email_summary_status();
         $ess->user_id = $user_id;
         $ess->created = common_sql_now();
         $ess->last_summary_id = $new_top;
         $ess->modified = common_sql_now();
         $ess->insert();
     } else {
         $orig = clone $ess;
         $ess->last_summary_id = $new_top;
         $ess->modified = common_sql_now();
         $ess->update($orig);
     }
     return true;
 }
Example #19
0
 static function newURI($profile_id, $group_id, $created)
 {
     return TagURI::mint('join:%d:%d:%s', $profile_id, $group_id, common_date_iso8601($created));
 }
Example #20
0
 static function saveNew($profile, $start_time, $end_time, $title, $location, $description, $url, $options = array())
 {
     if (array_key_exists('uri', $options)) {
         $other = Happening::getKV('uri', $options['uri']);
         if (!empty($other)) {
             // TRANS: Client exception thrown when trying to create an event that already exists.
             throw new ClientException(_m('Event already exists.'));
         }
     }
     $ev = new Happening();
     $ev->id = UUID::gen();
     $ev->profile_id = $profile->id;
     $ev->start_time = common_sql_date($start_time);
     $ev->end_time = common_sql_date($end_time);
     $ev->title = $title;
     $ev->location = $location;
     $ev->description = $description;
     $ev->url = $url;
     if (array_key_exists('created', $options)) {
         $ev->created = $options['created'];
     } else {
         $ev->created = common_sql_now();
     }
     if (array_key_exists('uri', $options)) {
         $ev->uri = $options['uri'];
     } else {
         $ev->uri = common_local_url('showevent', array('id' => $ev->id));
     }
     $ev->insert();
     // XXX: does this get truncated?
     // TRANS: Event description. %1$s is a title, %2$s is start time, %3$s is end time,
     // TRANS: %4$s is location, %5$s is a description.
     $content = sprintf(_m('"%1$s" %2$s - %3$s (%4$s): %5$s'), $title, common_exact_date($ev->start_time), common_exact_date($ev->end_time), $location, $description);
     // TRANS: Rendered microformats2 tagged event description.
     // TRANS: %1$s is a title, %2$s is start time, %3$s is start time,
     // TRANS: %4$s is end time, %5$s is end time, %6$s is location, %7$s is description.
     // TRANS: Class names should not be translated.
     $rendered = sprintf(_m('<div class="h-event">' . '<p class="p-name p-summary">%1$s</p> ' . '<time class="dt-start" datetime="%2$s">%3$s</time> - ' . '<time class="dt-end" datetime="%4$s">%5$s</time> ' . '(<span class="p-location">%6$s</span>): ' . '<div class="p-description">%7$s</div> ' . '</div>'), htmlspecialchars($title), htmlspecialchars(common_date_iso8601($ev->start_time)), htmlspecialchars(common_exact_date($ev->start_time)), htmlspecialchars(common_date_iso8601($ev->end_time)), htmlspecialchars(common_exact_date($ev->end_time)), htmlspecialchars($location), htmlspecialchars($description));
     $options = array_merge(array('object_type' => Happening::OBJECT_TYPE), $options);
     if (!array_key_exists('uri', $options)) {
         $options['uri'] = $ev->uri;
     }
     if (!empty($url)) {
         $options['urls'] = array($url);
     }
     $saved = Notice::saveNew($profile->id, $content, array_key_exists('source', $options) ? $options['source'] : 'web', $options);
     return $saved;
 }
Example #21
0
 /**
  * show the link to the main page for the notice
  *
  * Displays a link to the page for a notice, with "relative" time. Tries to
  * get remote notice URLs correct, but doesn't always succeed.
  *
  * @return void
  */
 function showNoticeLink()
 {
     $noticeurl = $this->notice->bestUrl();
     // above should always return an URL
     assert(!empty($noticeurl));
     $this->out->elementStart('a', array('rel' => 'bookmark', 'class' => 'timestamp', 'href' => $noticeurl));
     $dt = common_date_iso8601($this->notice->created);
     $this->out->element('abbr', array('class' => 'published', 'title' => $dt), common_date_string($this->notice->created));
     $this->out->elementEnd('a');
 }
 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;
 }
 function dumpGroups($user, $dir)
 {
     common_log(LOG_INFO, 'dumping memberships of ' . $user->nickname . ' to directory ' . $dir);
     $page = 1;
     do {
         $mem = Group_member::byMember($user->id, ($page - 1) * GROUPS_PER_PAGE, GROUPS_PER_PAGE + 1);
         while ($mem->fetch()) {
             try {
                 $fname = $dir . '/' . common_date_iso8601($mem->created) . '-membership-' . $mem->group_id . '.atom';
                 $act = $mem->asActivity();
                 $data = $act->asString(false, false, false);
                 common_log(LOG_INFO, 'dumping membership in ' . $mem->group_id . ' to file ' . $fname);
                 file_put_contents($fname, $data);
                 $data = null;
             } catch (Exception $e) {
                 common_log(LOG_ERR, "Error backing up membership in " . $mem->group_id . ": " . $e->getMessage());
                 continue;
             }
         }
         $page++;
     } while ($mem->N > GROUPS_PER_PAGE);
 }
 function asString($namespace = false)
 {
     $xs = new XMLStringer(true);
     if ($namespace) {
         $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:activity' => 'http://activitystrea.ms/spec/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');
     } else {
         $attrs = array();
     }
     $xs->elementStart('entry', $attrs);
     $xs->element('id', null, $this->id);
     $xs->element('title', null, $this->title);
     $xs->element('published', null, common_date_iso8601($this->time));
     $xs->element('content', array('type' => 'html'), $this->content);
     if (!empty($this->summary)) {
         $xs->element('summary', null, $this->summary);
     }
     if (!empty($this->link)) {
         $xs->element('link', array('rel' => 'alternate', 'type' => 'text/html'), $this->link);
     }
     // XXX: add context
     $xs->elementStart('author');
     $xs->element('uri', array(), $this->actor->id);
     if ($this->actor->title) {
         $xs->element('name', array(), $this->actor->title);
     }
     $xs->elementEnd('author');
     $xs->raw($this->actor->asString('activity:actor'));
     $xs->element('activity:verb', null, $this->verb);
     if (!empty($this->objects)) {
         foreach ($this->objects as $object) {
             $xs->raw($object->asString());
         }
     }
     if ($this->target) {
         $xs->raw($this->target->asString('activity:target'));
     }
     foreach ($this->categories as $cat) {
         $xs->raw($cat->asString());
     }
     $xs->elementEnd('entry');
     return $xs->getString();
 }
Example #25
0
 /**
  * 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;
 }
Example #26
0
 /**
  * show a single message in the list format
  *
  * XXX: This needs to be extracted out into a MessageList similar
  * to NoticeList.
  *
  * @param Message $message the message to show
  *
  * @return void
  */
 function showMessage($message)
 {
     $this->elementStart('li', array('class' => 'hentry notice', 'id' => 'message-' . $message->id));
     $profile = $this->getMessageProfile($message);
     $this->elementStart('div', 'entry-title');
     $this->elementStart('span', 'vcard author');
     $this->elementStart('a', array('href' => $profile->profileurl, 'class' => 'url'));
     $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
     $this->element('img', array('src' => $avatar ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE), 'class' => 'photo avatar', 'width' => AVATAR_STREAM_SIZE, 'height' => AVATAR_STREAM_SIZE, 'alt' => $profile->fullname ? $profile->fullname : $profile->nickname));
     $this->element('span', array('class' => 'nickname fn'), $profile->nickname);
     $this->elementEnd('a');
     $this->elementEnd('span');
     // FIXME: URL, image, video, audio
     $this->elementStart('p', array('class' => 'entry-content'));
     $this->raw($message->rendered);
     $this->elementEnd('p');
     $this->elementEnd('div');
     $messageurl = common_local_url('showmessage', array('message' => $message->id));
     // XXX: we need to figure this out better. Is this right?
     if (strcmp($message->uri, $messageurl) != 0 && preg_match('/^http/', $message->uri)) {
         $messageurl = $message->uri;
     }
     $this->elementStart('div', 'entry-content');
     $this->elementStart('a', array('rel' => 'bookmark', 'class' => 'timestamp', 'href' => $messageurl));
     $dt = common_date_iso8601($message->created);
     $this->element('abbr', array('class' => 'published', 'title' => $dt), common_date_string($message->created));
     $this->elementEnd('a');
     if ($message->source) {
         $this->elementStart('span', 'source');
         $this->text(_('from'));
         $this->element('span', 'device', $this->showSource($message->source));
         $this->elementEnd('span');
     }
     $this->elementEnd('div');
     $this->elementEnd('li');
 }
Example #27
0
 function showAtomGroups($group, $title, $id, $link, $subtitle = null, $selfuri = null)
 {
     $this->initDocument('atom');
     $this->element('title', null, common_xml_safe_str($title));
     $this->element('id', null, $id);
     $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
     if (!is_null($selfuri)) {
         $this->element('link', array('href' => $selfuri, 'rel' => 'self', 'type' => 'application/atom+xml'), null);
     }
     $this->element('updated', null, common_date_iso8601('now'));
     $this->element('subtitle', null, common_xml_safe_str($subtitle));
     if (is_array($group)) {
         foreach ($group as $g) {
             $this->raw($g->asAtomEntry());
         }
     } else {
         while ($group->fetch()) {
             $this->raw($group->asAtomEntry());
         }
     }
     $this->endDocument('atom');
 }
Example #28
0
 function setPublished($dt)
 {
     $this->published = common_date_iso8601($dt);
 }
Example #29
0
 /**
  * Show feed specific Atom elements
  *
  * @return void
  */
 function showFeed()
 {
     // TODO: A9 OpenSearch stuff like search.twitter.com?
     $server = common_config('site', 'server');
     $sitename = common_config('site', 'name');
     // XXX: Use xmlns:statusnet instead?
     $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:twitter' => 'http://api.twitter.com/', 'xml:lang' => 'en-US'));
     // XXX Other locales ?
     $taguribase = TagURI::base();
     $this->element('id', null, "tag:{$taguribase}:search/{$server}");
     $site_uri = common_path(false);
     $search_uri = $site_uri . 'api/search.atom?q=' . urlencode($this->query);
     if ($this->rpp != 15) {
         $search_uri .= '&rpp=' . $this->rpp;
     }
     // FIXME: this alternate link is not quite right because our
     // web-based notice search doesn't support a rpp (responses per
     // page) param yet
     $this->element('link', array('type' => 'text/html', 'rel' => 'alternate', 'href' => $site_uri . 'search/notice?q=' . urlencode($this->query)));
     // self link
     $self_uri = $search_uri;
     $self_uri .= $this->page > 1 ? '&page=' . $this->page : '';
     $this->element('link', array('type' => 'application/atom+xml', 'rel' => 'self', 'href' => $self_uri));
     // @todo Needs i18n?
     $this->element('title', null, "{$this->query} - {$sitename} Search");
     $this->element('updated', null, common_date_iso8601('now'));
     // XXX: The below "rel" links are not valid Atom, but it's what
     // Twitter does...
     // refresh link
     $refresh_uri = $search_uri . "&since_id=" . $this->max_id;
     $this->element('link', array('type' => 'application/atom+xml', 'rel' => 'refresh', 'href' => $refresh_uri));
     // pagination links
     if ($this->cnt > $this->rpp) {
         $next_uri = $search_uri . "&max_id=" . $this->max_id . '&page=' . ($this->page + 1);
         $this->element('link', array('type' => 'application/atom+xml', 'rel' => 'next', 'href' => $next_uri));
     }
     if ($this->page > 1) {
         $previous_uri = $search_uri . "&max_id=" . $this->max_id . '&page=' . ($this->page - 1);
         $this->element('link', array('type' => 'application/atom+xml', 'rel' => 'previous', 'href' => $previous_uri));
     }
 }
 /**
  * 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;
 }