private function _userReward($res, $activityId, $accessToken, $accessSecret) { $uid = AppbymeUserAccess::getUserIdByAccess($accessToken, $accessSecret); if (!$uid) { return $this->makeErrorInfo($res, 'mobcent_user_error'); } $exchangeInfo = AppbymeActivityInviteUser::getExchangeInfo($uid); $config = ActivityUtils::getInviteConfig($activityId); $res['body']['exchangeMin'] = (int) $config['exchange_min']; $res['body']['exchangeStatus'] = (int) $exchangeInfo['exchange_status']; $res['body']['virtualName'] = (string) $config['virtual_name']; $res['body']['exchangeRatio'] = (int) $config['exchange_ratio']; $res['body']['rewardSum'] = (int) $exchangeInfo['reward_sum']; $res['body']['availableReward'] = (int) $exchangeInfo['available_reward']; return $res; }
function __construct($element = null) { if (empty($element)) { return; } $replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR); if (!empty($replyToEl)) { $this->replyToID = $replyToEl->getAttribute(self::REF); $this->replyToUrl = $replyToEl->getAttribute(self::HREF); } $this->location = $this->getLocation($element); $this->conversation = ActivityUtils::getLink($element, self::CONVERSATION); // Multiple attention links allowed $links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK); $attention = array(); for ($i = 0; $i < $links->length; $i++) { $link = $links->item($i); $linkRel = $link->getAttribute(ActivityUtils::REL); // XXX: Deprecate this in favour of "mentioned" from Salmon spec // http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-salmon-00.html#SALR if ($linkRel == self::ATTENTION) { $attention[] = $link->getAttribute(self::HREF); } elseif ($linkRel == self::MENTIONED) { $attention[] = $link->getAttribute(self::HREF); } } $this->attention = array_unique($attention); }
private function _inviteCheck($res, $code, $device, $accessToken, $accessSecret, $activityId) { global $_G; // 获取邀请注册活动的配置 $config = ActivityUtils::getInviteConfig($activityId); // 验证是否能进行参数活动 $checkInvite = ActivityUtils::checkInvite($config, $_G['uid'], $device); if ($checkInvite['rs'] == 0) { return $this->makeErrorInfo($res, $checkInvite['errcode']); } $isSelf = AppbymeActivityInviteUser::getCheckByUidCode($_G['uid'], $code); if ($isSelf) { // 输入的是自己的验证码 return $this->makeErrorInfo($res, 'mobcent_check_code_self'); } $checkCode = AppbymeActivityInviteUser::checkCode($code); if (!$checkCode) { // 兑换码验证失败 return $this->makeErrorInfo($res, 'mobcent_check_code_self'); } if ($checkCode) { AppbymeActivityInviteUser::checkCodeSuccess($activityId, $code, $_G['uid']); } return $res; }
function __construct($element = null) { if (empty($element)) { return; } $replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR); if (!empty($replyToEl)) { $this->replyToID = $replyToEl->getAttribute(self::REF); $this->replyToUrl = $replyToEl->getAttribute(self::HREF); } $this->location = $this->getLocation($element); $convs = $element->getElementsByTagNameNS(self::OSTATUS, self::CONVERSATION); foreach ($convs as $conv) { $this->conversation = $conv->textContent; } if (empty($this->conversation)) { // fallback to the atom:link rel="ostatus:conversation" element $this->conversation = ActivityUtils::getLink($element, self::CONVERSATION); } // Multiple attention links allowed $links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK); for ($i = 0; $i < $links->length; $i++) { $link = $links->item($i); $linkRel = $link->getAttribute(ActivityUtils::REL); $linkHref = $link->getAttribute(self::HREF); if ($linkRel == self::MENTIONED && $linkHref !== '') { $this->attention[$linkHref] = $link->getAttribute(ActivityContext::OBJECTTYPE); } } }
private function _getAddress($element) { $addressEl = ActivityUtils::child($element, PoCoAddress::ADDRESS, PoCo::NS); if (!empty($addressEl)) { $formatted = ActivityUtils::childContent($addressEl, PoCoAddress::FORMATTED, self::NS); if (!empty($formatted)) { $address = new PoCoAddress(); $address->formatted = $formatted; return $address; } } return null; }
static function noticeCount($id) { $keypart = sprintf('conversation:notice_count:%d', $id); $cnt = self::cacheGet($keypart); if ($cnt !== false) { return $cnt; } $notice = new Notice(); $notice->conversation = $id; $notice->whereAddIn('verb', array(ActivityVerb::POST, ActivityUtils::resolveUri(ActivityVerb::POST, true)), $notice->columnType('verb')); $cnt = $notice->count(); self::cacheSet($keypart, $cnt); return $cnt; }
protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options = array()) { assert($this->isMyActivity($act)); $stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type); if (common_valid_http_url($act->objects[0]->link)) { $stored->url = $act->objects[0]->link; } // We don't have to do just about anything for a new, remote notice since the fields // are handled in the main Notice::saveActivity function. Such as content, attachments, // parent/conversation etc. // By returning true here instead of something that evaluates // to false, we show that we have processed everything properly. return true; }
function saveNoticeFromActivity(Activity $activity, Profile $actor, array $options = array()) { if (count($activity->objects) != 1) { throw new Exception('Too many activity objects.'); } $videoObj = $activity->objects[0]; if ($videoObj->type != Video::OBJECT_TYPE) { throw new Exception('Wrong type for object.'); } // For now we read straight from the xml tree, no other way to get this information. // When there's a better API for this, we should change to it. $uri = ActivityUtils::getLink($activity->entry, 'enclosure'); $options['object_type'] = Video::OBJECT_TYPE; Video::saveNew($actor, $uri, $options); }
public static function checkCodeSuccess($activityId, $code, $uid) { $config = ActivityUtils::getInviteConfig($activityId); $sql1 = 'UPDATE %t SET'; $sql1 .= ' invite_count=invite_count+1,'; $sql1 .= 'reward_sum=reward_sum+' . $config['invite_reward'] . ','; $sql1 .= 'available_reward=available_reward+' . $config['invite_reward']; $sql1 .= ' WHERE exchange_num=%s'; $sql2 = 'UPDATE %t SET'; $sql2 .= ' joining=1'; // $sql2 .= 'reward_sum=reward_sum+'.$config['invite_reward'].','; // $sql2 .= 'available_reward=available_reward+'.$config['invite_reward']; $sql2 .= ' WHERE uid=%d'; DbUtils::getDzDbUtils(true)->query($sql1, array('appbyme_activity_invite_user', $code)); DbUtils::getDzDbUtils(true)->query($sql2, array('appbyme_activity_invite_user', $uid)); }
function __construct($element) { $replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR); if (!empty($replyToEl)) { $this->replyToID = $replyToEl->getAttribute(self::REF); $this->replyToUrl = $replyToEl->getAttribute(self::HREF); } $this->location = $this->getLocation($element); $this->conversation = ActivityUtils::getLink($element, self::CONVERSATION); // Multiple attention links allowed $links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK); for ($i = 0; $i < $links->length; $i++) { $link = $links->item($i); $linkRel = $link->getAttribute(ActivityUtils::REL); if ($linkRel == self::ATTENTION) { $this->attention[] = $link->getAttribute(self::HREF); } } }
function handle($data) { list($user, $xml, $trusted) = $data; try { $doc = DOMDocument::loadXML($xml); $feed = $doc->documentElement; if ($feed->namespaceURI != Activity::ATOM || $feed->localName != 'feed') { // TRANS: Client exception thrown when an imported feed is not an Atom feed. throw new ClientException(_("Not an Atom feed.")); } $author = ActivityUtils::getFeedAuthor($feed); if (empty($author)) { // TRANS: Client exception thrown when an imported feed does not have an author. throw new ClientException(_("No author in the feed.")); } if (empty($user)) { if ($trusted) { $user = $this->userFromAuthor($author); } else { // TRANS: Client exception thrown when an imported feed does not have an author that // TRANS: can be associated with a user. throw new ClientException(_("Cannot import without a user.")); } } $activities = $this->getActivities($feed); $qm = QueueManager::get(); foreach ($activities as $activity) { $qm->enqueue(array($user, $author, $activity, $trusted), 'actimp'); } } catch (ClientException $ce) { common_log(LOG_WARNING, $ce->getMessage()); return true; } catch (ServerException $se) { common_log(LOG_ERR, $ce->getMessage()); return false; } catch (Exception $e) { common_log(LOG_ERR, $ce->getMessage()); return false; } }
function importActivityStream($user, $doc) { $feed = $doc->documentElement; $subjectEl = ActivityUtils::child($feed, Activity::SUBJECT, Activity::SPEC); if (!empty($subjectEl)) { $subject = new ActivityObject($subjectEl); printfv(_("Backup file for user %s (%s)") . "\n", $subject->id, Ostatus_profile::getActivityObjectNickname($subject)); } else { throw new Exception("Feed doesn't have an <activity:subject> element."); } if (is_null($user)) { printfv(_("No user specified; using backup user.") . "\n"); $user = userFromSubject($subject); } $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry'); printfv(_("%d entries in backup.") . "\n", $entries->length); for ($i = $entries->length - 1; $i >= 0; $i--) { try { $entry = $entries->item($i); $activity = new Activity($entry, $feed); switch ($activity->verb) { case ActivityVerb::FOLLOW: subscribeProfile($user, $subject, $activity); break; case ActivityVerb::JOIN: joinGroup($user, $activity); break; case ActivityVerb::POST: postNote($user, $activity); break; default: throw new Exception("Unknown verb: {$activity->verb}"); } } catch (Exception $e) { print $e->getMessage() . "\n"; continue; } } }
private function _inviteExchange($res, $mobile, $type, $activityId) { global $_G; $config = ActivityUtils::getInviteConfig($activityId); // 是否结束 if (time() > $config['stop_time']) { return $this->makeErrorInfo($res, 'mobcent_activity_end'); } // 兑换金额是否超过最低兑换值 $exchangeInfo = AppbymeActivityInviteUser::getExchangeInfo($_G['uid']); if ($exchangeInfo['available_reward'] < $config['exchange_min']) { return $this->makeErrorInfo($res, 'mobcent_exchange_min'); } if (!in_array($type, array('mobile', 'forum'))) { return $this->makeErrorInfo($res, 'mobcent_exchange_type_error'); } $exchange = array('exchange_type' => $type, 'mobile' => $mobile, 'exchange_status' => 1); $excInfo = AppbymeActivityInviteUser::inviteExchange($_G['uid'], $exchange); if (!$excInfo) { return $this->makeErrorInfo($res, 'mobcent_exchange_error'); } return $res; }
protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped) { switch (true) { case ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)): Fave::addNew($scoped, $target); break; case ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)): Fave::removeEntry($scoped, $target); break; default: throw new ServerException('ActivityVerb POST not handled by plugin that was supposed to do it.'); } return false; }
/** * Insert notifications for replies, mentions and repeats * * @return boolean hook flag */ function onStartNoticeDistribute($notice) { assert($notice->id > 0); // since we removed tests below // repeats if ($notice->isRepeat()) { $repeated_notice = Notice::getKV('id', $notice->repeat_of); if ($repeated_notice instanceof Notice) { $this->insertNotification($repeated_notice->profile_id, $notice->profile_id, 'repeat', $repeated_notice->id); // mark reply/mention-notifications as read if we're repeating to a notice we're notified about self::markNotificationAsSeen($repeated_notice->id, $notice->profile_id, 'mention'); self::markNotificationAsSeen($repeated_notice->id, $notice->profile_id, 'reply'); // (no other notifications repeats) return true; } } // don't add notifications for activity/non-post-verb notices if ($notice->source == 'activity' || !ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST))) { return true; } // mark reply/mention-notifications as read if we're replying to a notice we're notified about if ($notice->reply_to) { self::markNotificationAsSeen($notice->reply_to, $notice->profile_id, 'mention'); self::markNotificationAsSeen($notice->reply_to, $notice->profile_id, 'reply'); } // replies and mentions $reply_notification_to = false; // check for reply to insert in notifications if ($notice->reply_to) { try { $replyauthor = $notice->getParent()->getProfile(); $reply_notification_to = $replyauthor->id; $this->insertNotification($replyauthor->id, $notice->profile_id, 'reply', $notice->id); //} catch (NoParentNoticeException $e) { // TODO: catch this when everyone runs latest GNU social! // This is not a reply to something (has no parent) } catch (NoResultException $e) { // Parent author's profile not found! Complain louder? common_log(LOG_ERR, "Parent notice's author not found: " . $e->getMessage()); } } // check for mentions to insert in notifications $mentions = $notice->getReplies(); $sender = Profile::getKV($notice->profile_id); $all_mentioned_user_ids = array(); foreach ($mentions as $mentioned) { // no duplicate mentions if (in_array($mentioned, $all_mentioned_user_ids)) { continue; } $all_mentioned_user_ids[] = $mentioned; // only notify if mentioned user is not already notified for reply if ($reply_notification_to != $mentioned) { $this->insertNotification($mentioned, $notice->profile_id, 'mention', $notice->id); } } return true; }
public function getAtomLink($rel, $type = null) { return ActivityUtils::getLink($this->root, $rel, $type); }
static function saveActivity(Activity $act, Profile $actor, array $options = array()) { // First check if we're going to let this Activity through from the specific actor if (!$actor->hasRight(Right::NEWNOTICE)) { common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $actor->getNickname()); // TRANS: Client exception thrown when a user tries to post while being banned. throw new ClientException(_m('You are banned from posting notices on this site.'), 403); } if (common_config('throttle', 'enabled') && !self::checkEditThrottle($actor->id)) { common_log(LOG_WARNING, 'Excessive posting by profile #' . $actor->id . '; throttled.'); // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame. throw new ClientException(_m('Too many notices too fast; take a breather ' . 'and post again in a few minutes.')); } // Get ActivityObject properties $actobj = null; if (!empty($act->id)) { // implied object $options['uri'] = $act->id; $options['url'] = $act->link; } else { $actobj = count($act->objects) == 1 ? $act->objects[0] : null; if (!is_null($actobj) && !empty($actobj->id)) { $options['uri'] = $actobj->id; if ($actobj->link) { $options['url'] = $actobj->link; } elseif (preg_match('!^https?://!', $actobj->id)) { $options['url'] = $actobj->id; } } } $defaults = array('groups' => array(), 'is_local' => $actor->isLocal() ? self::LOCAL_PUBLIC : self::REMOTE, 'mentions' => array(), 'reply_to' => null, 'repeat_of' => null, 'scope' => null, 'source' => 'unknown', 'tags' => array(), 'uri' => null, 'url' => null, 'urls' => array(), 'distribute' => true); // options will have default values when nothing has been supplied $options = array_merge($defaults, $options); foreach (array_keys($defaults) as $key) { // Only convert the keynames we specify ourselves from 'defaults' array into variables ${$key} = $options[$key]; } extract($options, EXTR_SKIP); // dupe check $stored = new Notice(); if (!empty($uri) && !ActivityUtils::compareVerbs($act->verb, array(ActivityVerb::DELETE))) { $stored->uri = $uri; if ($stored->find()) { common_debug('cannot create duplicate Notice URI: ' . $stored->uri); // I _assume_ saving a Notice with a colliding URI means we're really trying to // save the same notice again... throw new AlreadyFulfilledException('Notice URI already exists'); } } $autosource = common_config('public', 'autosource'); // Sandboxed are non-false, but not 1, either if (!$actor->hasRight(Right::PUBLICNOTICE) || $source && $autosource && in_array($source, $autosource)) { // FIXME: ...what about remote nonpublic? Hmmm. That is, if we sandbox remote profiles... $stored->is_local = Notice::LOCAL_NONPUBLIC; } else { $stored->is_local = intval($is_local); } if (!$stored->isLocal()) { // Only do these checks for non-local notices. Local notices will generate these values later. if (!common_valid_http_url($url)) { common_debug('Bad notice URL: [' . $url . '], URI: [' . $uri . ']. Cannot link back to original! This is normal for shared notices etc.'); } if (empty($uri)) { throw new ServerException('No URI for remote notice. Cannot accept that.'); } } $stored->profile_id = $actor->id; $stored->source = $source; $stored->uri = $uri; $stored->url = $url; $stored->verb = $act->verb; // 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); // yeah, just don't use getRendered() here since it's not inserted yet ;) $stored->content = common_strip_html($stored->rendered); // 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); } else { $stored->created = common_sql_now(); } $reply = null; if ($act->context instanceof ActivityContext && !empty($act->context->replyToID)) { $reply = self::getKV('uri', $act->context->replyToID); } if (!$reply instanceof Notice && $act->target instanceof ActivityObject) { $reply = self::getKV('uri', $act->target->id); } if ($reply instanceof Notice) { if (!$reply->inScope($actor)) { // TRANS: Client error displayed when trying to reply to a notice a the target has no access to. // TRANS: %1$s is a user nickname, %2$d is a notice ID (number). throw new ClientException(sprintf(_m('%1$s has no right to reply to notice %2$d.'), $actor->getNickname(), $reply->id), 403); } $stored->reply_to = $reply->id; $stored->conversation = $reply->conversation; // If the original is private to a group, and notice has no group specified, // make it to the same group(s) if (empty($groups) && $reply->scope & Notice::GROUP_SCOPE) { $replyGroups = $reply->getGroups(); foreach ($replyGroups as $group) { if ($actor->isMember($group)) { $groups[] = $group->id; } } } if (is_null($scope)) { $scope = $reply->scope; } } else { // If we don't know the reply, we might know the conversation! // This will happen if a known remote user replies to an // unknown remote user - within a known conversation. if (empty($stored->conversation) and !empty($act->context->conversation)) { $conv = Conversation::getKV('uri', $act->context->conversation); if ($conv instanceof Conversation) { common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time (' . $stored->created . ') should maybe be compared to conversation creation time (' . $conv->created . ').'); } else { // Conversation entry with specified URI was not found, so we must create it. common_debug('Conversation URI not found, so we will create it with the URI given in the context of the activity: ' . $act->context->conversation); // The insert in Conversation::create throws exception on failure $conv = Conversation::create($act->context->conversation, $stored->created); } $stored->conversation = $conv->getID(); unset($conv); } } // If it's not part of a conversation, it's the beginning of a new conversation. if (empty($stored->conversation)) { $conv = Conversation::create(); $stored->conversation = $conv->getID(); unset($conv); } $notloc = null; if ($act->context instanceof ActivityContext) { if ($act->context->location instanceof Location) { $notloc = Notice_location::fromLocation($act->context->location); } } else { $act->context = new ActivityContext(); } $stored->scope = self::figureOutScope($actor, $groups, $scope); foreach ($act->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if (!empty($term)) { $tags[] = $term; } } } foreach ($act->enclosures as $href) { // @todo FIXME: Save these locally or....? $urls[] = $href; } if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) { if (empty($act->objects[0]->type)) { // Default type for the post verb is 'note', but we know it's // a 'comment' if it is in reply to something. $stored->object_type = empty($stored->reply_to) ? ActivityObject::NOTE : ActivityObject::COMMENT; } else { //TODO: Is it safe to always return a relative URI? The // JSON version of ActivityStreams always use it, so we // should definitely be able to handle it... $stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type, true); } } if (Event::handle('StartNoticeSave', array(&$stored))) { // XXX: some of these functions write to the DB try { $result = $stored->insert(); // throws exception on error if ($notloc instanceof Notice_location) { $notloc->notice_id = $stored->getID(); $notloc->insert(); } $orig = clone $stored; // for updating later in this try clause $object = null; Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); if (empty($object)) { throw new ServerException('Unsuccessful call to StoreActivityObject ' . $stored->getUri() . ': ' . $act->asString()); } // If something changed in the Notice during StoreActivityObject $stored->update($orig); } catch (Exception $e) { if (empty($stored->id)) { common_debug('Failed to save stored object entry in database (' . $e->getMessage() . ')'); } else { common_debug('Failed to store activity object in database (' . $e->getMessage() . '), deleting notice id ' . $stored->id); $stored->delete(); } throw $e; } } if (!$stored instanceof Notice) { throw new ServerException('StartNoticeSave did not give back a Notice'); } // 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($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. $stored->saveAttentions($act->context->attention); if (!empty($urls)) { $stored->saveKnownUrls($urls); } else { $stored->saveUrls(); } } if ($distribute) { // Prepare inbox delivery, may be queued to background. $stored->distribute(); } return $stored; }
/** * This is run before ->insert, so our task in this function is just to * delete if it is the delete verb. */ public function onStartNoticeSave(Notice $stored) { // DELETE is a bit special, we have to remove the existing entry and then // add a new one with the same URI in order to trigger the distribution. // (that's why we don't use $this->isMyNotice(...)) if (!ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::DELETE))) { return true; } try { $target = Notice::getByUri($stored->uri); } catch (NoResultException $e) { throw new AlreadyFulfilledException('Notice URI not found, so we have nothing to delete.'); } $actor = $stored->getProfile(); $owner = $target->getProfile(); if ($owner->hasRole(Profile_role::DELETED)) { // Don't bother with replacing notices if its author is being deleted. // The later "StoreActivityObject" will pick this up and execute // the deletion then. // (the "delete verb notice" is too new to ever pass through Notice::saveNew // which otherwise wouldn't execute the StoreActivityObject event) return true; } // Since the user deleting may not be the same as the notice's owner, // double-check this and also set the "re-stored" notice profile_id. if (!$actor->sameAs($owner) && !$actor->hasRight(Right::DELETEOTHERSNOTICE)) { throw new AuthorizationException(_('You are not allowed to delete another user\'s notice.')); } // We copy the identifying fields and replace the sensitive ones. //$stored->id = $target->id; // We can't copy this since DB_DataObject won't inject it anyway $props = array('uri', 'profile_id', 'conversation', 'reply_to', 'created', 'repeat_of', 'object_type', 'is_local', 'scope'); foreach ($props as $prop) { $stored->{$prop} = $target->{$prop}; } // Let's see if this has been deleted already. try { $deleted = Deleted_notice::getByKeys(['uri' => $stored->getUri()]); return $deleted; } catch (NoResultException $e) { $deleted = new Deleted_notice(); $deleted->id = $target->getID(); $deleted->profile_id = $actor->getID(); $deleted->uri = $stored->getUri(); $deleted->act_created = $stored->created; $deleted->created = common_sql_now(); // throws exception on error $result = $deleted->insert(); } // Now we delete the original notice, leaving the id and uri free. $target->delete(); return true; }
/** * Save a bookmark from an activity * * @param Activity $activity Activity to save * @param Profile $actor Profile to use as author * @param array $options Options to pass to bookmark-saving code * * @return Notice resulting notice */ function saveNoticeFromActivity(Activity $activity, Profile $actor, array $options = array()) { $bookmark = $activity->objects[0]; $relLinkEls = ActivityUtils::getLinks($bookmark->element, 'related'); if (count($relLinkEls) < 1) { // TRANS: Client exception thrown when a bookmark is formatted incorrectly. throw new ClientException(_m('Expected exactly 1 link ' . 'rel=related in a Bookmark.')); } if (count($relLinkEls) > 1) { common_log(LOG_WARNING, "Got too many link rel=related in a Bookmark."); } $linkEl = $relLinkEls[0]; $url = $linkEl->getAttribute('href'); $tags = array(); foreach ($activity->categories as $category) { $tags[] = common_canonical_tag($category->term); } if (!empty($activity->time)) { $options['created'] = common_sql_date($activity->time); } // Fill in location if available $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } $options['groups'] = array(); $options['replies'] = array(); // TODO: context->attention foreach ($activity->context->attention as $attnUrl => $type) { try { $other = Profile::fromUri($attnUrl); if ($other->isGroup()) { $options['groups'][] = $other->id; } else { $options['replies'][] = $attnUrl; } } catch (UnknownUriException $e) { // We simply don't know this URI, despite lookup attempts. } } // Maintain direct reply associations // @fixme what about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::getKV('uri', $activity->context->replyToID); if (!empty($orig)) { $options['reply_to'] = $orig->id; } } return Bookmark::saveNew($actor, $bookmark->title, $url, $tags, $bookmark->summary, $options); }
static function canonicalType($type) { return ActivityUtils::resolveUri($type, true); }
static function textConstruct($el) { $src = $el->getAttribute(self::SRC); if (!empty($src)) { // TRANS: Client exception thrown when there is no source attribute. throw new ClientException(_("Can't handle remote content yet.")); } $type = $el->getAttribute(self::TYPE); // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3 if (empty($type) || $type == 'text') { // We have plaintext saved as the XML text content. // Since we want HTML, we need to escape any special chars. return htmlspecialchars($el->textContent); } else { if ($type == 'html') { // We have HTML saved as the XML text content. // No additional processing required once we've got it. $text = $el->textContent; return $text; } else { if ($type == 'xhtml') { // Per spec, the <content type="xhtml"> contains a single // HTML <div> with XHTML namespace on it as a child node. // We need to pull all of that <div>'s child nodes and // serialize them back to an (X)HTML source fragment. $divEl = ActivityUtils::child($el, 'div', 'http://www.w3.org/1999/xhtml'); if (empty($divEl)) { return null; } $doc = $divEl->ownerDocument; $text = ''; $children = $divEl->childNodes; for ($i = 0; $i < $children->length; $i++) { $child = $children->item($i); $text .= $doc->saveXML($child); } return trim($text); } else { if (in_array($type, array('text/xml', 'application/xml')) || preg_match('#(+|/)xml$#', $type)) { // TRANS: Client exception thrown when there embedded XML content is found that cannot be processed yet. throw new ClientException(_("Can't handle embedded XML content yet.")); } else { if (strncasecmp($type, 'text/', 5)) { return $el->textContent; } else { // TRANS: Client exception thrown when base64 encoded content is found that cannot be processed yet. throw new ClientException(_("Can't handle embedded Base64 content yet.")); } } } } } }
private function _getSource($element) { $sourceEl = ActivityUtils::child($element, 'source'); if (empty($sourceEl)) { return null; } else { $href = ActivityUtils::getLink($sourceEl, 'self'); if (!empty($href)) { return $href; } else { return ActivityUtils::childContent($sourceEl, 'id'); } } }
private function _child($element, $tag, $namespace = self::SPEC) { return ActivityUtils::child($element, $tag, $namespace); }
public function testBookmarkRelated() { global $_example11; $dom = new DOMDocument(); $dom->loadXML($_example11); $feed = $dom->documentElement; $entry = $dom->getElementsByTagName('entry')->item(0); $expected = 'http://blog.teambox.com/open-source-companies'; $links = ActivityUtils::getLinks($entry, 'related'); $this->assertFalse(empty($links)); $this->assertTrue(is_array($links)); $this->assertEquals(count($links), 1); $url = $links[0]->getAttribute('href'); $this->assertEquals($url, $expected); }
/** * Add stuff to notices in API responses * * @param Action $action action being executed * * @return boolean hook return */ function onNoticeSimpleStatusArray($notice, &$twitter_status, $scoped) { // groups $notice_groups = $notice->getGroups(); $group_addressees = false; foreach ($notice_groups as $g) { $group_addressees[] = array('nickname' => $g->nickname, 'url' => $g->mainpage); } $twitter_status['statusnet_in_groups'] = $group_addressees; // for older verions of gnu social: include the repeat-id, which we need when unrepeating later if (array_key_exists('repeated', $twitter_status) && $twitter_status['repeated'] === true) { $repeated = Notice::pkeyGet(array('profile_id' => $scoped->id, 'repeat_of' => $notice->id, 'verb' => 'http://activitystrea.ms/schema/1.0/share')); $twitter_status['repeated_id'] = $repeated->id; } // more metadata about attachments // get all attachments first, and put all the extra meta data in an array $attachments = $notice->attachments(); $attachment_url_to_id = array(); if (!empty($attachments)) { foreach ($attachments as $attachment) { if (is_object($attachment)) { try { $enclosure_o = $attachment->getEnclosure(); // add id to all attachments $attachment_url_to_id[$enclosure_o->url]['id'] = $attachment->id; // add data about thumbnails $thumb = $attachment->getThumbnail(); $large_thumb = $attachment->getThumbnail(1000, 3000, false); if (method_exists('File_thumbnail', 'url')) { $thumb_url = File_thumbnail::url($thumb->filename); $large_thumb_url = File_thumbnail::url($large_thumb->filename); } else { $thumb_url = $thumb->getUrl(); $large_thumb_url = $large_thumb->getUrl(); } $attachment_url_to_id[$enclosure_o->url]['thumb_url'] = $thumb_url; $attachment_url_to_id[$enclosure_o->url]['large_thumb_url'] = $large_thumb_url; $attachment_url_to_id[$enclosure_o->url]['width'] = $attachment->width; $attachment_url_to_id[$enclosure_o->url]['height'] = $attachment->height; // animated gif? if ($attachment->mimetype == 'image/gif') { $image = ImageFile::fromFileObject($attachment); if ($image->animated == 1) { $attachment_url_to_id[$enclosure_o->url]['animated'] = true; } else { $attachment_url_to_id[$enclosure_o->url]['animated'] = false; } } // this applies to older versions of gnu social, i think } catch (ServerException $e) { $thumb = File_thumbnail::getKV('file_id', $attachment->id); if ($thumb instanceof File_thumbnail) { $thumb_url = $thumb->getUrl(); $attachment_url_to_id[$enclosure_o->url]['id'] = $attachment->id; $attachment_url_to_id[$enclosure_o->url]['thumb_url'] = $thumb_url; $attachment_url_to_id[$enclosure_o->url]['large_thumb_url'] = $thumb_url; $attachment_url_to_id[$enclosure_o->url]['width'] = $attachment->width; $attachment_url_to_id[$enclosure_o->url]['height'] = $attachment->height; // animated gif? if ($attachment->mimetype == 'image/gif') { $image = ImageFile::fromFileObject($attachment); if ($image->animated == 1) { $attachment_url_to_id[$enclosure_o->url]['animated'] = true; } else { $attachment_url_to_id[$enclosure_o->url]['animated'] = false; } } } } } } } // add the extra meta data to $twitter_status if (!empty($twitter_status['attachments'])) { foreach ($twitter_status['attachments'] as &$attachment) { if (!empty($attachment_url_to_id[$attachment['url']])) { $attachment = array_merge($attachment, $attachment_url_to_id[$attachment['url']]); } } } // reply-to profile url try { $reply = $notice->getParent(); $twitter_status['in_reply_to_profileurl'] = $reply->getProfile()->getUrl(); } catch (ServerException $e) { $twitter_status['in_reply_to_profileurl'] = null; } // fave number $faves = Fave::byNotice($notice); $favenum = count($faves); $twitter_status['fave_num'] = $favenum; // repeat number $repeats = $notice->repeatStream(); $repeatnum = 0; while ($repeats->fetch()) { if ($repeats->verb == ActivityVerb::SHARE) { // i.e. not deleted repeats $repeatnum++; } } $twitter_status['repeat_num'] = $repeatnum; // some more metadata about notice if ($notice->is_local == '1') { $twitter_status['is_local'] = true; } else { $twitter_status['is_local'] = false; if ($notice->object_type != 'activity') { try { $twitter_status['external_url'] = $notice->getUrl(true); } catch (InvalidUrlException $e) { common_debug('Qvitter: No URL available for external notice: id=' . $notice->id); } } } if ($notice->source == 'activity' || $notice->object_type == 'activity' || $notice->object_type == 'http://activitystrea.ms/schema/1.0/activity' || $notice->verb == 'delete') { $twitter_status['is_activity'] = true; } else { $twitter_status['is_activity'] = false; } if (ActivityUtils::compareTypes($notice->verb, array('qvitter-delete-notice', 'delete'))) { $twitter_status['qvitter_delete_notice'] = true; } return true; }
public function testConversationLink() { $orig = $this->_fakeNotice($this->targetUser1); $text = "@" . $this->targetUser1->nickname . " reply text " . common_good_rand(4); $reply = Notice::saveNew($this->author1->id, $text, 'test', array('uri' => null, 'reply_to' => $orig->id)); $conv = Conversation::staticGet('id', $reply->conversation); $entry = $reply->asAtomEntry(); $element = $this->_entryToElement($entry, true); $this->assertEquals($conv->uri, ActivityUtils::getLink($element, 'ostatus:conversation')); }
/** * Get the identifier URI for the remote entity described * by this ActivityObject. This URI is *not* guaranteed to be * a resolvable HTTP/HTTPS URL. * * @param ActivityObject $object * @return string * @throws ServerException if feed info invalid */ protected static function getActivityObjectProfileURI(ActivityObject $object) { if ($object->id) { if (ActivityUtils::validateUri($object->id)) { return $object->id; } } // If the id is missing or invalid (we've seen feeds mistakenly listing // things like local usernames in that field) then we'll use the profile // page link, if valid. if ($object->link && common_valid_http_url($object->link)) { return $object->link; } // TRANS: Server exception. throw new ServerException(_m('No author ID URI found.')); }
protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options = array()) { assert($this->isMyActivity($act)); // The below algorithm is mainly copied from the previous Ostatus_profile->processShare() if (count($act->objects) !== 1) { // TRANS: Client exception thrown when trying to share multiple activities at once. throw new ClientException(_m('Can only handle share activities with exactly one object.')); } $shared = $act->objects[0]; if (!$shared instanceof Activity) { // TRANS: Client exception thrown when trying to share a non-activity object. throw new ClientException(_m('Can only handle shared activities.')); } $sharedUri = $shared->id; if (!empty($shared->objects[0]->id)) { // Because StatusNet since commit 8cc4660 sets $shared->id to a TagURI which // f***s up federation, because the URI is no longer recognised by the origin. // So we set it to the object ID if it exists, otherwise we trust $shared->id $sharedUri = $shared->objects[0]->id; } if (empty($sharedUri)) { throw new ClientException(_m('Shared activity does not have an id')); } try { // First check if we have the shared activity. This has to be done first, because // we can't use these functions to "ensureActivityObjectProfile" of a local user, // who might be the creator of the shared activity in question. $sharedNotice = Notice::getByUri($sharedUri); } catch (NoResultException $e) { // If no locally stored notice is found, process it! // TODO: Remember to check Deleted_notice! // TODO: If a post is shared that we can't retrieve - what to do? $other = Ostatus_profile::ensureActivityObjectProfile($shared->actor); $sharedNotice = $other->processActivity($shared, 'push'); // FIXME: push/salmon/what? if (!$sharedNotice instanceof Notice) { // And if we apparently can't get the shared notice, we'll abort the whole thing. // TRANS: Client exception thrown when saving an activity share fails. // TRANS: %s is a share ID. throw new ClientException(sprintf(_m('Failed to save activity %s.'), $sharedUri)); } } catch (FeedSubException $e) { // Remote feed could not be found or verified, should we // transform this into an "RT @user Blah, blah, blah..."? common_log(LOG_INFO, __METHOD__ . ' got a ' . get_class($e) . ': ' . $e->getMessage()); return false; } // Setting this here because when the algorithm gets back to // Notice::saveActivity it will update the Notice object. $stored->repeat_of = $sharedNotice->getID(); $stored->conversation = $sharedNotice->conversation; $stored->object_type = ActivityUtils::resolveUri(ActivityObject::ACTIVITY, true); // We don't have to save a repeat in a separate table, we can // find repeats by just looking at the notice.repeat_of field. // By returning true here instead of something that evaluates // to false, we show that we have processed everything properly. return true; }
/** * Action of the form * * @return string URL of the action */ function action() { return common_local_url('activityverb', array('id' => $this->notice->getID(), 'verb' => ActivityUtils::resolveUri(ActivityVerb::UNFAVORITE, true))); }
/** * Handle a posted object from Salmon * * @param Activity $activity activity to handle * @param mixed $target user or group targeted * * @return boolean hook value */ function onStartHandleSalmonTarget(Activity $activity, $target) { if (!$this->isMyActivity($activity)) { return true; } $this->log(LOG_INFO, "Checking {$activity->id} as a valid Salmon slap."); if ($target instanceof User_group || $target->isGroup()) { $uri = $target->getUri(); if (!array_key_exists($uri, $activity->context->attention)) { // @todo FIXME: please document (i18n). // TRANS: Client exception thrown when ... throw new ClientException(_('Object not posted to this group.')); } } elseif ($target instanceof Profile && $target->isLocal()) { $original = null; // FIXME: Shouldn't favorites show up with a 'target' activityobject? if (!ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) { // If this is not a post, it's a verb targeted at something (such as a Favorite attached to a note) if (!empty($activity->objects[0]->id)) { $activity->context->replyToID = $activity->objects[0]->id; } } if (!empty($activity->context->replyToID)) { $original = Notice::getKV('uri', $activity->context->replyToID); } if ((!$original instanceof Notice || $original->profile_id != $target->id) && !array_key_exists($target->getUri(), $activity->context->attention)) { // @todo FIXME: Please document (i18n). // TRANS: Client exception when ... throw new ClientException(_('Object not posted to this user.')); } } else { // TRANS: Server exception thrown when a micro app plugin uses a target that cannot be handled. throw new ServerException(_('Do not know how to handle this kind of target.')); } $oactor = Ostatus_profile::ensureActivityObjectProfile($activity->actor); $actor = $oactor->localProfile(); // FIXME: will this work in all cases? I made it work for Favorite... if (ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST))) { $object = $activity->objects[0]; } else { $object = $activity; } $options = array('uri' => $object->id, 'url' => $object->link, 'is_local' => Notice::REMOTE, 'source' => 'ostatus'); if (!isset($this->oldSaveNew)) { $notice = Notice::saveActivity($activity, $actor, $options); } else { $notice = $this->saveNoticeFromActivity($activity, $actor, $options); } return false; }