From 543ff40ef62f2587cde084b03bcf2e956f52ce2f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 24 Feb 2010 16:51:24 -0800 Subject: Populate more profile information when doing a remote subscribe --- lib/activity.php | 56 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/activity.php b/lib/activity.php index 33932ad0e..eace672a5 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -154,7 +154,15 @@ class PoCo PoCo::NS ); - array_push($urls, new PoCoURL($type, $value, $primary)); + $isPrimary = false; + + if (isset($primary) && $primary == 'true') { + $isPrimary = true; + } + + // @todo check to make sure a primary hasn't already been added + + array_push($urls, new PoCoURL($type, $value, $isPrimary)); } return $urls; } @@ -213,6 +221,15 @@ class PoCo return $poco; } + function getPrimaryURL() + { + foreach ($this->urls as $url) { + if ($url->primary) { + return $url; + } + } + } + function asString() { $xs = new XMLStringer(true); @@ -494,6 +511,12 @@ class ActivityObject $this->element = $element; + $this->geopoint = $this->_childContent( + $element, + ActivityContext::POINT, + ActivityContext::GEORSS + ); + if ($element->tagName == 'author') { $this->type = self::PERSON; // XXX: is this fair? @@ -759,22 +782,29 @@ class ActivityContext for ($i = 0; $i < $points->length; $i++) { $point = $points->item($i)->textContent; - $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" - $point = preg_replace('/\s+/', ' ', $point); - $point = trim($point); - $coords = explode(' ', $point); - if (count($coords) == 2) { - list($lat, $lon) = $coords; - if (is_numeric($lat) && is_numeric($lon)) { - common_log(LOG_INFO, "Looking up location for $lat $lon from georss"); - return Location::fromLatLon($lat, $lon); - } - } - common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); + return self::locationFromPoint($point); } return null; } + + // XXX: Move to ActivityUtils or Location? + static function locationFromPoint($point) + { + $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" + $point = preg_replace('/\s+/', ' ', $point); + $point = trim($point); + $coords = explode(' ', $point); + if (count($coords) == 2) { + list($lat, $lon) = $coords; + if (is_numeric($lat) && is_numeric($lon)) { + common_log(LOG_INFO, "Looking up location for $lat $lon from georss point"); + return Location::fromLatLon($lat, $lon); + } + } + common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); + return null; + } } /** -- cgit v1.2.3-54-g00ecf From e6858d7203bd36923f6251968bede6f4b271bf84 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 25 Feb 2010 08:44:15 -0500 Subject: modify group actions so they use Local_group to look up by nickname --- actions/apigroupcreate.php | 8 +++-- actions/apigrouplistall.php | 10 +++--- actions/blockedfromgroup.php | 9 +++++- actions/editgroup.php | 14 +++++++-- actions/foafgroup.php | 17 ++++++++--- actions/groupdesignsettings.php | 5 ++- actions/grouplogo.php | 5 ++- actions/groupmembers.php | 9 +++++- actions/grouprss.php | 9 +++++- actions/groups.php | 18 ++++++----- actions/joingroup.php | 9 +++++- actions/leavegroup.php | 9 +++++- actions/newgroup.php | 7 +++-- actions/showgroup.php | 10 +++++- classes/Local_group.php | 29 ++++++++++++++++-- classes/User_group.php | 68 ++++++++++++++++++++++++----------------- lib/api.php | 21 +++++++++++-- 17 files changed, 190 insertions(+), 67 deletions(-) (limited to 'lib') diff --git a/actions/apigroupcreate.php b/actions/apigroupcreate.php index 028d76a78..145806356 100644 --- a/actions/apigroupcreate.php +++ b/actions/apigroupcreate.php @@ -123,7 +123,9 @@ class ApiGroupCreateAction extends ApiAuthAction 'description' => $this->description, 'location' => $this->location, 'aliases' => $this->aliases, - 'userid' => $this->user->id)); + 'userid' => $this->user->id, + 'local' => true)); + switch($this->format) { case 'xml': $this->showSingleXmlGroup($group); @@ -306,9 +308,9 @@ class ApiGroupCreateAction extends ApiAuthAction function groupNicknameExists($nickname) { - $group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); - if (!empty($group)) { + if (!empty($local)) { return true; } diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php index d2ef2978a..e1b54a832 100644 --- a/actions/apigrouplistall.php +++ b/actions/apigrouplistall.php @@ -134,13 +134,13 @@ class ApiGroupListAllAction extends ApiPrivateAuthAction function getGroups() { - $groups = array(); - - // XXX: Use the $page, $count, $max_id, $since_id, and $since parameters + $qry = 'SELECT user_group.* '. + 'from user_group join local_group on user_group.id = local_group.group_id '. + 'order by created desc '; $group = new User_group(); - $group->orderBy('created DESC'); - $group->find(); + + $group->query($qry); while ($group->fetch()) { $groups[] = clone($group); diff --git a/actions/blockedfromgroup.php b/actions/blockedfromgroup.php index 0b4caf5bf..a0598db27 100644 --- a/actions/blockedfromgroup.php +++ b/actions/blockedfromgroup.php @@ -74,7 +74,14 @@ class BlockedfromgroupAction extends GroupDesignAction return false; } - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $this->clientError(_('No such group.'), 404); diff --git a/actions/editgroup.php b/actions/editgroup.php index ad0b6e185..bbbb7a0f4 100644 --- a/actions/editgroup.php +++ b/actions/editgroup.php @@ -86,10 +86,14 @@ class EditgroupAction extends GroupDesignAction } $groupid = $this->trimmed('groupid'); + if ($groupid) { $this->group = User_group::staticGet('id', $groupid); } else { - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + if ($local) { + $this->group = User_group::staticGet('id', $local->group_id); + } } if (!$this->group) { @@ -259,6 +263,12 @@ class EditgroupAction extends GroupDesignAction $this->serverError(_('Could not create aliases.')); } + if ($nickname != $orig->nickname) { + common_log(LOG_INFO, "Saving local group info."); + $local = Local_group::staticGet('group_id', $this->group->id); + $local->setNickname($nickname); + } + $this->group->query('COMMIT'); if ($this->group->nickname != $orig->nickname) { @@ -272,7 +282,7 @@ class EditgroupAction extends GroupDesignAction function nicknameExists($nickname) { - $group = User_group::staticGet('nickname', $nickname); + $group = Local_group::staticGet('nickname', $nickname); if (!empty($group) && $group->id != $this->group->id) { diff --git a/actions/foafgroup.php b/actions/foafgroup.php index f5fd7fe88..ebdf1cee2 100644 --- a/actions/foafgroup.php +++ b/actions/foafgroup.php @@ -56,7 +56,14 @@ class FoafGroupAction extends Action return false; } - $this->group = User_group::staticGet('nickname', $this->nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $this->clientError(_('No such group.'), 404); @@ -113,7 +120,7 @@ class FoafGroupAction extends Action if ($this->group->homepage_logo) { $this->element('depiction', array('rdf:resource' => $this->group->homepage_logo)); } - + $members = $this->group->getMembers(); $member_details = array(); while ($members->fetch()) { @@ -123,7 +130,7 @@ class FoafGroupAction extends Action ); $this->element('member', array('rdf:resource' => $member_uri)); } - + $admins = $this->group->getAdmins(); while ($admins->fetch()) { $admin_uri = common_local_url('userbyid', array('id'=>$admins->id)); @@ -132,7 +139,7 @@ class FoafGroupAction extends Action } $this->elementEnd('Group'); - + ksort($member_details); foreach ($member_details as $uri => $details) { if ($details['is_admin']) @@ -158,7 +165,7 @@ class FoafGroupAction extends Action )); } } - + $this->elementEnd('rdf:RDF'); $this->endXML(); } diff --git a/actions/groupdesignsettings.php b/actions/groupdesignsettings.php index e290ba514..526226a28 100644 --- a/actions/groupdesignsettings.php +++ b/actions/groupdesignsettings.php @@ -90,7 +90,10 @@ class GroupDesignSettingsAction extends DesignSettingsAction if ($groupid) { $this->group = User_group::staticGet('id', $groupid); } else { - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + if ($local) { + $this->group = User_group::staticGet('id', $local->group_id); + } } if (!$this->group) { diff --git a/actions/grouplogo.php b/actions/grouplogo.php index 3c9b56296..f414a23cc 100644 --- a/actions/grouplogo.php +++ b/actions/grouplogo.php @@ -92,7 +92,10 @@ class GrouplogoAction extends GroupDesignAction if ($groupid) { $this->group = User_group::staticGet('id', $groupid); } else { - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + if ($local) { + $this->group = User_group::staticGet('id', $local->group_id); + } } if (!$this->group) { diff --git a/actions/groupmembers.php b/actions/groupmembers.php index f16e972a4..a16debd7b 100644 --- a/actions/groupmembers.php +++ b/actions/groupmembers.php @@ -77,7 +77,14 @@ class GroupmembersAction extends GroupDesignAction return false; } - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $this->clientError(_('No such group.'), 404); diff --git a/actions/grouprss.php b/actions/grouprss.php index 866fc66eb..490f6f945 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -92,7 +92,14 @@ class groupRssAction extends Rss10Action return false; } - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $this->clientError(_('No such group.'), 404); diff --git a/actions/groups.php b/actions/groups.php index 10a1d5964..8aacff8b0 100644 --- a/actions/groups.php +++ b/actions/groups.php @@ -109,17 +109,21 @@ class GroupsAction extends Action } $offset = ($this->page-1) * GROUPS_PER_PAGE; - $limit = GROUPS_PER_PAGE + 1; + $limit = GROUPS_PER_PAGE + 1; + + $qry = 'SELECT user_group.* '. + 'from user_group join local_group on user_group.id = local_group.group_id '. + 'order by user_group.created desc '. + 'limit ' . $limit . ' offset ' . $offset; $groups = new User_group(); - $groups->orderBy('created DESC'); - $groups->limit($offset, $limit); $cnt = 0; - if ($groups->find()) { - $gl = new GroupList($groups, null, $this); - $cnt = $gl->show(); - } + + $groups->query($qry); + + $gl = new GroupList($groups, null, $this); + $cnt = $gl->show(); $this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE, $this->page, 'groups'); diff --git a/actions/joingroup.php b/actions/joingroup.php index 235e5ab4c..ba642f712 100644 --- a/actions/joingroup.php +++ b/actions/joingroup.php @@ -77,7 +77,14 @@ class JoingroupAction extends Action return false; } - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $this->clientError(_('No such group.'), 404); diff --git a/actions/leavegroup.php b/actions/leavegroup.php index 9b9d83b6c..222d4c1b4 100644 --- a/actions/leavegroup.php +++ b/actions/leavegroup.php @@ -77,7 +77,14 @@ class LeavegroupAction extends Action return false; } - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $this->clientError(_('No such group.'), 404); diff --git a/actions/newgroup.php b/actions/newgroup.php index 25da7f8fc..6bb3eca76 100644 --- a/actions/newgroup.php +++ b/actions/newgroup.php @@ -192,16 +192,17 @@ class NewgroupAction extends Action 'description' => $description, 'location' => $location, 'aliases' => $aliases, - 'userid' => $cur->id)); + 'userid' => $cur->id, + 'local' => true)); common_redirect($group->homeUrl(), 303); } function nicknameExists($nickname) { - $group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); - if (!empty($group)) { + if (!empty($local)) { return true; } diff --git a/actions/showgroup.php b/actions/showgroup.php index eb1238902..0139ba157 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -122,7 +122,15 @@ class ShowgroupAction extends GroupDesignAction return false; } - $this->group = User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + + if (!$local) { + common_log(LOG_NOTICE, "Couldn't find local group for nickname '$nickname'"); + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->group = User_group::staticGet('id', $local->group_id); if (!$this->group) { $alias = Group_alias::staticGet('alias', $nickname); diff --git a/classes/Local_group.php b/classes/Local_group.php index 02663048f..42312ec63 100644 --- a/classes/Local_group.php +++ b/classes/Local_group.php @@ -2,9 +2,8 @@ /** * Table Definition for local_group */ -require_once 'classes/Memcached_DataObject.php'; -class Local_group extends Memcached_DataObject +class Local_group extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -16,8 +15,32 @@ class Local_group extends Memcached_DataObject public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Local_group',$k,$v); } + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Local_group',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } + + function setNickname($nickname) + { + $this->decache(); + $qry = 'UPDATE local_group set nickname = "'.$nickname.'" where group_id = ' . $this->group_id; + + $result = $this->query($qry); + + if ($result) { + $this->nickname = $nickname; + $this->fixupTimestamps(); + $this->encache(); + } else { + common_log_db_error($local, 'UPDATE', __FILE__); + throw new ServerException(_('Could not update local group.')); + } + + return $result; + } } diff --git a/classes/User_group.php b/classes/User_group.php index 6e58a4d67..5877ce202 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -10,16 +10,16 @@ class User_group extends Memcached_DataObject public $__table = 'user_group'; // table name public $id; // int(4) primary_key not_null - public $nickname; // varchar(64) - public $fullname; // varchar(255) - public $homepage; // varchar(255) - public $description; // text - public $location; // varchar(255) - public $original_logo; // varchar(255) - public $homepage_logo; // varchar(255) - public $stream_logo; // varchar(255) - public $mini_logo; // varchar(255) - public $design_id; // int(4) + public $nickname; // varchar(64) + public $fullname; // varchar(255) + public $homepage; // varchar(255) + public $description; // text + public $location; // varchar(255) + public $original_logo; // varchar(255) + public $homepage_logo; // varchar(255) + public $stream_logo; // varchar(255) + public $mini_logo; // varchar(255) + public $design_id; // int(4) public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $uri; // varchar(255) unique_key @@ -414,28 +414,30 @@ class User_group extends Memcached_DataObject $group->homepage = $homepage; $group->description = $description; $group->location = $location; + $group->uri = $uri; $group->created = common_sql_now(); $result = $group->insert(); if (!$result) { common_log_db_error($group, 'INSERT', __FILE__); - $this->serverError( - _('Could not create group.'), - 500, - $this->format - ); - return; + throw new ServerException(_('Could not create group.')); } + + if (!isset($uri) || empty($uri)) { + $orig = clone($group); + $group->uri = common_local_url('groupbyid', array('id' => $group->id)); + $result = $group->update($orig); + if (!$result) { + common_log_db_error($group, 'UPDATE', __FILE__); + throw new ServerException(_('Could not set group uri.')); + } + } + $result = $group->setAliases($aliases); if (!$result) { - $this->serverError( - _('Could not create aliases.'), - 500, - $this->format - ); - return; + throw new ServerException(_('Could not create aliases.')); } $member = new Group_member(); @@ -449,12 +451,22 @@ class User_group extends Memcached_DataObject if (!$result) { common_log_db_error($member, 'INSERT', __FILE__); - $this->serverError( - _('Could not set group membership.'), - 500, - $this->format - ); - return; + throw new ServerException(_('Could not set group membership.')); + } + + if ($local) { + $local_group = new Local_group(); + + $local_group->group_id = $group->id; + $local_group->nickname = $nickname; + $local_group->created = common_sql_now(); + + $result = $local_group->insert(); + + if (!$result) { + common_log_db_error($local_group, 'INSERT', __FILE__); + throw new ServerException(_('Could not save local group info.')); + } } $group->query('COMMIT'); diff --git a/lib/api.php b/lib/api.php index 0bcf4cc21..d79dc327e 100644 --- a/lib/api.php +++ b/lib/api.php @@ -1218,7 +1218,12 @@ class ApiAction extends Action return User_group::staticGet($this->arg('id')); } else if ($this->arg('id')) { $nickname = common_canonical_nickname($this->arg('id')); - return User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + if (empty($local)) { + return null; + } else { + return User_group::staticGet('id', $local->id); + } } else if ($this->arg('group_id')) { // This is to ensure that a non-numeric user_id still // overrides screen_name even if it doesn't get used @@ -1227,14 +1232,24 @@ class ApiAction extends Action } } else if ($this->arg('group_name')) { $nickname = common_canonical_nickname($this->arg('group_name')); - return User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + if (empty($local)) { + return null; + } else { + return User_group::staticGet('id', $local->id); + } } } else if (is_numeric($id)) { return User_group::staticGet($id); } else { $nickname = common_canonical_nickname($id); - return User_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $nickname); + if (empty($local)) { + return null; + } else { + return User_group::staticGet('id', $local->id); + } } } -- cgit v1.2.3-54-g00ecf From 39a8e9d8e679cfd02e47fa93aa26373101515cf9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 10:30:37 -0800 Subject: Ensure that shortened URLs haven't accumulated whitespace when fetched by a plugin. Some shorteners have ended up inserting extra newlines when the string gets extracted from tidied HTML. --- lib/util.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/util.php b/lib/util.php index 9354431f2..9c50d9931 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1606,6 +1606,7 @@ function common_database_tablename($tablename) */ function common_shorten_url($long_url) { + $long_url = trim($long_url); $user = common_current_user(); if (empty($user)) { // common current user does not find a user when called from the XMPP daemon @@ -1620,7 +1621,7 @@ function common_shorten_url($long_url) return $long_url; }else{ //URL was shortened, so return the result - return $shortenedUrl; + return trim($shortenedUrl); } } -- cgit v1.2.3-54-g00ecf From 79c0d52daa92b60e8eed80fa9459367c26f97122 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 11:26:33 -0800 Subject: OStatus: save categories from the Atom entry as hashtags. --- classes/Notice.php | 37 +++++++++++++++-- lib/activity.php | 61 ++++++++++++++++++++++++++++- lib/distribqueuehandler.php | 12 ------ plugins/OStatus/classes/Ostatus_profile.php | 14 ++++++- 4 files changed, 106 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/classes/Notice.php b/classes/Notice.php index e8d5c45cb..46c5ebb37 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -121,6 +121,9 @@ class Notice extends Memcached_DataObject $result = parent::delete(); } + /** + * Extract #hashtags from this notice's content and save them to the database. + */ function saveTags() { /* extract all #hastags */ @@ -129,14 +132,22 @@ class Notice extends Memcached_DataObject return true; } + /* Add them to the database */ + return $this->saveKnownTags($match[1]); + } + + /** + * Record the given set of hash tags in the db for this notice. + * Given tag strings will be normalized and checked for dupes. + */ + function saveKnownTags($hashtags) + { //turn each into their canonical tag //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag - $hashtags = array(); - for($i=0; $isaveTag($hashtag); @@ -145,6 +156,10 @@ class Notice extends Memcached_DataObject return true; } + /** + * Record a single hash tag as associated with this notice. + * Tag format and uniqueness must be validated by caller. + */ function saveTag($hashtag) { $tag = new Notice_tag(); @@ -194,6 +209,8 @@ class Notice extends Memcached_DataObject * place of extracting @-replies from content. * array 'groups' list of group IDs to deliver to, in place of * extracting ! tags from content + * array 'tags' list of hashtag strings to save with the notice + * in place of extracting # tags from content * @fixme tag override * * @return Notice @@ -343,6 +360,8 @@ class Notice extends Memcached_DataObject $notice->blowOnInsert(); + // Save per-notice metadata... + if (isset($replies)) { $notice->saveKnownReplies($replies); } else { @@ -355,6 +374,16 @@ class Notice extends Memcached_DataObject $notice->saveGroups(); } + if (isset($tags)) { + $notice->saveKnownTags($tags); + } else { + $notice->saveTags(); + } + + // @fixme pass in data for URLs too? + $notice->saveUrls(); + + // Prepare inbox delivery, may be queued to background. $notice->distribute(); return $notice; diff --git a/lib/activity.php b/lib/activity.php index 3de5f62c7..e592aad6f 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -859,6 +859,7 @@ class Activity public $content; // HTML content of activity public $id; // ID of the activity public $title; // title of the activity + public $categories = array(); // list of AtomCategory objects /** * Turns a regular old Atom into a magical activity @@ -947,6 +948,14 @@ class Activity $this->summary = ActivityUtils::childContent($entry, 'summary'); $this->id = ActivityUtils::childContent($entry, 'id'); $this->content = ActivityUtils::getContent($entry); + + $catEls = $entry->getElementsByTagNameNS(self::ATOM, 'category'); + if ($catEls) { + for ($i = 0; $i < $catEls->length; $i++) { + $catEl = $catEls->item($i); + $this->categories[] = new AtomCategory($catEl); + } + } } /** @@ -1011,6 +1020,10 @@ class Activity $xs->raw($this->target->asString('activity:target')); } + foreach ($this->categories as $cat) { + $xs->raw($cat->asString()); + } + $xs->elementEnd('entry'); return $xs->getString(); @@ -1020,4 +1033,50 @@ class Activity { return ActivityUtils::child($element, $tag, $namespace); } -} \ No newline at end of file +} + +class AtomCategory +{ + public $term; + public $scheme; + public $label; + + function __construct($element=null) + { + if ($element && $element->attributes) { + $this->term = $this->extract($element, 'term'); + $this->scheme = $this->extract($element, 'scheme'); + $this->label = $this->extract($element, 'label'); + } + } + + protected function extract($element, $attrib) + { + $node = $element->attributes->getNamedItemNS(Activity::ATOM, $attrib); + if ($node) { + return trim($node->textContent); + } + $node = $element->attributes->getNamedItem($attrib); + if ($node) { + return trim($node->textContent); + } + return null; + } + + function asString() + { + $attribs = array(); + if ($this->term !== null) { + $attribs['term'] = $this->term; + } + if ($this->scheme !== null) { + $attribs['scheme'] = $this->scheme; + } + if ($this->label !== null) { + $attribs['label'] = $this->label; + } + $xs = new XMLStringer(); + $xs->element('category', $attribs); + return $xs->asString(); + } +} diff --git a/lib/distribqueuehandler.php b/lib/distribqueuehandler.php index dc183fb36..d2be7a92c 100644 --- a/lib/distribqueuehandler.php +++ b/lib/distribqueuehandler.php @@ -62,24 +62,12 @@ class DistribQueueHandler { // XXX: do we need to change this for remote users? - try { - $notice->saveTags(); - } catch (Exception $e) { - $this->logit($notice, $e); - } - try { $notice->addToInboxes(); } catch (Exception $e) { $this->logit($notice, $e); } - try { - $notice->saveUrls(); - } catch (Exception $e) { - $this->logit($notice, $e); - } - try { Event::handle('EndNoticeSave', array($notice)); // Enqueue for other handlers diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 300e38c05..d66939399 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -638,7 +638,9 @@ class Ostatus_profile extends Memcached_DataObject 'uri' => $sourceUri, 'rendered' => $rendered, 'replies' => array(), - 'groups' => array()); + 'groups' => array(), + 'tags' => array()); + // Check for optional attributes... @@ -673,6 +675,16 @@ class Ostatus_profile extends Memcached_DataObject } } + // Atom categories <-> hashtags + foreach ($activity->categories as $cat) { + if ($cat->term) { + $term = common_canonical_tag($cat->term); + if ($term) { + $options['tags'][] = $term; + } + } + } + try { $saved = Notice::saveNew($oprofile->profile_id, $content, -- cgit v1.2.3-54-g00ecf From a8d0c8d8efca2f0ecd4203184c44c73ff11309fb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 11:53:39 -0800 Subject: Normalize nickname case on login; fixes failed logins where people were typing MixedCase nicknames (if browser saved this form, it would never work again until clearing the saved form data; very icky.) --- lib/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/util.php b/lib/util.php index dd8189a58..84928ec48 100644 --- a/lib/util.php +++ b/lib/util.php @@ -134,7 +134,7 @@ function common_check_user($nickname, $password) $authenticatedUser = false; if (Event::handle('StartCheckPassword', array($nickname, $password, &$authenticatedUser))) { - $user = User::staticGet('nickname', $nickname); + $user = User::staticGet('nickname', common_canonical_nickname($nickname)); if (!empty($user)) { if (!empty($password)) { // never allow login with blank password if (0 == strcmp(common_munge_password($password, $user->id), -- cgit v1.2.3-54-g00ecf From b5b5184c885e74d9532409a5962f851c4baf41c4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 13:02:08 -0800 Subject: OStatus: fix remote groups to work with new user_groups/local_groups split. - fix generation so we get the profile info (what's available so far) - use id instead of nickname for group join/leave forms so we can join/leave remote groups while the rest of the groups UI remains limited to local groups (plugins are responsible for making sure remote notifications and permission checks are done) - fix remote notification when joining group through OStatus's remote subscribe form --- actions/joingroup.php | 39 ++++++++++++++++------------- actions/leavegroup.php | 39 ++++++++++++++++------------- classes/User_group.php | 2 +- lib/activity.php | 7 ++++-- lib/joinform.php | 2 +- lib/leaveform.php | 2 +- lib/router.php | 3 +++ plugins/OStatus/actions/ostatussub.php | 14 ++++++++--- plugins/OStatus/classes/Ostatus_profile.php | 26 +++++++++++-------- 9 files changed, 79 insertions(+), 55 deletions(-) (limited to 'lib') diff --git a/actions/joingroup.php b/actions/joingroup.php index ba642f712..f87e5dae2 100644 --- a/actions/joingroup.php +++ b/actions/joingroup.php @@ -62,30 +62,33 @@ class JoingroupAction extends Action } $nickname_arg = $this->trimmed('nickname'); - $nickname = common_canonical_nickname($nickname_arg); - - // Permanent redirect on non-canonical nickname + $id = intval($this->arg('id')); + if ($id) { + $this->group = User_group::staticGet('id', $id); + } else if ($nickname_arg) { + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + common_redirect(common_local_url('leavegroup', $args), 301); + return false; + } - if ($nickname_arg != $nickname) { - $args = array('nickname' => $nickname); - common_redirect(common_local_url('joingroup', $args), 301); - return false; - } + $local = Local_group::staticGet('nickname', $nickname); - if (!$nickname) { - $this->clientError(_('No nickname.'), 404); - return false; - } - - $local = Local_group::staticGet('nickname', $nickname); + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } - if (!$local) { - $this->clientError(_('No such group.'), 404); + $this->group = User_group::staticGet('id', $local->group_id); + } else { + $this->clientError(_('No nickname or ID.'), 404); return false; } - $this->group = User_group::staticGet('id', $local->group_id); - if (!$this->group) { $this->clientError(_('No such group.'), 404); return false; diff --git a/actions/leavegroup.php b/actions/leavegroup.php index 222d4c1b4..329b5aafe 100644 --- a/actions/leavegroup.php +++ b/actions/leavegroup.php @@ -62,30 +62,33 @@ class LeavegroupAction extends Action } $nickname_arg = $this->trimmed('nickname'); - $nickname = common_canonical_nickname($nickname_arg); - - // Permanent redirect on non-canonical nickname + $id = intval($this->arg('id')); + if ($id) { + $this->group = User_group::staticGet('id', $id); + } else if ($nickname_arg) { + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + common_redirect(common_local_url('leavegroup', $args), 301); + return false; + } - if ($nickname_arg != $nickname) { - $args = array('nickname' => $nickname); - common_redirect(common_local_url('leavegroup', $args), 301); - return false; - } + $local = Local_group::staticGet('nickname', $nickname); - if (!$nickname) { - $this->clientError(_('No nickname.'), 404); - return false; - } - - $local = Local_group::staticGet('nickname', $nickname); + if (!$local) { + $this->clientError(_('No such group.'), 404); + return false; + } - if (!$local) { - $this->clientError(_('No such group.'), 404); + $this->group = User_group::staticGet('id', $local->group_id); + } else { + $this->clientError(_('No nickname or ID.'), 404); return false; } - $this->group = User_group::staticGet('id', $local->group_id); - if (!$this->group) { $this->clientError(_('No such group.'), 404); return false; diff --git a/classes/User_group.php b/classes/User_group.php index f24bef764..2a58d8895 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -404,7 +404,7 @@ class User_group extends Memcached_DataObject $xs = new XMLStringer(true); $xs->elementStart('activity:subject'); - $xs->element('activity:object', null, 'http://activitystrea.ms/schema/1.0/group'); + $xs->element('activity:object-type', null, 'http://activitystrea.ms/schema/1.0/group'); $xs->element('id', null, $this->permalink()); $xs->element('title', null, $this->getBestName()); $xs->element( diff --git a/lib/activity.php b/lib/activity.php index e592aad6f..4e7ff5336 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -610,7 +610,10 @@ class ActivityObject $object->id = $profile->getUri(); $object->title = $profile->getBestName(); $object->link = $profile->profileurl; - $object->avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + if ($avatar) { + $object->avatar = $avatar->displayUrl(); + } if (isset($profile->lat) && isset($profile->lon)) { $object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon; @@ -664,7 +667,7 @@ class ActivityObject 'rel' => 'avatar', 'href' => empty($this->avatar) ? Avatar::defaultImage(AVATAR_PROFILE_SIZE) - : $this->avatar->displayUrl() + : $this->avatar ), null ); diff --git a/lib/joinform.php b/lib/joinform.php index aefb553aa..aa8bc20e2 100644 --- a/lib/joinform.php +++ b/lib/joinform.php @@ -100,7 +100,7 @@ class JoinForm extends Form function action() { return common_local_url('joingroup', - array('nickname' => $this->group->nickname)); + array('id' => $this->group->id)); } /** diff --git a/lib/leaveform.php b/lib/leaveform.php index e63d96ee8..5469b5704 100644 --- a/lib/leaveform.php +++ b/lib/leaveform.php @@ -100,7 +100,7 @@ class LeaveForm extends Form function action() { return common_local_url('leavegroup', - array('nickname' => $this->group->nickname)); + array('id' => $this->group->id)); } /** diff --git a/lib/router.php b/lib/router.php index 987d0152e..0e15d83b9 100644 --- a/lib/router.php +++ b/lib/router.php @@ -247,6 +247,9 @@ class Router $m->connect('group/:nickname/'.$v, array('action' => $v.'group'), array('nickname' => '[a-zA-Z0-9]+')); + $m->connect('group/:id/id/'.$v, + array('action' => $v.'group'), + array('id' => '[0-9]+')); } foreach (array('members', 'logo', 'rss', 'designsettings') as $n) { diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index 12832cdcf..aae22f868 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -333,10 +333,18 @@ class OStatusSubAction extends Action $group = $this->oprofile->localGroup(); if ($user->isMember($group)) { $this->showForm(_m('Already a member!')); - } elseif (Group_member::join($this->oprofile->group_id, $user->id)) { - $this->successGroup(); + return; + } + if (Event::handle('StartJoinGroup', array($group, $user))) { + $ok = Group_member::join($this->oprofile->group_id, $user->id); + if ($ok) { + Event::handle('EndJoinGroup', array($group, $user)); + $this->successGroup(); + } else { + $this->showForm(_m('Remote group join failed!')); + } } else { - $this->showForm(_m('Remote group join failed!')); + $this->showForm(_m('Remote group join aborted!')); } } else { $local = $this->oprofile->localProfile(); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index d66939399..f23017077 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -162,7 +162,7 @@ class Ostatus_profile extends Memcached_DataObject 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif'); - $extension = pathinfo(parse_url($avatarHref, PHP_URL_PATH), PATHINFO_EXTENSION); + $extension = pathinfo(parse_url($object->avatar, PHP_URL_PATH), PATHINFO_EXTENSION); if (isset($map[$extension])) { // @fixme this ain't used/saved yet $object->avatarType = $map[$extension]; @@ -332,6 +332,9 @@ class Ostatus_profile extends Memcached_DataObject */ public function unsubscribe() { $feedsub = FeedSub::staticGet('uri', $this->feeduri); + if (!$feedsub) { + return true; + } if ($feedsub->sub_state == 'active') { return $feedsub->unsubscribe(); } else if ($feedsub->sub_state == '' || $feedsub->sub_state == 'inactive' || $feedsub->sub_state == 'unsubscribe') { @@ -356,7 +359,7 @@ class Ostatus_profile extends Memcached_DataObject $count = $this->localProfile()->subscriberCount(); } if ($count == 0) { - common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $oprofile->feeduri"); + common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $this->feeduri"); $this->unsubscribe(); return true; } else { @@ -1095,8 +1098,8 @@ class Ostatus_profile extends Memcached_DataObject if ($object->type == ActivityObject::PERSON) { $profile = new Profile(); + $profile->created = common_sql_now(); self::updateProfile($profile, $object, $hints); - $profile->created = common_sql_now(); $oprofile->profile_id = $profile->insert(); if (!$oprofile->profile_id) { @@ -1104,6 +1107,7 @@ class Ostatus_profile extends Memcached_DataObject } } else { $group = new User_group(); + $group->uri = $homeuri; $group->created = common_sql_now(); self::updateGroup($group, $object, $hints); @@ -1183,19 +1187,19 @@ class Ostatus_profile extends Memcached_DataObject { $orig = clone($group); - // @fixme need to make nick unique etc *hack hack* $group->nickname = self::getActivityObjectNickname($object, $hints); $group->fullname = $object->title; - // @fixme no canonical profileurl; using homepage instead for now - $group->homepage = $object->id; + if (!empty($object->link)) { + $group->mainpage = $object->link; + } else if (array_key_exists('profileurl', $hints)) { + $group->mainpage = $hints['profileurl']; + } - // @fixme homepage - // @fixme bio - // @fixme tags/categories - // @fixme location? // @todo tags from categories - // @todo lat/lon/location? + $group->description = self::getActivityObjectBio($object, $hints); + $group->location = self::getActivityObjectLocation($object, $hints); + $group->homepage = self::getActivityObjectHomepage($object, $hints); if ($group->id) { common_log(LOG_DEBUG, "Updating OStatus group $group->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true)); -- cgit v1.2.3-54-g00ecf From e61edb55d9c92c0c93de3a591928f4760b6cea16 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 25 Feb 2010 13:34:43 -0800 Subject: Rationalize group activity stuff --- classes/User_group.php | 46 ++++++++++++++++++++++++------------- lib/activity.php | 61 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/classes/User_group.php b/classes/User_group.php index f24bef764..7240e2703 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -399,25 +399,41 @@ class User_group extends Memcached_DataObject return $xs->getString(); } + /** + * Returns an XML string fragment with group information as an + * Activity Streams element. + * + * Assumes that 'activity' namespace has been previously defined. + * + * @return string + */ function asActivitySubject() { - $xs = new XMLStringer(true); + return $this->asActivityNoun('subject'); + } - $xs->elementStart('activity:subject'); - $xs->element('activity:object', null, 'http://activitystrea.ms/schema/1.0/group'); - $xs->element('id', null, $this->permalink()); - $xs->element('title', null, $this->getBestName()); - $xs->element( - 'link', array( - 'rel' => 'avatar', - 'href' => empty($this->homepage_logo) - ? User_group::defaultLogo(AVATAR_PROFILE_SIZE) - : $this->homepage_logo - ) - ); - $xs->elementEnd('activity:subject'); + /** + * Returns an XML string fragment with group information as an + * Activity Streams noun object with the given element type. + * + * Assumes that 'activity', 'georss', and 'poco' namespace has been + * previously defined. + * + * @param string $element one of 'actor', 'subject', 'object', 'target' + * + * @return string + */ + function asActivityNoun($element) + { + $noun = ActivityObject::fromGroup($this); + return $noun->asString('activity:' . $element); + } - return $xs->getString(); + function getAvatar() + { + return empty($this->homepage_logo) + ? User_group::defaultLogo(AVATAR_PROFILE_SIZE) + : $this->homepage_logo; } static function register($fields) { diff --git a/lib/activity.php b/lib/activity.php index e592aad6f..0863cf8fa 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -223,6 +223,37 @@ class PoCo return $poco; } + function fromGroup($group) + { + if (empty($group)) { + return null; + } + + $poco = new PoCo(); + + $poco->preferredUsername = $group->nickname; + $poco->displayName = $group->getBestName(); + + $poco->note = $group->description; + + $paddy = new PoCoAddress(); + $paddy->formatted = $group->location; + $poco->address = $paddy; + + if (!empty($group->homepage)) { + array_push( + $poco->urls, + new PoCoURL( + 'homepage', + $group->homepage, + true + ) + ); + } + + return $poco; + } + function getPrimaryURL() { foreach ($this->urls as $url) { @@ -621,6 +652,21 @@ class ActivityObject return $object; } + static function fromGroup($group) + { + $object = new ActivityObject(); + + $object->type = ActivityObject::GROUP; + $object->id = $group->getUri(); + $object->title = $group->getBestName(); + $object->link = $group->getUri(); + $object->avatar = $group->getAvatar(); + + $object->poco = PoCo::fromGroup($group); + + return $object; + } + function asString($tag='activity:object') { $xs = new XMLStringer(true); @@ -656,8 +702,7 @@ class ActivityObject ); } - if ($this->type == ActivityObject::PERSON - || $this->type == ActivityObject::GROUP) { + if ($this->type == ActivityObject::PERSON) { $xs->element( 'link', array( 'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype, @@ -670,6 +715,18 @@ class ActivityObject ); } + // XXX: Gotta figure out mime-type! Gar. + + if ($this->type == ActivityObject::GROUP) { + $xs->element( + 'link', array( + 'rel' => 'avatar', + 'href' => $this->avatar + ), + null + ); + } + if (!empty($this->geopoint)) { $xs->element( 'georss:point', -- cgit v1.2.3-54-g00ecf From 76216af806b1a683e1b885724bc68906214209c2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 25 Feb 2010 18:39:55 -0500 Subject: Add an hcard action A dedicated hcard action for users. Our profile page includes an hcard, but it's so full of other hcards that it's ambiguous which one is the "real" one. So, this one make sense for meaning, "This is my hcard." --- actions/hcard.php | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/router.php | 4 +- 2 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 actions/hcard.php (limited to 'lib') diff --git a/actions/hcard.php b/actions/hcard.php new file mode 100644 index 000000000..55d0f65c8 --- /dev/null +++ b/actions/hcard.php @@ -0,0 +1,120 @@ +. + * + * @category Personal + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * User profile page + * + * @category Personal + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class HcardAction extends Action +{ + var $user; + var $profile; + + function prepare($args) + { + parent::prepare($args); + + $nickname_arg = $this->arg('nickname'); + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + common_redirect(common_local_url('hcard', $args), 301); + return false; + } + + $this->user = User::staticGet('nickname', $nickname); + + if (!$this->user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->profile = $this->user->getProfile(); + + if (!$this->profile) { + $this->serverError(_('User has no profile.')); + return false; + } + + return true; + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function title() + { + return $this->profile->getBestName(); + } + + function showContent() + { + $up = new ShortUserProfile($this, $this->user, $this->profile); + $up->show(); + } + + function showHeader() + { + return; + } + + function showAside() + { + return; + } + + function showSecondaryNav() + { + return; + } +} + +class ShortUserProfile extends UserProfile +{ + function showEntityActions() + { + return; + } +} \ No newline at end of file diff --git a/lib/router.php b/lib/router.php index 987d0152e..16b2847d3 100644 --- a/lib/router.php +++ b/lib/router.php @@ -668,7 +668,7 @@ class Router foreach (array('subscriptions', 'subscribers', 'all', 'foaf', 'xrds', - 'replies', 'microsummary') as $a) { + 'replies', 'microsummary', 'hcard') as $a) { $m->connect($a, array('action' => $a, 'nickname' => $nickname)); @@ -734,7 +734,7 @@ class Router foreach (array('subscriptions', 'subscribers', 'nudge', 'all', 'foaf', 'xrds', - 'replies', 'inbox', 'outbox', 'microsummary') as $a) { + 'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) { $m->connect(':nickname/'.$a, array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); -- cgit v1.2.3-54-g00ecf From 593885f98c206a3acffdb179feae544e05a50ef2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 23:52:34 +0000 Subject: Tweak common_url_to_nickname to take the last path component; fixes pulling nicks from Google profile pages (path is "/profile/") --- lib/util.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/util.php b/lib/util.php index d1c78f7d0..8381bc63c 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1698,7 +1698,8 @@ function common_url_to_nickname($url) # Strip starting, ending slashes $path = preg_replace('@/$@', '', $parts['path']); $path = preg_replace('@^/@', '', $path); - if (strpos($path, '/') === false) { + $path = basename($path); + if ($path) { return common_nicknamize($path); } } -- cgit v1.2.3-54-g00ecf From 7922edb5b6c4ad55a1b81fa16c93e5656b676b26 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 25 Feb 2010 16:06:49 -0800 Subject: Add lots of fun avatars to our Atom output --- classes/Notice.php | 1 + lib/activity.php | 146 ++++++++++++++++++++++------ lib/atomnoticefeed.php | 5 + plugins/OStatus/classes/Ostatus_profile.php | 3 +- 4 files changed, 124 insertions(+), 31 deletions(-) (limited to 'lib') diff --git a/classes/Notice.php b/classes/Notice.php index 46c5ebb37..ac4640534 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1096,6 +1096,7 @@ class Notice extends Memcached_DataObject 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0', 'xmlns:georss' => 'http://www.georss.org/georss', 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/', + 'xmlns:media' => 'http://purl.org/syndication/atommedia', 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0', 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0'); } else { diff --git a/lib/activity.php b/lib/activity.php index 30cdc5a56..0f30e8bf5 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -465,6 +465,57 @@ class ActivityUtils } } +// XXX: Arg! This wouldn't be necessary if we used Avatars conistently +class AvatarLink +{ + public $url; + public $type; + public $size; + public $width; + + static function fromAvatar($avatar) + { + if (empty($avatar)) { + return null; + } + $alink = new AvatarLink(); + $alink->type = $avatar->mediatype; + $alink->height = $avatar->mediatype; + $alink->width = $avatar->width; + $alink->url = $avatar->displayUrl(); + return $alink; + } + + static function fromFilename($filename, $size) + { + $alink = new AvatarLink(); + $alink->url = $filename; + $alink->height = $size; + if (!empty($filename)) { + $alink->width = $size; + $alink->type = self::mediatype($filename); + } else { + $alink->url = User_group::defaultLogo($size); + $alink->type = 'image/png'; + } + return $alink; + } + + // yuck! + static function mediatype($filename) { + $ext = strtolower(end(explode('.', $filename))); + if ($ext == 'jpeg') { + $ext = 'jpg'; + } + // hope we don't support any others + $types = array('png', 'gif', 'jpg', 'jpeg'); + if (in_array($ext, $types)) { + return 'image/' . $ext; + } + return null; + } +} + /** * A noun-ish thing in the activity universe * @@ -521,7 +572,7 @@ class ActivityObject public $content; public $link; public $source; - public $avatar; + public $avatarLinks = array(); public $geopoint; public $poco; public $displayName; @@ -641,13 +692,40 @@ class ActivityObject $object->id = $profile->getUri(); $object->title = $profile->getBestName(); $object->link = $profile->profileurl; - $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $object->avatar = $avatar->displayUrl(); + + $orig = $profile->getOriginalAvatar(); + + if (!empty($orig)) { + $object->avatarLinks[] = AvatarLink::fromAvatar($orig); + } + + $sizes = array( + AVATAR_PROFILE_SIZE, + AVATAR_STREAM_SIZE, + AVATAR_MINI_SIZE + ); + + foreach ($sizes as $size) { + + $alink = null; + $avatar = $profile->getAvatar($size); + + if (!empty($avatar)) { + $alink = AvatarLink::fromAvatar($avatar); + } else { + $alink = new AvatarLink(); + $alink->type = 'image/png'; + $alink->height = $size; + $alink->width = $size; + $alink->url = Avatar::defaultImage($size); + } + + $object->avatarLinks[] = $alink; } if (isset($profile->lat) && isset($profile->lon)) { - $object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon; + $object->geopoint = (float)$profile->lat + . ' ' . (float)$profile->lon; } $object->poco = PoCo::fromProfile($profile); @@ -663,13 +741,28 @@ class ActivityObject $object->id = $group->getUri(); $object->title = $group->getBestName(); $object->link = $group->getUri(); - $object->avatar = $group->getAvatar(); + + $object->avatarLinks[] = AvatarLink::fromFilename( + $group->homepage_logo, + AVATAR_PROFILE_SIZE + ); + + $object->avatarLinks[] = AvatarLink::fromFilename( + $group->stream_logo, + AVATAR_STREAM_SIZE + ); + + $object->avatarLinks[] = AvatarLink::fromFilename( + $group->mini_logo, + AVATAR_MINI_SIZE + ); $object->poco = PoCo::fromGroup($group); return $object; } + function asString($tag='activity:object') { $xs = new XMLStringer(true); @@ -705,29 +798,21 @@ class ActivityObject ); } - if ($this->type == ActivityObject::PERSON) { - $xs->element( - 'link', array( - 'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype, - 'rel' => 'avatar', - 'href' => empty($this->avatar) - ? Avatar::defaultImage(AVATAR_PROFILE_SIZE) - : $this->avatar - ), - null - ); - } - - // XXX: Gotta figure out mime-type! Gar. - - if ($this->type == ActivityObject::GROUP) { - $xs->element( - 'link', array( - 'rel' => 'avatar', - 'href' => $this->avatar - ), - null - ); + if ($this->type == ActivityObject::PERSON + || $this->type == ActivityObject::GROUP) { + + foreach ($this->avatarLinks as $avatar) { + $xs->element( + 'link', array( + 'rel' => 'avatar', + 'type' => $avatar->type, + 'media:width' => $avatar->width, + 'media:height' => $avatar->height, + 'href' => $avatar->url + ), + null + ); + } } if (!empty($this->geopoint)) { @@ -1038,7 +1123,8 @@ class Activity '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:poco' => 'http://portablecontacts.net/spec/1.0', + 'xmlns:media' => 'http://purl.org/syndication/atommedia'); } else { $attrs = array(); } diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index d2bf2a416..3c3556cb9 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -64,6 +64,11 @@ class AtomNoticeFeed extends Atom10Feed 'http://activitystrea.ms/spec/1.0/' ); + $this->addNamespace( + 'media', + 'http://purl.org/syndication/atommedia' + ); + $this->addNamespace( 'poco', 'http://portablecontacts.net/spec/1.0' diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index f23017077..7026d82f2 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -401,7 +401,8 @@ class Ostatus_profile extends Memcached_DataObject '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:poco' => 'http://portablecontacts.net/spec/1.0', + 'xmlns:media' => 'http://purl.org/syndication/atommedia'); $entry = new XMLStringer(); $entry->elementStart('entry', $attributes); -- cgit v1.2.3-54-g00ecf From 207cc8907475cb75091e6f1a05fb096fdf477b48 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 25 Feb 2010 18:06:03 -0800 Subject: Set avatar height correctly --- lib/activity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/activity.php b/lib/activity.php index 0f30e8bf5..161232c42 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -480,7 +480,7 @@ class AvatarLink } $alink = new AvatarLink(); $alink->type = $avatar->mediatype; - $alink->height = $avatar->mediatype; + $alink->height = $avatar->height; $alink->width = $avatar->width; $alink->url = $avatar->displayUrl(); return $alink; -- cgit v1.2.3-54-g00ecf From 2feb09f4346e54805f68a9f677f5a94340875d8e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 18:51:44 -0800 Subject: OStatus: pull best-sized avatar image (96x96 if found, otherwise largest, otherwise if none labeled takes the first) --- lib/activity.php | 43 +++++++++++++++++++++++++++-- plugins/OStatus/classes/Ostatus_profile.php | 16 +++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/activity.php b/lib/activity.php index 0f30e8bf5..b64a82f0a 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -360,6 +360,25 @@ class ActivityUtils return null; } + static function getLinks(DOMNode $element, $rel, $type=null) + { + $links = $element->getElementsByTagnameNS(self::ATOM, self::LINK); + $out = array(); + + foreach ($links as $link) { + + $linkRel = $link->getAttribute(self::REL); + $linkType = $link->getAttribute(self::TYPE); + + if ($linkRel == $rel && + (is_null($type) || $linkType == $type)) { + $out[] = $link; + } + } + + return $out; + } + /** * Gets the first child element with the given tag * @@ -472,6 +491,24 @@ class AvatarLink public $type; public $size; public $width; + public $height; + + function __construct($element=null) + { + if ($element) { + // @fixme use correct namespaces + $this->url = $element->getAttribute('href'); + $this->type = $element->getAttribute('type'); + $width = $element->getAttribute('media:width'); + if ($width != null) { + $this->width = intval($width); + } + $height = $element->getAttribute('media:height'); + if ($height != null) { + $this->height = intval($height); + } + } + } static function fromAvatar($avatar) { @@ -640,8 +677,10 @@ class ActivityObject if ($this->type == self::PERSON || $this->type == self::GROUP) { $this->displayName = $this->title; - // @fixme we may have multiple avatars with different resolutions specified - $this->avatar = ActivityUtils::getLink($element, 'avatar'); + $avatars = ActivityUtils::getLinks($element, 'avatar'); + foreach ($avatars as $link) { + $this->avatarLinks[] = new AvatarLink($link); + } $this->poco = new PoCo($element); } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index ad9170f5b..9b7be4e9a 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -912,8 +912,20 @@ class Ostatus_profile extends Memcached_DataObject protected static function getActivityObjectAvatar($object, $hints=array()) { - if ($object->avatar) { - return $object->avatar; + if ($object->avatarLinks) { + $best = false; + // Take the exact-size avatar, or the largest avatar, or the first avatar if all sizeless + foreach ($object->avatarLinks as $avatar) { + if ($avatar->width == AVATAR_PROFILE_SIZE && $avatar->height = AVATAR_PROFILE_SIZE) { + // Exact match! + $best = $avatar; + break; + } + if (!$best || $avatar->width > $best->width) { + $best = $avatar; + } + } + return $best->url; } else if (array_key_exists('avatar', $hints)) { return $hints['avatar']; } -- cgit v1.2.3-54-g00ecf From f9e5acbfa7a8e210e262cbc0fd1f728abdcafed9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 25 Feb 2010 21:53:18 -0500 Subject: remove linkback from default plugins --- lib/default.php | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/default.php b/lib/default.php index bb7708bfc..d849055c2 100644 --- a/lib/default.php +++ b/lib/default.php @@ -278,7 +278,6 @@ $default = 'TightUrl' => array('shortenerName' => '2tu.us', 'freeService' => true,'serviceUrl'=>'http://2tu.us/?save=y&url=%1$s'), 'Geonames' => null, 'Mapstraction' => null, - 'Linkback' => null, 'WikiHashtags' => null, 'OpenID' => null), ), -- cgit v1.2.3-54-g00ecf From 417f5d653fb2053cd6194900eba591d42ebfcb0d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 25 Feb 2010 23:27:20 -0500 Subject: update to beta6 --- lib/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/common.php b/lib/common.php index 68723955e..2dbe3b3c5 100644 --- a/lib/common.php +++ b/lib/common.php @@ -22,7 +22,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } //exit with 200 response, if this is checking fancy from the installer if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } -define('STATUSNET_VERSION', '0.9.0beta5'); +define('STATUSNET_VERSION', '0.9.0beta6'); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('STATUSNET_CODENAME', 'Stand'); -- cgit v1.2.3-54-g00ecf From 08c17bfcaa6bce08d547afa719601139fcb0e8c6 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 21:06:16 -0800 Subject: try/catch on omb profile pings --- lib/profilequeuehandler.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/profilequeuehandler.php b/lib/profilequeuehandler.php index e8a00aef3..6ce93229b 100644 --- a/lib/profilequeuehandler.php +++ b/lib/profilequeuehandler.php @@ -39,7 +39,11 @@ class ProfileQueueHandler extends QueueHandler if (Event::handle('StartBroadcastProfile', array($profile))) { require_once(INSTALLDIR.'/lib/omb.php'); - omb_broadcast_profile($profile); + try { + omb_broadcast_profile($profile); + } catch (Exception $e) { + common_log(LOG_ERR, "Failed sending OMB profiles: " . $e->getMessage()); + } } Event::handle('EndBroadcastProfile', array($profile)); return true; -- cgit v1.2.3-54-g00ecf From b573c5e2603dab212bea99bc30beebcff91e2195 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 25 Feb 2010 21:27:46 -0800 Subject: Fix for group timeline feeds by name --- lib/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api.php b/lib/api.php index d79dc327e..2af150ab9 100644 --- a/lib/api.php +++ b/lib/api.php @@ -1248,7 +1248,7 @@ class ApiAction extends Action if (empty($local)) { return null; } else { - return User_group::staticGet('id', $local->id); + return User_group::staticGet('id', $local->group_id); } } } -- cgit v1.2.3-54-g00ecf From eabae96fb40a51002a90961b3092b38d29b1f5f9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 25 Feb 2010 22:02:40 -0800 Subject: Get ApiAction autoloading properly --- lib/api.php | 1356 ----------------------------------------------------- lib/apiaction.php | 1356 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1356 insertions(+), 1356 deletions(-) delete mode 100644 lib/api.php create mode 100644 lib/apiaction.php (limited to 'lib') diff --git a/lib/api.php b/lib/api.php deleted file mode 100644 index 2af150ab9..000000000 --- a/lib/api.php +++ /dev/null @@ -1,1356 +0,0 @@ -. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Dan Moore - * @author Evan Prodromou - * @author Jeffery To - * @author Toby Inkster - * @author Zach Copley - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -/** - * Contains most of the Twitter-compatible API output functions. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Dan Moore - * @author Evan Prodromou - * @author Jeffery To - * @author Toby Inkster - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -class ApiAction extends Action -{ - const READ_ONLY = 1; - const READ_WRITE = 2; - - var $format = null; - var $user = null; - var $auth_user = null; - var $page = null; - var $count = null; - var $max_id = null; - var $since_id = null; - var $since = null; - - var $access = self::READ_ONLY; // read (default) or read-write - - /** - * Initialization. - * - * @param array $args Web and URL arguments - * - * @return boolean false if user doesn't exist - */ - - function prepare($args) - { - StatusNet::setApi(true); // reduce exception reports to aid in debugging - parent::prepare($args); - - $this->format = $this->arg('format'); - $this->page = (int)$this->arg('page', 1); - $this->count = (int)$this->arg('count', 20); - $this->max_id = (int)$this->arg('max_id', 0); - $this->since_id = (int)$this->arg('since_id', 0); - $this->since = $this->arg('since'); - - return true; - } - - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - - function handle($args) - { - parent::handle($args); - } - - /** - * Overrides XMLOutputter::element to write booleans as strings (true|false). - * See that method's documentation for more info. - * - * @param string $tag Element type or tagname - * @param array $attrs Array of element attributes, as - * key-value pairs - * @param string $content string content of the element - * - * @return void - */ - function element($tag, $attrs=null, $content=null) - { - if (is_bool($content)) { - $content = ($content ? 'true' : 'false'); - } - - return parent::element($tag, $attrs, $content); - } - - function twitterUserArray($profile, $get_notice=false) - { - $twitter_user = array(); - - $twitter_user['id'] = intval($profile->id); - $twitter_user['name'] = $profile->getBestName(); - $twitter_user['screen_name'] = $profile->nickname; - $twitter_user['location'] = ($profile->location) ? $profile->location : null; - $twitter_user['description'] = ($profile->bio) ? $profile->bio : null; - - $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); - $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : - Avatar::defaultImage(AVATAR_STREAM_SIZE); - - $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; - $twitter_user['protected'] = false; # not supported by StatusNet yet - $twitter_user['followers_count'] = $profile->subscriberCount(); - - $design = null; - $user = $profile->getUser(); - - // Note: some profiles don't have an associated user - - $defaultDesign = Design::siteDesign(); - - if (!empty($user)) { - $design = $user->getDesign(); - } - - if (empty($design)) { - $design = $defaultDesign; - } - - $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor); - $twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor); - $twitter_user['profile_text_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $color = Design::toWebColor(empty($design->linkcolor) ? $defaultDesign->linkcolor : $design->linkcolor); - $twitter_user['profile_link_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $color = Design::toWebColor(empty($design->sidebarcolor) ? $defaultDesign->sidebarcolor : $design->sidebarcolor); - $twitter_user['profile_sidebar_fill_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $twitter_user['profile_sidebar_border_color'] = ''; - - $twitter_user['friends_count'] = $profile->subscriptionCount(); - - $twitter_user['created_at'] = $this->dateTwitter($profile->created); - - $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! - - $timezone = 'UTC'; - - if (!empty($user) && $user->timezone) { - $timezone = $user->timezone; - } - - $t = new DateTime; - $t->setTimezone(new DateTimeZone($timezone)); - - $twitter_user['utc_offset'] = $t->format('Z'); - $twitter_user['time_zone'] = $timezone; - - $twitter_user['profile_background_image_url'] - = empty($design->backgroundimage) - ? '' : ($design->disposition & BACKGROUND_ON) - ? Design::url($design->backgroundimage) : ''; - - $twitter_user['profile_background_tile'] - = empty($design->disposition) - ? '' : ($design->disposition & BACKGROUND_TILE) ? 'true' : 'false'; - - $twitter_user['statuses_count'] = $profile->noticeCount(); - - // Is the requesting user following this user? - $twitter_user['following'] = false; - $twitter_user['notifications'] = false; - - if (isset($this->auth_user)) { - - $twitter_user['following'] = $this->auth_user->isSubscribed($profile); - - // Notifications on? - $sub = Subscription::pkeyGet(array('subscriber' => - $this->auth_user->id, - 'subscribed' => $profile->id)); - - if ($sub) { - $twitter_user['notifications'] = ($sub->jabber || $sub->sms); - } - } - - if ($get_notice) { - $notice = $profile->getCurrentNotice(); - if ($notice) { - # don't get user! - $twitter_user['status'] = $this->twitterStatusArray($notice, false); - } - } - - return $twitter_user; - } - - function twitterStatusArray($notice, $include_user=true) - { - $base = $this->twitterSimpleStatusArray($notice, $include_user); - - if (!empty($notice->repeat_of)) { - $original = Notice::staticGet('id', $notice->repeat_of); - if (!empty($original)) { - $original_array = $this->twitterSimpleStatusArray($original, $include_user); - $base['retweeted_status'] = $original_array; - } - } - - return $base; - } - - function twitterSimpleStatusArray($notice, $include_user=true) - { - $profile = $notice->getProfile(); - - $twitter_status = array(); - $twitter_status['text'] = $notice->content; - $twitter_status['truncated'] = false; # Not possible on StatusNet - $twitter_status['created_at'] = $this->dateTwitter($notice->created); - $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? - intval($notice->reply_to) : null; - $twitter_status['source'] = $this->sourceLink($notice->source); - $twitter_status['id'] = intval($notice->id); - - $replier_profile = null; - - if ($notice->reply_to) { - $reply = Notice::staticGet(intval($notice->reply_to)); - if ($reply) { - $replier_profile = $reply->getProfile(); - } - } - - $twitter_status['in_reply_to_user_id'] = - ($replier_profile) ? intval($replier_profile->id) : null; - $twitter_status['in_reply_to_screen_name'] = - ($replier_profile) ? $replier_profile->nickname : null; - - if (isset($notice->lat) && isset($notice->lon)) { - // This is the format that GeoJSON expects stuff to be in - $twitter_status['geo'] = array('type' => 'Point', - 'coordinates' => array((float) $notice->lat, - (float) $notice->lon)); - } else { - $twitter_status['geo'] = null; - } - - if (isset($this->auth_user)) { - $twitter_status['favorited'] = $this->auth_user->hasFave($notice); - } else { - $twitter_status['favorited'] = false; - } - - // Enclosures - $attachments = $notice->attachments(); - - if (!empty($attachments)) { - - $twitter_status['attachments'] = array(); - - foreach ($attachments as $attachment) { - if ($attachment->isEnclosure()) { - $enclosure = array(); - $enclosure['url'] = $attachment->url; - $enclosure['mimetype'] = $attachment->mimetype; - $enclosure['size'] = $attachment->size; - $twitter_status['attachments'][] = $enclosure; - } - } - } - - if ($include_user && $profile) { - # Don't get notice (recursive!) - $twitter_user = $this->twitterUserArray($profile, false); - $twitter_status['user'] = $twitter_user; - } - - return $twitter_status; - } - - function twitterGroupArray($group) - { - $twitter_group=array(); - $twitter_group['id']=$group->id; - $twitter_group['url']=$group->permalink(); - $twitter_group['nickname']=$group->nickname; - $twitter_group['fullname']=$group->fullname; - $twitter_group['homepage_url']=$group->homepage_url; - $twitter_group['original_logo']=$group->original_logo; - $twitter_group['homepage_logo']=$group->homepage_logo; - $twitter_group['stream_logo']=$group->stream_logo; - $twitter_group['mini_logo']=$group->mini_logo; - $twitter_group['homepage']=$group->homepage; - $twitter_group['description']=$group->description; - $twitter_group['location']=$group->location; - $twitter_group['created']=$this->dateTwitter($group->created); - $twitter_group['modified']=$this->dateTwitter($group->modified); - return $twitter_group; - } - - function twitterRssGroupArray($group) - { - $entry = array(); - $entry['content']=$group->description; - $entry['title']=$group->nickname; - $entry['link']=$group->permalink(); - $entry['published']=common_date_iso8601($group->created); - $entry['updated']==common_date_iso8601($group->modified); - $taguribase = common_config('integration', 'groupuri'); - $entry['id'] = "group:$groupuribase:$entry[link]"; - - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($group->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function twitterRssEntryArray($notice) - { - $profile = $notice->getProfile(); - $entry = array(); - - // We trim() to avoid extraneous whitespace in the output - - $entry['content'] = common_xml_safe_str(trim($notice->rendered)); - $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content)); - $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id)); - $entry['published'] = common_date_iso8601($notice->created); - - $taguribase = TagURI::base(); - $entry['id'] = "tag:$taguribase:$entry[link]"; - - $entry['updated'] = $entry['published']; - $entry['author'] = $profile->getBestName(); - - // Enclosures - $attachments = $notice->attachments(); - $enclosures = array(); - - foreach ($attachments as $attachment) { - $enclosure_o=$attachment->getEnclosure(); - if ($enclosure_o) { - $enclosure = array(); - $enclosure['url'] = $enclosure_o->url; - $enclosure['mimetype'] = $enclosure_o->mimetype; - $enclosure['size'] = $enclosure_o->size; - $enclosures[] = $enclosure; - } - } - - if (!empty($enclosures)) { - $entry['enclosures'] = $enclosures; - } - - // Tags/Categories - $tag = new Notice_tag(); - $tag->notice_id = $notice->id; - if ($tag->find()) { - $entry['tags']=array(); - while ($tag->fetch()) { - $entry['tags'][]=$tag->tag; - } - } - $tag->free(); - - // RSS Item specific - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($notice->created); - $entry['guid'] = $entry['link']; - - if (isset($notice->lat) && isset($notice->lon)) { - // This is the format that GeoJSON expects stuff to be in. - // showGeoRSS() below uses it for XML output, so we reuse it - $entry['geo'] = array('type' => 'Point', - 'coordinates' => array((float) $notice->lat, - (float) $notice->lon)); - } else { - $entry['geo'] = null; - } - - return $entry; - } - - function twitterRelationshipArray($source, $target) - { - $relationship = array(); - - $relationship['source'] = - $this->relationshipDetailsArray($source, $target); - $relationship['target'] = - $this->relationshipDetailsArray($target, $source); - - return array('relationship' => $relationship); - } - - function relationshipDetailsArray($source, $target) - { - $details = array(); - - $details['screen_name'] = $source->nickname; - $details['followed_by'] = $target->isSubscribed($source); - $details['following'] = $source->isSubscribed($target); - - $notifications = false; - - if ($source->isSubscribed($target)) { - - $sub = Subscription::pkeyGet(array('subscriber' => - $source->id, 'subscribed' => $target->id)); - - if (!empty($sub)) { - $notifications = ($sub->jabber || $sub->sms); - } - } - - $details['notifications_enabled'] = $notifications; - $details['blocking'] = $source->hasBlocked($target); - $details['id'] = $source->id; - - return $details; - } - - function showTwitterXmlRelationship($relationship) - { - $this->elementStart('relationship'); - - foreach($relationship as $element => $value) { - if ($element == 'source' || $element == 'target') { - $this->elementStart($element); - $this->showXmlRelationshipDetails($value); - $this->elementEnd($element); - } - } - - $this->elementEnd('relationship'); - } - - function showXmlRelationshipDetails($details) - { - foreach($details as $element => $value) { - $this->element($element, null, $value); - } - } - - function showTwitterXmlStatus($twitter_status, $tag='status') - { - $this->elementStart($tag); - foreach($twitter_status as $element => $value) { - switch ($element) { - case 'user': - $this->showTwitterXmlUser($twitter_status['user']); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - case 'attachments': - $this->showXmlAttachments($twitter_status['attachments']); - break; - case 'geo': - $this->showGeoRSS($value); - break; - case 'retweeted_status': - $this->showTwitterXmlStatus($value, 'retweeted_status'); - break; - default: - $this->element($element, null, $value); - } - } - $this->elementEnd($tag); - } - - function showTwitterXmlGroup($twitter_group) - { - $this->elementStart('group'); - foreach($twitter_group as $element => $value) { - $this->element($element, null, $value); - } - $this->elementEnd('group'); - } - - function showTwitterXmlUser($twitter_user, $role='user') - { - $this->elementStart($role); - foreach($twitter_user as $element => $value) { - if ($element == 'status') { - $this->showTwitterXmlStatus($twitter_user['status']); - } else { - $this->element($element, null, $value); - } - } - $this->elementEnd($role); - } - - function showXmlAttachments($attachments) { - if (!empty($attachments)) { - $this->elementStart('attachments', array('type' => 'array')); - foreach ($attachments as $attachment) { - $attrs = array(); - $attrs['url'] = $attachment['url']; - $attrs['mimetype'] = $attachment['mimetype']; - $attrs['size'] = $attachment['size']; - $this->element('enclosure', $attrs, ''); - } - $this->elementEnd('attachments'); - } - } - - function showGeoRSS($geo) - { - if (empty($geo)) { - // empty geo element - $this->element('geo'); - } else { - $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss')); - $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]); - $this->elementEnd('geo'); - } - } - - function showTwitterRssItem($entry) - { - $this->elementStart('item'); - $this->element('title', null, $entry['title']); - $this->element('description', null, $entry['description']); - $this->element('pubDate', null, $entry['pubDate']); - $this->element('guid', null, $entry['guid']); - $this->element('link', null, $entry['link']); - - # RSS only supports 1 enclosure per item - if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ - $enclosure = $entry['enclosures'][0]; - $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); - } - - if(array_key_exists('tags', $entry)){ - foreach($entry['tags'] as $tag){ - $this->element('category', null,$tag); - } - } - - $this->showGeoRSS($entry['geo']); - $this->elementEnd('item'); - } - - function showJsonObjects($objects) - { - print(json_encode($objects)); - } - - function showSingleXmlStatus($notice) - { - $this->initDocument('xml'); - $twitter_status = $this->twitterStatusArray($notice); - $this->showTwitterXmlStatus($twitter_status); - $this->endDocument('xml'); - } - - function show_single_json_status($notice) - { - $this->initDocument('json'); - $status = $this->twitterStatusArray($notice); - $this->showJsonObjects($status); - $this->endDocument('json'); - } - - function showXmlTimeline($notice) - { - - $this->initDocument('xml'); - $this->elementStart('statuses', array('type' => 'array')); - - if (is_array($notice)) { - foreach ($notice as $n) { - $twitter_status = $this->twitterStatusArray($n); - $this->showTwitterXmlStatus($twitter_status); - } - } else { - while ($notice->fetch()) { - $twitter_status = $this->twitterStatusArray($notice); - $this->showTwitterXmlStatus($twitter_status); - } - } - - $this->elementEnd('statuses'); - $this->endDocument('xml'); - } - - function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null) - { - - $this->initDocument('rss'); - - $this->element('title', null, $title); - $this->element('link', null, $link); - if (!is_null($suplink)) { - // For FriendFeed's SUP protocol - $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', - 'rel' => 'http://api.friendfeed.com/2008/03#sup', - 'href' => $suplink, - 'type' => 'application/json')); - } - - if (!is_null($logo)) { - $this->elementStart('image'); - $this->element('link', null, $link); - $this->element('title', null, $title); - $this->element('url', null, $logo); - $this->elementEnd('image'); - } - - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($notice)) { - foreach ($notice as $n) { - $entry = $this->twitterRssEntryArray($n); - $this->showTwitterRssItem($entry); - } - } else { - while ($notice->fetch()) { - $entry = $this->twitterRssEntryArray($notice); - $this->showTwitterRssItem($entry); - } - } - - $this->endTwitterRss(); - } - - function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null) - { - - $this->initDocument('atom'); - - $this->element('title', null, $title); - $this->element('id', null, $id); - $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); - - if (!is_null($logo)) { - $this->element('logo',null,$logo); - } - - if (!is_null($suplink)) { - # For FriendFeed's SUP protocol - $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', - 'href' => $suplink, - 'type' => 'application/json')); - } - - 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, $subtitle); - - if (is_array($notice)) { - foreach ($notice as $n) { - $this->raw($n->asAtomEntry()); - } - } else { - while ($notice->fetch()) { - $this->raw($notice->asAtomEntry()); - } - } - - $this->endDocument('atom'); - - } - - function showRssGroups($group, $title, $link, $subtitle) - { - - $this->initDocument('rss'); - - $this->element('title', null, $title); - $this->element('link', null, $link); - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($group)) { - foreach ($group as $g) { - $twitter_group = $this->twitterRssGroupArray($g); - $this->showTwitterRssItem($twitter_group); - } - } else { - while ($group->fetch()) { - $twitter_group = $this->twitterRssGroupArray($group); - $this->showTwitterRssItem($twitter_group); - } - } - - $this->endTwitterRss(); - } - - function showTwitterAtomEntry($entry) - { - $this->elementStart('entry'); - $this->element('title', null, $entry['title']); - $this->element('content', array('type' => 'html'), $entry['content']); - $this->element('id', null, $entry['id']); - $this->element('published', null, $entry['published']); - $this->element('updated', null, $entry['updated']); - $this->element('link', array('type' => 'text/html', - 'href' => $entry['link'], - 'rel' => 'alternate')); - $this->element('link', array('type' => $entry['avatar-type'], - 'href' => $entry['avatar'], - 'rel' => 'image')); - $this->elementStart('author'); - - $this->element('name', null, $entry['author-name']); - $this->element('uri', null, $entry['author-uri']); - - $this->elementEnd('author'); - $this->elementEnd('entry'); - } - - function showXmlDirectMessage($dm) - { - $this->elementStart('direct_message'); - foreach($dm as $element => $value) { - switch ($element) { - case 'sender': - case 'recipient': - $this->showTwitterXmlUser($value, $element); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - default: - $this->element($element, null, $value); - break; - } - } - $this->elementEnd('direct_message'); - } - - function directMessageArray($message) - { - $dmsg = array(); - - $from_profile = $message->getFrom(); - $to_profile = $message->getTo(); - - $dmsg['id'] = $message->id; - $dmsg['sender_id'] = $message->from_profile; - $dmsg['text'] = trim($message->content); - $dmsg['recipient_id'] = $message->to_profile; - $dmsg['created_at'] = $this->dateTwitter($message->created); - $dmsg['sender_screen_name'] = $from_profile->nickname; - $dmsg['recipient_screen_name'] = $to_profile->nickname; - $dmsg['sender'] = $this->twitterUserArray($from_profile, false); - $dmsg['recipient'] = $this->twitterUserArray($to_profile, false); - - return $dmsg; - } - - function rssDirectMessageArray($message) - { - $entry = array(); - - $from = $message->getFrom(); - - $entry['title'] = sprintf('Message from %1$s to %2$s', - $from->nickname, $message->getTo()->nickname); - - $entry['content'] = common_xml_safe_str($message->rendered); - $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); - $entry['published'] = common_date_iso8601($message->created); - - $taguribase = TagURI::base(); - - $entry['id'] = "tag:$taguribase:$entry[link]"; - $entry['updated'] = $entry['published']; - - $entry['author-name'] = $from->getBestName(); - $entry['author-uri'] = $from->homepage; - - $avatar = $from->getAvatar(AVATAR_STREAM_SIZE); - - $entry['avatar'] = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE); - $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png'; - - // RSS item specific - - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($message->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function showSingleXmlDirectMessage($message) - { - $this->initDocument('xml'); - $dmsg = $this->directMessageArray($message); - $this->showXmlDirectMessage($dmsg); - $this->endDocument('xml'); - } - - function showSingleJsonDirectMessage($message) - { - $this->initDocument('json'); - $dmsg = $this->directMessageArray($message); - $this->showJsonObjects($dmsg); - $this->endDocument('json'); - } - - function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null) - { - - $this->initDocument('atom'); - - $this->element('title', null, $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, $subtitle); - - if (is_array($group)) { - foreach ($group as $g) { - $this->raw($g->asAtomEntry()); - } - } else { - while ($group->fetch()) { - $this->raw($group->asAtomEntry()); - } - } - - $this->endDocument('atom'); - - } - - function showJsonTimeline($notice) - { - - $this->initDocument('json'); - - $statuses = array(); - - if (is_array($notice)) { - foreach ($notice as $n) { - $twitter_status = $this->twitterStatusArray($n); - array_push($statuses, $twitter_status); - } - } else { - while ($notice->fetch()) { - $twitter_status = $this->twitterStatusArray($notice); - array_push($statuses, $twitter_status); - } - } - - $this->showJsonObjects($statuses); - - $this->endDocument('json'); - } - - function showJsonGroups($group) - { - - $this->initDocument('json'); - - $groups = array(); - - if (is_array($group)) { - foreach ($group as $g) { - $twitter_group = $this->twitterGroupArray($g); - array_push($groups, $twitter_group); - } - } else { - while ($group->fetch()) { - $twitter_group = $this->twitterGroupArray($group); - array_push($groups, $twitter_group); - } - } - - $this->showJsonObjects($groups); - - $this->endDocument('json'); - } - - function showXmlGroups($group) - { - - $this->initDocument('xml'); - $this->elementStart('groups', array('type' => 'array')); - - if (is_array($group)) { - foreach ($group as $g) { - $twitter_group = $this->twitterGroupArray($g); - $this->showTwitterXmlGroup($twitter_group); - } - } else { - while ($group->fetch()) { - $twitter_group = $this->twitterGroupArray($group); - $this->showTwitterXmlGroup($twitter_group); - } - } - - $this->elementEnd('groups'); - $this->endDocument('xml'); - } - - function showTwitterXmlUsers($user) - { - - $this->initDocument('xml'); - $this->elementStart('users', array('type' => 'array')); - - if (is_array($user)) { - foreach ($user as $u) { - $twitter_user = $this->twitterUserArray($u); - $this->showTwitterXmlUser($twitter_user); - } - } else { - while ($user->fetch()) { - $twitter_user = $this->twitterUserArray($user); - $this->showTwitterXmlUser($twitter_user); - } - } - - $this->elementEnd('users'); - $this->endDocument('xml'); - } - - function showJsonUsers($user) - { - - $this->initDocument('json'); - - $users = array(); - - if (is_array($user)) { - foreach ($user as $u) { - $twitter_user = $this->twitterUserArray($u); - array_push($users, $twitter_user); - } - } else { - while ($user->fetch()) { - $twitter_user = $this->twitterUserArray($user); - array_push($users, $twitter_user); - } - } - - $this->showJsonObjects($users); - - $this->endDocument('json'); - } - - function showSingleJsonGroup($group) - { - $this->initDocument('json'); - $twitter_group = $this->twitterGroupArray($group); - $this->showJsonObjects($twitter_group); - $this->endDocument('json'); - } - - function showSingleXmlGroup($group) - { - $this->initDocument('xml'); - $twitter_group = $this->twitterGroupArray($group); - $this->showTwitterXmlGroup($twitter_group); - $this->endDocument('xml'); - } - - function dateTwitter($dt) - { - $dateStr = date('d F Y H:i:s', strtotime($dt)); - $d = new DateTime($dateStr, new DateTimeZone('UTC')); - $d->setTimezone(new DateTimeZone(common_timezone())); - return $d->format('D M d H:i:s O Y'); - } - - function initDocument($type='xml') - { - switch ($type) { - case 'xml': - header('Content-Type: application/xml; charset=utf-8'); - $this->startXML(); - break; - case 'json': - header('Content-Type: application/json; charset=utf-8'); - - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print $callback . '('; - } - break; - case 'rss': - header("Content-Type: application/rss+xml; charset=utf-8"); - $this->initTwitterRss(); - break; - case 'atom': - header('Content-Type: application/atom+xml; charset=utf-8'); - $this->initTwitterAtom(); - break; - default: - $this->clientError(_('Not a supported data format.')); - break; - } - - return; - } - - function endDocument($type='xml') - { - switch ($type) { - case 'xml': - $this->endXML(); - break; - case 'json': - - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print ')'; - } - break; - case 'rss': - $this->endTwitterRss(); - break; - case 'atom': - $this->endTwitterRss(); - break; - default: - $this->clientError(_('Not a supported data format.')); - break; - } - return; - } - - function clientError($msg, $code = 400, $format = 'xml') - { - $action = $this->trimmed('action'); - - common_debug("User error '$code' on '$action': $msg", __FILE__); - - if (!array_key_exists($code, ClientErrorAction::$status)) { - $code = 400; - } - - $status_string = ClientErrorAction::$status[$code]; - - header('HTTP/1.1 '.$code.' '.$status_string); - - if ($format == 'xml') { - $this->initDocument('xml'); - $this->elementStart('hash'); - $this->element('error', null, $msg); - $this->element('request', null, $_SERVER['REQUEST_URI']); - $this->elementEnd('hash'); - $this->endDocument('xml'); - } elseif ($format == 'json'){ - $this->initDocument('json'); - $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); - print(json_encode($error_array)); - $this->endDocument('json'); - } else { - - // If user didn't request a useful format, throw a regular client error - throw new ClientException($msg, $code); - } - } - - function serverError($msg, $code = 500, $content_type = 'xml') - { - $action = $this->trimmed('action'); - - common_debug("Server error '$code' on '$action': $msg", __FILE__); - - if (!array_key_exists($code, ServerErrorAction::$status)) { - $code = 400; - } - - $status_string = ServerErrorAction::$status[$code]; - - header('HTTP/1.1 '.$code.' '.$status_string); - - if ($content_type == 'xml') { - $this->initDocument('xml'); - $this->elementStart('hash'); - $this->element('error', null, $msg); - $this->element('request', null, $_SERVER['REQUEST_URI']); - $this->elementEnd('hash'); - $this->endDocument('xml'); - } else { - $this->initDocument('json'); - $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); - print(json_encode($error_array)); - $this->endDocument('json'); - } - } - - function initTwitterRss() - { - $this->startXML(); - $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); - $this->elementStart('channel'); - Event::handle('StartApiRss', array($this)); - } - - function endTwitterRss() - { - $this->elementEnd('channel'); - $this->elementEnd('rss'); - $this->endXML(); - } - - function initTwitterAtom() - { - $this->startXML(); - // FIXME: don't hardcode the language here! - $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', - 'xml:lang' => 'en-US', - 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); - } - - function endTwitterAtom() - { - $this->elementEnd('feed'); - $this->endXML(); - } - - function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true) - { - $profile_array = $this->twitterUserArray($profile, $includeStatuses); - switch ($content_type) { - case 'xml': - $this->showTwitterXmlUser($profile_array); - break; - case 'json': - $this->showJsonObjects($profile_array); - break; - default: - $this->clientError(_('Not a supported data format.')); - return; - } - return; - } - - function getTargetUser($id) - { - if (empty($id)) { - - // Twitter supports these other ways of passing the user ID - if (is_numeric($this->arg('id'))) { - return User::staticGet($this->arg('id')); - } else if ($this->arg('id')) { - $nickname = common_canonical_nickname($this->arg('id')); - return User::staticGet('nickname', $nickname); - } else if ($this->arg('user_id')) { - // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it doesn't get used - if (is_numeric($this->arg('user_id'))) { - return User::staticGet('id', $this->arg('user_id')); - } - } else if ($this->arg('screen_name')) { - $nickname = common_canonical_nickname($this->arg('screen_name')); - return User::staticGet('nickname', $nickname); - } else { - // Fall back to trying the currently authenticated user - return $this->auth_user; - } - - } else if (is_numeric($id)) { - return User::staticGet($id); - } else { - $nickname = common_canonical_nickname($id); - return User::staticGet('nickname', $nickname); - } - } - - function getTargetGroup($id) - { - if (empty($id)) { - if (is_numeric($this->arg('id'))) { - return User_group::staticGet($this->arg('id')); - } else if ($this->arg('id')) { - $nickname = common_canonical_nickname($this->arg('id')); - $local = Local_group::staticGet('nickname', $nickname); - if (empty($local)) { - return null; - } else { - return User_group::staticGet('id', $local->id); - } - } else if ($this->arg('group_id')) { - // This is to ensure that a non-numeric user_id still - // overrides screen_name even if it doesn't get used - if (is_numeric($this->arg('group_id'))) { - return User_group::staticGet('id', $this->arg('group_id')); - } - } else if ($this->arg('group_name')) { - $nickname = common_canonical_nickname($this->arg('group_name')); - $local = Local_group::staticGet('nickname', $nickname); - if (empty($local)) { - return null; - } else { - return User_group::staticGet('id', $local->id); - } - } - - } else if (is_numeric($id)) { - return User_group::staticGet($id); - } else { - $nickname = common_canonical_nickname($id); - $local = Local_group::staticGet('nickname', $nickname); - if (empty($local)) { - return null; - } else { - return User_group::staticGet('id', $local->group_id); - } - } - } - - function sourceLink($source) - { - $source_name = _($source); - switch ($source) { - case 'web': - case 'xmpp': - case 'mail': - case 'omb': - case 'api': - break; - default: - - $name = null; - $url = null; - - $ns = Notice_source::staticGet($source); - - if ($ns) { - $name = $ns->name; - $url = $ns->url; - } else { - $app = Oauth_application::staticGet('name', $source); - if ($app) { - $name = $app->name; - $url = $app->source_url; - } - } - - if (!empty($name) && !empty($url)) { - $source_name = '' . $name . ''; - } - - break; - } - return $source_name; - } - - /** - * Returns query argument or default value if not found. Certain - * parameters used throughout the API are lightly scrubbed and - * bounds checked. This overrides Action::arg(). - * - * @param string $key requested argument - * @param string $def default value to return if $key is not provided - * - * @return var $var - */ - function arg($key, $def=null) - { - - // XXX: Do even more input validation/scrubbing? - - if (array_key_exists($key, $this->args)) { - switch($key) { - case 'page': - $page = (int)$this->args['page']; - return ($page < 1) ? 1 : $page; - case 'count': - $count = (int)$this->args['count']; - if ($count < 1) { - return 20; - } elseif ($count > 200) { - return 200; - } else { - return $count; - } - case 'since_id': - $since_id = (int)$this->args['since_id']; - return ($since_id < 1) ? 0 : $since_id; - case 'max_id': - $max_id = (int)$this->args['max_id']; - return ($max_id < 1) ? 0 : $max_id; - case 'since': - return strtotime($this->args['since']); - default: - return parent::arg($key, $def); - } - } else { - return $def; - } - } - - function getSelfUri($action, $aargs) - { - parse_str($_SERVER['QUERY_STRING'], $params); - $pstring = ''; - if (!empty($params)) { - unset($params['p']); - $pstring = http_build_query($params); - } - - $uri = common_local_url($action, $aargs); - - if (!empty($pstring)) { - $uri .= '?' . $pstring; - } - - return $uri; - } - -} diff --git a/lib/apiaction.php b/lib/apiaction.php new file mode 100644 index 000000000..2af150ab9 --- /dev/null +++ b/lib/apiaction.php @@ -0,0 +1,1356 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author Jeffery To + * @author Toby Inkster + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Contains most of the Twitter-compatible API output functions. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author Jeffery To + * @author Toby Inkster + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiAction extends Action +{ + const READ_ONLY = 1; + const READ_WRITE = 2; + + var $format = null; + var $user = null; + var $auth_user = null; + var $page = null; + var $count = null; + var $max_id = null; + var $since_id = null; + var $since = null; + + var $access = self::READ_ONLY; // read (default) or read-write + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + + function prepare($args) + { + StatusNet::setApi(true); // reduce exception reports to aid in debugging + parent::prepare($args); + + $this->format = $this->arg('format'); + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + } + + /** + * Overrides XMLOutputter::element to write booleans as strings (true|false). + * See that method's documentation for more info. + * + * @param string $tag Element type or tagname + * @param array $attrs Array of element attributes, as + * key-value pairs + * @param string $content string content of the element + * + * @return void + */ + function element($tag, $attrs=null, $content=null) + { + if (is_bool($content)) { + $content = ($content ? 'true' : 'false'); + } + + return parent::element($tag, $attrs, $content); + } + + function twitterUserArray($profile, $get_notice=false) + { + $twitter_user = array(); + + $twitter_user['id'] = intval($profile->id); + $twitter_user['name'] = $profile->getBestName(); + $twitter_user['screen_name'] = $profile->nickname; + $twitter_user['location'] = ($profile->location) ? $profile->location : null; + $twitter_user['description'] = ($profile->bio) ? $profile->bio : null; + + $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : + Avatar::defaultImage(AVATAR_STREAM_SIZE); + + $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; + $twitter_user['protected'] = false; # not supported by StatusNet yet + $twitter_user['followers_count'] = $profile->subscriberCount(); + + $design = null; + $user = $profile->getUser(); + + // Note: some profiles don't have an associated user + + $defaultDesign = Design::siteDesign(); + + if (!empty($user)) { + $design = $user->getDesign(); + } + + if (empty($design)) { + $design = $defaultDesign; + } + + $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor); + $twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue(); + $color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor); + $twitter_user['profile_text_color'] = ($color == null) ? '' : '#'.$color->hexValue(); + $color = Design::toWebColor(empty($design->linkcolor) ? $defaultDesign->linkcolor : $design->linkcolor); + $twitter_user['profile_link_color'] = ($color == null) ? '' : '#'.$color->hexValue(); + $color = Design::toWebColor(empty($design->sidebarcolor) ? $defaultDesign->sidebarcolor : $design->sidebarcolor); + $twitter_user['profile_sidebar_fill_color'] = ($color == null) ? '' : '#'.$color->hexValue(); + $twitter_user['profile_sidebar_border_color'] = ''; + + $twitter_user['friends_count'] = $profile->subscriptionCount(); + + $twitter_user['created_at'] = $this->dateTwitter($profile->created); + + $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! + + $timezone = 'UTC'; + + if (!empty($user) && $user->timezone) { + $timezone = $user->timezone; + } + + $t = new DateTime; + $t->setTimezone(new DateTimeZone($timezone)); + + $twitter_user['utc_offset'] = $t->format('Z'); + $twitter_user['time_zone'] = $timezone; + + $twitter_user['profile_background_image_url'] + = empty($design->backgroundimage) + ? '' : ($design->disposition & BACKGROUND_ON) + ? Design::url($design->backgroundimage) : ''; + + $twitter_user['profile_background_tile'] + = empty($design->disposition) + ? '' : ($design->disposition & BACKGROUND_TILE) ? 'true' : 'false'; + + $twitter_user['statuses_count'] = $profile->noticeCount(); + + // Is the requesting user following this user? + $twitter_user['following'] = false; + $twitter_user['notifications'] = false; + + if (isset($this->auth_user)) { + + $twitter_user['following'] = $this->auth_user->isSubscribed($profile); + + // Notifications on? + $sub = Subscription::pkeyGet(array('subscriber' => + $this->auth_user->id, + 'subscribed' => $profile->id)); + + if ($sub) { + $twitter_user['notifications'] = ($sub->jabber || $sub->sms); + } + } + + if ($get_notice) { + $notice = $profile->getCurrentNotice(); + if ($notice) { + # don't get user! + $twitter_user['status'] = $this->twitterStatusArray($notice, false); + } + } + + return $twitter_user; + } + + function twitterStatusArray($notice, $include_user=true) + { + $base = $this->twitterSimpleStatusArray($notice, $include_user); + + if (!empty($notice->repeat_of)) { + $original = Notice::staticGet('id', $notice->repeat_of); + if (!empty($original)) { + $original_array = $this->twitterSimpleStatusArray($original, $include_user); + $base['retweeted_status'] = $original_array; + } + } + + return $base; + } + + function twitterSimpleStatusArray($notice, $include_user=true) + { + $profile = $notice->getProfile(); + + $twitter_status = array(); + $twitter_status['text'] = $notice->content; + $twitter_status['truncated'] = false; # Not possible on StatusNet + $twitter_status['created_at'] = $this->dateTwitter($notice->created); + $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? + intval($notice->reply_to) : null; + $twitter_status['source'] = $this->sourceLink($notice->source); + $twitter_status['id'] = intval($notice->id); + + $replier_profile = null; + + if ($notice->reply_to) { + $reply = Notice::staticGet(intval($notice->reply_to)); + if ($reply) { + $replier_profile = $reply->getProfile(); + } + } + + $twitter_status['in_reply_to_user_id'] = + ($replier_profile) ? intval($replier_profile->id) : null; + $twitter_status['in_reply_to_screen_name'] = + ($replier_profile) ? $replier_profile->nickname : null; + + if (isset($notice->lat) && isset($notice->lon)) { + // This is the format that GeoJSON expects stuff to be in + $twitter_status['geo'] = array('type' => 'Point', + 'coordinates' => array((float) $notice->lat, + (float) $notice->lon)); + } else { + $twitter_status['geo'] = null; + } + + if (isset($this->auth_user)) { + $twitter_status['favorited'] = $this->auth_user->hasFave($notice); + } else { + $twitter_status['favorited'] = false; + } + + // Enclosures + $attachments = $notice->attachments(); + + if (!empty($attachments)) { + + $twitter_status['attachments'] = array(); + + foreach ($attachments as $attachment) { + if ($attachment->isEnclosure()) { + $enclosure = array(); + $enclosure['url'] = $attachment->url; + $enclosure['mimetype'] = $attachment->mimetype; + $enclosure['size'] = $attachment->size; + $twitter_status['attachments'][] = $enclosure; + } + } + } + + if ($include_user && $profile) { + # Don't get notice (recursive!) + $twitter_user = $this->twitterUserArray($profile, false); + $twitter_status['user'] = $twitter_user; + } + + return $twitter_status; + } + + function twitterGroupArray($group) + { + $twitter_group=array(); + $twitter_group['id']=$group->id; + $twitter_group['url']=$group->permalink(); + $twitter_group['nickname']=$group->nickname; + $twitter_group['fullname']=$group->fullname; + $twitter_group['homepage_url']=$group->homepage_url; + $twitter_group['original_logo']=$group->original_logo; + $twitter_group['homepage_logo']=$group->homepage_logo; + $twitter_group['stream_logo']=$group->stream_logo; + $twitter_group['mini_logo']=$group->mini_logo; + $twitter_group['homepage']=$group->homepage; + $twitter_group['description']=$group->description; + $twitter_group['location']=$group->location; + $twitter_group['created']=$this->dateTwitter($group->created); + $twitter_group['modified']=$this->dateTwitter($group->modified); + return $twitter_group; + } + + function twitterRssGroupArray($group) + { + $entry = array(); + $entry['content']=$group->description; + $entry['title']=$group->nickname; + $entry['link']=$group->permalink(); + $entry['published']=common_date_iso8601($group->created); + $entry['updated']==common_date_iso8601($group->modified); + $taguribase = common_config('integration', 'groupuri'); + $entry['id'] = "group:$groupuribase:$entry[link]"; + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($group->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + function twitterRssEntryArray($notice) + { + $profile = $notice->getProfile(); + $entry = array(); + + // We trim() to avoid extraneous whitespace in the output + + $entry['content'] = common_xml_safe_str(trim($notice->rendered)); + $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content)); + $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id)); + $entry['published'] = common_date_iso8601($notice->created); + + $taguribase = TagURI::base(); + $entry['id'] = "tag:$taguribase:$entry[link]"; + + $entry['updated'] = $entry['published']; + $entry['author'] = $profile->getBestName(); + + // Enclosures + $attachments = $notice->attachments(); + $enclosures = array(); + + foreach ($attachments as $attachment) { + $enclosure_o=$attachment->getEnclosure(); + if ($enclosure_o) { + $enclosure = array(); + $enclosure['url'] = $enclosure_o->url; + $enclosure['mimetype'] = $enclosure_o->mimetype; + $enclosure['size'] = $enclosure_o->size; + $enclosures[] = $enclosure; + } + } + + if (!empty($enclosures)) { + $entry['enclosures'] = $enclosures; + } + + // Tags/Categories + $tag = new Notice_tag(); + $tag->notice_id = $notice->id; + if ($tag->find()) { + $entry['tags']=array(); + while ($tag->fetch()) { + $entry['tags'][]=$tag->tag; + } + } + $tag->free(); + + // RSS Item specific + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($notice->created); + $entry['guid'] = $entry['link']; + + if (isset($notice->lat) && isset($notice->lon)) { + // This is the format that GeoJSON expects stuff to be in. + // showGeoRSS() below uses it for XML output, so we reuse it + $entry['geo'] = array('type' => 'Point', + 'coordinates' => array((float) $notice->lat, + (float) $notice->lon)); + } else { + $entry['geo'] = null; + } + + return $entry; + } + + function twitterRelationshipArray($source, $target) + { + $relationship = array(); + + $relationship['source'] = + $this->relationshipDetailsArray($source, $target); + $relationship['target'] = + $this->relationshipDetailsArray($target, $source); + + return array('relationship' => $relationship); + } + + function relationshipDetailsArray($source, $target) + { + $details = array(); + + $details['screen_name'] = $source->nickname; + $details['followed_by'] = $target->isSubscribed($source); + $details['following'] = $source->isSubscribed($target); + + $notifications = false; + + if ($source->isSubscribed($target)) { + + $sub = Subscription::pkeyGet(array('subscriber' => + $source->id, 'subscribed' => $target->id)); + + if (!empty($sub)) { + $notifications = ($sub->jabber || $sub->sms); + } + } + + $details['notifications_enabled'] = $notifications; + $details['blocking'] = $source->hasBlocked($target); + $details['id'] = $source->id; + + return $details; + } + + function showTwitterXmlRelationship($relationship) + { + $this->elementStart('relationship'); + + foreach($relationship as $element => $value) { + if ($element == 'source' || $element == 'target') { + $this->elementStart($element); + $this->showXmlRelationshipDetails($value); + $this->elementEnd($element); + } + } + + $this->elementEnd('relationship'); + } + + function showXmlRelationshipDetails($details) + { + foreach($details as $element => $value) { + $this->element($element, null, $value); + } + } + + function showTwitterXmlStatus($twitter_status, $tag='status') + { + $this->elementStart($tag); + foreach($twitter_status as $element => $value) { + switch ($element) { + case 'user': + $this->showTwitterXmlUser($twitter_status['user']); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + case 'attachments': + $this->showXmlAttachments($twitter_status['attachments']); + break; + case 'geo': + $this->showGeoRSS($value); + break; + case 'retweeted_status': + $this->showTwitterXmlStatus($value, 'retweeted_status'); + break; + default: + $this->element($element, null, $value); + } + } + $this->elementEnd($tag); + } + + function showTwitterXmlGroup($twitter_group) + { + $this->elementStart('group'); + foreach($twitter_group as $element => $value) { + $this->element($element, null, $value); + } + $this->elementEnd('group'); + } + + function showTwitterXmlUser($twitter_user, $role='user') + { + $this->elementStart($role); + foreach($twitter_user as $element => $value) { + if ($element == 'status') { + $this->showTwitterXmlStatus($twitter_user['status']); + } else { + $this->element($element, null, $value); + } + } + $this->elementEnd($role); + } + + function showXmlAttachments($attachments) { + if (!empty($attachments)) { + $this->elementStart('attachments', array('type' => 'array')); + foreach ($attachments as $attachment) { + $attrs = array(); + $attrs['url'] = $attachment['url']; + $attrs['mimetype'] = $attachment['mimetype']; + $attrs['size'] = $attachment['size']; + $this->element('enclosure', $attrs, ''); + } + $this->elementEnd('attachments'); + } + } + + function showGeoRSS($geo) + { + if (empty($geo)) { + // empty geo element + $this->element('geo'); + } else { + $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss')); + $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]); + $this->elementEnd('geo'); + } + } + + function showTwitterRssItem($entry) + { + $this->elementStart('item'); + $this->element('title', null, $entry['title']); + $this->element('description', null, $entry['description']); + $this->element('pubDate', null, $entry['pubDate']); + $this->element('guid', null, $entry['guid']); + $this->element('link', null, $entry['link']); + + # RSS only supports 1 enclosure per item + if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ + $enclosure = $entry['enclosures'][0]; + $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); + } + + if(array_key_exists('tags', $entry)){ + foreach($entry['tags'] as $tag){ + $this->element('category', null,$tag); + } + } + + $this->showGeoRSS($entry['geo']); + $this->elementEnd('item'); + } + + function showJsonObjects($objects) + { + print(json_encode($objects)); + } + + function showSingleXmlStatus($notice) + { + $this->initDocument('xml'); + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); + $this->endDocument('xml'); + } + + function show_single_json_status($notice) + { + $this->initDocument('json'); + $status = $this->twitterStatusArray($notice); + $this->showJsonObjects($status); + $this->endDocument('json'); + } + + function showXmlTimeline($notice) + { + + $this->initDocument('xml'); + $this->elementStart('statuses', array('type' => 'array')); + + if (is_array($notice)) { + foreach ($notice as $n) { + $twitter_status = $this->twitterStatusArray($n); + $this->showTwitterXmlStatus($twitter_status); + } + } else { + while ($notice->fetch()) { + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); + } + } + + $this->elementEnd('statuses'); + $this->endDocument('xml'); + } + + function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null) + { + + $this->initDocument('rss'); + + $this->element('title', null, $title); + $this->element('link', null, $link); + if (!is_null($suplink)) { + // For FriendFeed's SUP protocol + $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', + 'rel' => 'http://api.friendfeed.com/2008/03#sup', + 'href' => $suplink, + 'type' => 'application/json')); + } + + if (!is_null($logo)) { + $this->elementStart('image'); + $this->element('link', null, $link); + $this->element('title', null, $title); + $this->element('url', null, $logo); + $this->elementEnd('image'); + } + + $this->element('description', null, $subtitle); + $this->element('language', null, 'en-us'); + $this->element('ttl', null, '40'); + + if (is_array($notice)) { + foreach ($notice as $n) { + $entry = $this->twitterRssEntryArray($n); + $this->showTwitterRssItem($entry); + } + } else { + while ($notice->fetch()) { + $entry = $this->twitterRssEntryArray($notice); + $this->showTwitterRssItem($entry); + } + } + + $this->endTwitterRss(); + } + + function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null) + { + + $this->initDocument('atom'); + + $this->element('title', null, $title); + $this->element('id', null, $id); + $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); + + if (!is_null($logo)) { + $this->element('logo',null,$logo); + } + + if (!is_null($suplink)) { + # For FriendFeed's SUP protocol + $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', + 'href' => $suplink, + 'type' => 'application/json')); + } + + 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, $subtitle); + + if (is_array($notice)) { + foreach ($notice as $n) { + $this->raw($n->asAtomEntry()); + } + } else { + while ($notice->fetch()) { + $this->raw($notice->asAtomEntry()); + } + } + + $this->endDocument('atom'); + + } + + function showRssGroups($group, $title, $link, $subtitle) + { + + $this->initDocument('rss'); + + $this->element('title', null, $title); + $this->element('link', null, $link); + $this->element('description', null, $subtitle); + $this->element('language', null, 'en-us'); + $this->element('ttl', null, '40'); + + if (is_array($group)) { + foreach ($group as $g) { + $twitter_group = $this->twitterRssGroupArray($g); + $this->showTwitterRssItem($twitter_group); + } + } else { + while ($group->fetch()) { + $twitter_group = $this->twitterRssGroupArray($group); + $this->showTwitterRssItem($twitter_group); + } + } + + $this->endTwitterRss(); + } + + function showTwitterAtomEntry($entry) + { + $this->elementStart('entry'); + $this->element('title', null, $entry['title']); + $this->element('content', array('type' => 'html'), $entry['content']); + $this->element('id', null, $entry['id']); + $this->element('published', null, $entry['published']); + $this->element('updated', null, $entry['updated']); + $this->element('link', array('type' => 'text/html', + 'href' => $entry['link'], + 'rel' => 'alternate')); + $this->element('link', array('type' => $entry['avatar-type'], + 'href' => $entry['avatar'], + 'rel' => 'image')); + $this->elementStart('author'); + + $this->element('name', null, $entry['author-name']); + $this->element('uri', null, $entry['author-uri']); + + $this->elementEnd('author'); + $this->elementEnd('entry'); + } + + function showXmlDirectMessage($dm) + { + $this->elementStart('direct_message'); + foreach($dm as $element => $value) { + switch ($element) { + case 'sender': + case 'recipient': + $this->showTwitterXmlUser($value, $element); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + default: + $this->element($element, null, $value); + break; + } + } + $this->elementEnd('direct_message'); + } + + function directMessageArray($message) + { + $dmsg = array(); + + $from_profile = $message->getFrom(); + $to_profile = $message->getTo(); + + $dmsg['id'] = $message->id; + $dmsg['sender_id'] = $message->from_profile; + $dmsg['text'] = trim($message->content); + $dmsg['recipient_id'] = $message->to_profile; + $dmsg['created_at'] = $this->dateTwitter($message->created); + $dmsg['sender_screen_name'] = $from_profile->nickname; + $dmsg['recipient_screen_name'] = $to_profile->nickname; + $dmsg['sender'] = $this->twitterUserArray($from_profile, false); + $dmsg['recipient'] = $this->twitterUserArray($to_profile, false); + + return $dmsg; + } + + function rssDirectMessageArray($message) + { + $entry = array(); + + $from = $message->getFrom(); + + $entry['title'] = sprintf('Message from %1$s to %2$s', + $from->nickname, $message->getTo()->nickname); + + $entry['content'] = common_xml_safe_str($message->rendered); + $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); + $entry['published'] = common_date_iso8601($message->created); + + $taguribase = TagURI::base(); + + $entry['id'] = "tag:$taguribase:$entry[link]"; + $entry['updated'] = $entry['published']; + + $entry['author-name'] = $from->getBestName(); + $entry['author-uri'] = $from->homepage; + + $avatar = $from->getAvatar(AVATAR_STREAM_SIZE); + + $entry['avatar'] = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE); + $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png'; + + // RSS item specific + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($message->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + function showSingleXmlDirectMessage($message) + { + $this->initDocument('xml'); + $dmsg = $this->directMessageArray($message); + $this->showXmlDirectMessage($dmsg); + $this->endDocument('xml'); + } + + function showSingleJsonDirectMessage($message) + { + $this->initDocument('json'); + $dmsg = $this->directMessageArray($message); + $this->showJsonObjects($dmsg); + $this->endDocument('json'); + } + + function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null) + { + + $this->initDocument('atom'); + + $this->element('title', null, $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, $subtitle); + + if (is_array($group)) { + foreach ($group as $g) { + $this->raw($g->asAtomEntry()); + } + } else { + while ($group->fetch()) { + $this->raw($group->asAtomEntry()); + } + } + + $this->endDocument('atom'); + + } + + function showJsonTimeline($notice) + { + + $this->initDocument('json'); + + $statuses = array(); + + if (is_array($notice)) { + foreach ($notice as $n) { + $twitter_status = $this->twitterStatusArray($n); + array_push($statuses, $twitter_status); + } + } else { + while ($notice->fetch()) { + $twitter_status = $this->twitterStatusArray($notice); + array_push($statuses, $twitter_status); + } + } + + $this->showJsonObjects($statuses); + + $this->endDocument('json'); + } + + function showJsonGroups($group) + { + + $this->initDocument('json'); + + $groups = array(); + + if (is_array($group)) { + foreach ($group as $g) { + $twitter_group = $this->twitterGroupArray($g); + array_push($groups, $twitter_group); + } + } else { + while ($group->fetch()) { + $twitter_group = $this->twitterGroupArray($group); + array_push($groups, $twitter_group); + } + } + + $this->showJsonObjects($groups); + + $this->endDocument('json'); + } + + function showXmlGroups($group) + { + + $this->initDocument('xml'); + $this->elementStart('groups', array('type' => 'array')); + + if (is_array($group)) { + foreach ($group as $g) { + $twitter_group = $this->twitterGroupArray($g); + $this->showTwitterXmlGroup($twitter_group); + } + } else { + while ($group->fetch()) { + $twitter_group = $this->twitterGroupArray($group); + $this->showTwitterXmlGroup($twitter_group); + } + } + + $this->elementEnd('groups'); + $this->endDocument('xml'); + } + + function showTwitterXmlUsers($user) + { + + $this->initDocument('xml'); + $this->elementStart('users', array('type' => 'array')); + + if (is_array($user)) { + foreach ($user as $u) { + $twitter_user = $this->twitterUserArray($u); + $this->showTwitterXmlUser($twitter_user); + } + } else { + while ($user->fetch()) { + $twitter_user = $this->twitterUserArray($user); + $this->showTwitterXmlUser($twitter_user); + } + } + + $this->elementEnd('users'); + $this->endDocument('xml'); + } + + function showJsonUsers($user) + { + + $this->initDocument('json'); + + $users = array(); + + if (is_array($user)) { + foreach ($user as $u) { + $twitter_user = $this->twitterUserArray($u); + array_push($users, $twitter_user); + } + } else { + while ($user->fetch()) { + $twitter_user = $this->twitterUserArray($user); + array_push($users, $twitter_user); + } + } + + $this->showJsonObjects($users); + + $this->endDocument('json'); + } + + function showSingleJsonGroup($group) + { + $this->initDocument('json'); + $twitter_group = $this->twitterGroupArray($group); + $this->showJsonObjects($twitter_group); + $this->endDocument('json'); + } + + function showSingleXmlGroup($group) + { + $this->initDocument('xml'); + $twitter_group = $this->twitterGroupArray($group); + $this->showTwitterXmlGroup($twitter_group); + $this->endDocument('xml'); + } + + function dateTwitter($dt) + { + $dateStr = date('d F Y H:i:s', strtotime($dt)); + $d = new DateTime($dateStr, new DateTimeZone('UTC')); + $d->setTimezone(new DateTimeZone(common_timezone())); + return $d->format('D M d H:i:s O Y'); + } + + function initDocument($type='xml') + { + switch ($type) { + case 'xml': + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + break; + case 'json': + header('Content-Type: application/json; charset=utf-8'); + + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print $callback . '('; + } + break; + case 'rss': + header("Content-Type: application/rss+xml; charset=utf-8"); + $this->initTwitterRss(); + break; + case 'atom': + header('Content-Type: application/atom+xml; charset=utf-8'); + $this->initTwitterAtom(); + break; + default: + $this->clientError(_('Not a supported data format.')); + break; + } + + return; + } + + function endDocument($type='xml') + { + switch ($type) { + case 'xml': + $this->endXML(); + break; + case 'json': + + // Check for JSONP callback + $callback = $this->arg('callback'); + if ($callback) { + print ')'; + } + break; + case 'rss': + $this->endTwitterRss(); + break; + case 'atom': + $this->endTwitterRss(); + break; + default: + $this->clientError(_('Not a supported data format.')); + break; + } + return; + } + + function clientError($msg, $code = 400, $format = 'xml') + { + $action = $this->trimmed('action'); + + common_debug("User error '$code' on '$action': $msg", __FILE__); + + if (!array_key_exists($code, ClientErrorAction::$status)) { + $code = 400; + } + + $status_string = ClientErrorAction::$status[$code]; + + header('HTTP/1.1 '.$code.' '.$status_string); + + if ($format == 'xml') { + $this->initDocument('xml'); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->endDocument('xml'); + } elseif ($format == 'json'){ + $this->initDocument('json'); + $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + $this->endDocument('json'); + } else { + + // If user didn't request a useful format, throw a regular client error + throw new ClientException($msg, $code); + } + } + + function serverError($msg, $code = 500, $content_type = 'xml') + { + $action = $this->trimmed('action'); + + common_debug("Server error '$code' on '$action': $msg", __FILE__); + + if (!array_key_exists($code, ServerErrorAction::$status)) { + $code = 400; + } + + $status_string = ServerErrorAction::$status[$code]; + + header('HTTP/1.1 '.$code.' '.$status_string); + + if ($content_type == 'xml') { + $this->initDocument('xml'); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->endDocument('xml'); + } else { + $this->initDocument('json'); + $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + $this->endDocument('json'); + } + } + + function initTwitterRss() + { + $this->startXML(); + $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); + $this->elementStart('channel'); + Event::handle('StartApiRss', array($this)); + } + + function endTwitterRss() + { + $this->elementEnd('channel'); + $this->elementEnd('rss'); + $this->endXML(); + } + + function initTwitterAtom() + { + $this->startXML(); + // FIXME: don't hardcode the language here! + $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', + 'xml:lang' => 'en-US', + 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); + } + + function endTwitterAtom() + { + $this->elementEnd('feed'); + $this->endXML(); + } + + function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true) + { + $profile_array = $this->twitterUserArray($profile, $includeStatuses); + switch ($content_type) { + case 'xml': + $this->showTwitterXmlUser($profile_array); + break; + case 'json': + $this->showJsonObjects($profile_array); + break; + default: + $this->clientError(_('Not a supported data format.')); + return; + } + return; + } + + function getTargetUser($id) + { + if (empty($id)) { + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return User::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return User::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::staticGet('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $this->auth_user; + } + + } else if (is_numeric($id)) { + return User::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return User::staticGet('nickname', $nickname); + } + } + + function getTargetGroup($id) + { + if (empty($id)) { + if (is_numeric($this->arg('id'))) { + return User_group::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + $local = Local_group::staticGet('nickname', $nickname); + if (empty($local)) { + return null; + } else { + return User_group::staticGet('id', $local->id); + } + } else if ($this->arg('group_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('group_id'))) { + return User_group::staticGet('id', $this->arg('group_id')); + } + } else if ($this->arg('group_name')) { + $nickname = common_canonical_nickname($this->arg('group_name')); + $local = Local_group::staticGet('nickname', $nickname); + if (empty($local)) { + return null; + } else { + return User_group::staticGet('id', $local->id); + } + } + + } else if (is_numeric($id)) { + return User_group::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + $local = Local_group::staticGet('nickname', $nickname); + if (empty($local)) { + return null; + } else { + return User_group::staticGet('id', $local->group_id); + } + } + } + + function sourceLink($source) + { + $source_name = _($source); + switch ($source) { + case 'web': + case 'xmpp': + case 'mail': + case 'omb': + case 'api': + break; + default: + + $name = null; + $url = null; + + $ns = Notice_source::staticGet($source); + + if ($ns) { + $name = $ns->name; + $url = $ns->url; + } else { + $app = Oauth_application::staticGet('name', $source); + if ($app) { + $name = $app->name; + $url = $app->source_url; + } + } + + if (!empty($name) && !empty($url)) { + $source_name = '' . $name . ''; + } + + break; + } + return $source_name; + } + + /** + * Returns query argument or default value if not found. Certain + * parameters used throughout the API are lightly scrubbed and + * bounds checked. This overrides Action::arg(). + * + * @param string $key requested argument + * @param string $def default value to return if $key is not provided + * + * @return var $var + */ + function arg($key, $def=null) + { + + // XXX: Do even more input validation/scrubbing? + + if (array_key_exists($key, $this->args)) { + switch($key) { + case 'page': + $page = (int)$this->args['page']; + return ($page < 1) ? 1 : $page; + case 'count': + $count = (int)$this->args['count']; + if ($count < 1) { + return 20; + } elseif ($count > 200) { + return 200; + } else { + return $count; + } + case 'since_id': + $since_id = (int)$this->args['since_id']; + return ($since_id < 1) ? 0 : $since_id; + case 'max_id': + $max_id = (int)$this->args['max_id']; + return ($max_id < 1) ? 0 : $max_id; + case 'since': + return strtotime($this->args['since']); + default: + return parent::arg($key, $def); + } + } else { + return $def; + } + } + + function getSelfUri($action, $aargs) + { + parse_str($_SERVER['QUERY_STRING'], $params); + $pstring = ''; + if (!empty($params)) { + unset($params['p']); + $pstring = http_build_query($params); + } + + $uri = common_local_url($action, $aargs); + + if (!empty($pstring)) { + $uri .= '?' . $pstring; + } + + return $uri; + } + +} -- cgit v1.2.3-54-g00ecf From 6cc26a613b7849103d8cfae674bb3a91a7161656 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 25 Feb 2010 22:06:31 -0800 Subject: Remove unnecessary requires --- actions/apistatusnetconfig.php | 2 -- actions/twitapisearchatom.php | 2 -- actions/twitapisearchjson.php | 1 - actions/twitapitrends.php | 2 -- lib/apiauth.php | 1 - plugins/Mapstraction/map.php | 2 -- plugins/Realtime/RealtimePlugin.php | 2 -- 7 files changed, 12 deletions(-) (limited to 'lib') diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php index dc1ab8685..296376d19 100644 --- a/actions/apistatusnetconfig.php +++ b/actions/apistatusnetconfig.php @@ -32,8 +32,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; - /** * Gives a full dump of configuration variables for this instance * of StatusNet, minus variables that may be security-sensitive (like diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index e389ddec8..24aa619bd 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/api.php'; - /** * Action for outputting search results in Twitter compatible Atom * format. diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php index 741ed78d6..b5c006aa7 100644 --- a/actions/twitapisearchjson.php +++ b/actions/twitapisearchjson.php @@ -31,7 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/api.php'; require_once INSTALLDIR.'/lib/jsonsearchresultslist.php'; /** diff --git a/actions/twitapitrends.php b/actions/twitapitrends.php index 779405e6d..5a04569a2 100644 --- a/actions/twitapitrends.php +++ b/actions/twitapitrends.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/api.php'; - /** * Returns the top ten queries that are currently trending * diff --git a/lib/apiauth.php b/lib/apiauth.php index 25e2196cf..5090871cf 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -38,7 +38,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; require_once INSTALLDIR . '/lib/apioauth.php'; /** diff --git a/plugins/Mapstraction/map.php b/plugins/Mapstraction/map.php index a33dfc736..b809c1b8e 100644 --- a/plugins/Mapstraction/map.php +++ b/plugins/Mapstraction/map.php @@ -142,8 +142,6 @@ class MapAction extends OwnerDesignAction // of refactoring from within a plugin, so I'm just abusing // the ApiAction method. Don't do this unless you're me! - require_once(INSTALLDIR.'/lib/api.php'); - $act = new ApiAction('/dev/null'); $arr = $act->twitterStatusArray($notice, true); diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 6c212453e..2b3cb35f1 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -244,8 +244,6 @@ class RealtimePlugin extends Plugin // of refactoring from within a plugin, so I'm just abusing // the ApiAction method. Don't do this unless you're me! - require_once(INSTALLDIR.'/lib/api.php'); - $act = new ApiAction('/dev/null'); $arr = $act->twitterStatusArray($notice, true); -- cgit v1.2.3-54-g00ecf