summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrion Vibber <brion@pobox.com>2010-12-17 17:13:21 -0800
committerBrion Vibber <brion@pobox.com>2010-12-17 17:13:21 -0800
commitd8a3a88ec8eae3134694c98e5a9ae42bc30b289f (patch)
tree22554406da875404d3417edd91eaeaeef0bb693c
parentbf59eaf602c8319d5ae99d9809b4d4867cb632cd (diff)
parentfb8312ebf4537033077917d0003f716206d0d23d (diff)
Merge branch '0.9.x' into 1.0.x
Conflicts: classes/Memcached_DataObject.php
-rw-r--r--EVENTS.txt12
-rw-r--r--actions/apiuserprofileimage.php135
-rw-r--r--actions/showstream.php2
-rw-r--r--classes/Fave.php13
-rw-r--r--classes/Memcached_DataObject.php12
-rw-r--r--classes/Notice.php132
-rw-r--r--classes/Notice_tag.php11
-rw-r--r--classes/Profile.php73
-rw-r--r--classes/Reply.php11
-rw-r--r--classes/User.php25
-rw-r--r--classes/User_group.php11
-rw-r--r--db/096to097.sql26
-rw-r--r--db/statusnet.sql39
-rw-r--r--lib/router.php5
-rw-r--r--plugins/LdapCommon/LdapCommon.php4
-rw-r--r--plugins/SQLProfile/SQLProfilePlugin.php66
-rw-r--r--plugins/TwitterBridge/scripts/initialize_notice_to_status.php2
-rw-r--r--plugins/TwitterBridge/twitter.php2
-rw-r--r--plugins/TwitterBridge/twitterimport.php10
-rw-r--r--plugins/TwitterBridge/twitteroauthclient.php18
20 files changed, 466 insertions, 143 deletions
diff --git a/EVENTS.txt b/EVENTS.txt
index 9f98637bc..57f2bf6bc 100644
--- a/EVENTS.txt
+++ b/EVENTS.txt
@@ -999,7 +999,7 @@ StartXrdActionAliases: About to set aliases for the XRD object for a user
EndXrdActionAliases: Done with aliases for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
-
+
StartXrdActionLinks: About to set links for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
@@ -1007,3 +1007,13 @@ StartXrdActionLinks: About to set links for the XRD object for a user
EndXrdActionLinks: Done with links for the XRD object for a user
- &$xrd: XRD object being shown
- $user: User being shown
+
+AdminPanelCheck: When checking whether the current user can access a given admin panel
+- $name: Name of the admin panel
+- &$isOK: Boolean whether the user is allowed to use the panel
+
+StartAdminPanelNav: Before displaying the first item in the list of admin panels
+- $nav The AdminPanelNav widget
+
+EndAdminPanelNav: After displaying the last item in the list of admin panels
+- $nav The AdminPanelNav widget
diff --git a/actions/apiuserprofileimage.php b/actions/apiuserprofileimage.php
new file mode 100644
index 000000000..d2cf9a3e5
--- /dev/null
+++ b/actions/apiuserprofileimage.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Return a user's avatar image
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category API
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2010 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);
+}
+
+require_once INSTALLDIR . '/lib/apiprivateauth.php';
+
+/**
+ * Ouputs avatar URL for a user, specified by screen name.
+ * Unlike most API endpoints, this returns an HTTP redirect rather than direct data.
+ *
+ * @category API
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class ApiUserProfileImageAction extends ApiPrivateAuthAction
+{
+ /**
+ * Take arguments for running
+ *
+ * @param array $args $_REQUEST args
+ *
+ * @return boolean success flag
+ *
+ */
+ function prepare($args)
+ {
+ parent::prepare($args);
+ $this->user = User::staticGet('nickname', $this->arg('screen_name'));
+ $this->size = $this->arg('size');
+
+ return true;
+ }
+
+ /**
+ * Handle the request
+ *
+ * Check the format and show the user info
+ *
+ * @param array $args $_REQUEST data (unused)
+ *
+ * @return void
+ */
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (empty($this->user)) {
+ // TRANS: Client error displayed when requesting user information for a non-existing user.
+ $this->clientError(_('User not found.'), 404, $this->format);
+ return;
+ }
+
+ $profile = $this->user->getProfile();
+
+ if (empty($profile)) {
+ // TRANS: Client error displayed when requesting user information for a user without a profile.
+ $this->clientError(_('User has no profile.'));
+ return;
+ }
+
+ $size = $this->avatarSize();
+ $avatar = $profile->getAvatar($size);
+ if ($avatar) {
+ $url = $avatar->displayUrl();
+ } else {
+ $url = Avatar::defaultImage($size);
+ }
+
+ // We don't actually output JSON or XML data -- redirect!
+ common_redirect($url, 302);
+ }
+
+ /**
+ * Get the appropriate pixel size for an avatar based on the request...
+ *
+ * @return int
+ */
+ private function avatarSize()
+ {
+ switch ($this->size) {
+ case 'mini':
+ return AVATAR_MINI_SIZE; // 24x24
+ case 'bigger':
+ return AVATAR_PROFILE_SIZE; // Twitter does 73x73, but we do 96x96
+ case 'normal': // fall through
+ default:
+ return AVATAR_STREAM_SIZE; // 48x48
+ }
+ }
+
+ /**
+ * Return true if read only.
+ *
+ * MAY override
+ *
+ * @param array $args other arguments
+ *
+ * @return boolean is read only action?
+ */
+ function isReadOnly($args)
+ {
+ return true;
+ }
+}
diff --git a/actions/showstream.php b/actions/showstream.php
index 702edd732..14f629b6a 100644
--- a/actions/showstream.php
+++ b/actions/showstream.php
@@ -311,7 +311,7 @@ class ProfileNoticeListItem extends DoFollowListItem
'class' => 'url');
if (!empty($this->profile->fullname)) {
- $attrs['title'] = $this->getFancyName();
+ $attrs['title'] = $this->profile->getFancyName();
}
$this->out->elementStart('span', 'repeat');
diff --git a/classes/Fave.php b/classes/Fave.php
index 3aa23e7b4..4a9cfaae0 100644
--- a/classes/Fave.php
+++ b/classes/Fave.php
@@ -85,6 +85,19 @@ class Fave extends Memcached_DataObject
return $ids;
}
+ /**
+ * Note that the sorting for this is by order of *fave* not order of *notice*.
+ *
+ * @fixme add since_id, max_id support?
+ *
+ * @param <type> $user_id
+ * @param <type> $own
+ * @param <type> $offset
+ * @param <type> $limit
+ * @param <type> $since_id
+ * @param <type> $max_id
+ * @return <type>
+ */
function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id)
{
$fav = new Fave();
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php
index 27bb5d3c9..f71bfd3da 100644
--- a/classes/Memcached_DataObject.php
+++ b/classes/Memcached_DataObject.php
@@ -339,10 +339,14 @@ class Memcached_DataObject extends Safe_DataObject
$start = microtime(true);
$fail = false;
- try {
- $result = parent::_query($string);
- } catch (Exception $e) {
- $fail = $e;
+ $result = null;
+ if (Event::handle('StartDBQuery', array($this, $string, &$result))) {
+ try {
+ $result = parent::_query($string);
+ } catch (Exception $e) {
+ $fail = $e;
+ }
+ Event::handle('EndDBQuery', array($this, $string, &$result));
}
$delta = microtime(true) - $start;
diff --git a/classes/Notice.php b/classes/Notice.php
index c1150626b..6acf0c7c1 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -654,7 +654,7 @@ class Notice extends Memcached_DataObject
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
- $notice->orderBy('id DESC');
+ $notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
@@ -668,13 +668,8 @@ class Notice extends Memcached_DataObject
$notice->whereAdd('is_local !='. Notice::GATEWAY);
}
- if ($since_id != 0) {
- $notice->whereAdd('id > ' . $since_id);
- }
-
- if ($max_id != 0) {
- $notice->whereAdd('id <= ' . $max_id);
- }
+ Notice::addWhereSinceId($notice, $since_id);
+ Notice::addWhereMaxId($notice, $max_id);
$ids = array();
@@ -709,19 +704,14 @@ class Notice extends Memcached_DataObject
$notice->conversation = $id;
- $notice->orderBy('id DESC');
+ $notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
- if ($since_id != 0) {
- $notice->whereAdd('id > ' . $since_id);
- }
-
- if ($max_id != 0) {
- $notice->whereAdd('id <= ' . $max_id);
- }
+ Notice::addWhereSinceId($notice, $since_id);
+ Notice::addWhereMaxId($notice, $max_id);
$ids = array();
@@ -1695,10 +1685,10 @@ class Notice extends Memcached_DataObject
$notice->repeat_of = $this->id;
- $notice->orderBy('created'); // NB: asc!
+ $notice->orderBy('created, id'); // NB: asc!
- if (!is_null($offset)) {
- $notice->limit($offset, $limit);
+ if (!is_null($limit)) {
+ $notice->limit(0, $limit);
}
$ids = array();
@@ -1978,4 +1968,108 @@ class Notice extends Memcached_DataObject
$d = new DateTime($dateStr, new DateTimeZone('UTC'));
return $d->format(DATE_W3C);
}
+
+ /**
+ * Look up the creation timestamp for a given notice ID, even
+ * if it's been deleted.
+ *
+ * @param int $id
+ * @return mixed string recorded creation timestamp, or false if can't be found
+ */
+ public static function getAsTimestamp($id)
+ {
+ if (!$id) {
+ return false;
+ }
+
+ $notice = Notice::staticGet('id', $id);
+ if ($notice) {
+ return $notice->created;
+ }
+
+ $deleted = Deleted_notice::staticGet('id', $id);
+ if ($deleted) {
+ return $deleted->created;
+ }
+
+ return false;
+ }
+
+ /**
+ * Build an SQL 'where' fragment for timestamp-based sorting from a since_id
+ * parameter, matching notices posted after the given one (exclusive).
+ *
+ * If the referenced notice can't be found, will return false.
+ *
+ * @param int $id
+ * @param string $idField
+ * @param string $createdField
+ * @return mixed string or false if no match
+ */
+ public static function whereSinceId($id, $idField='id', $createdField='created')
+ {
+ $since = Notice::getAsTimestamp($id);
+ if ($since) {
+ return sprintf("($createdField = '%s' and $idField > %d) or ($createdField > '%s')", $since, $id, $since);
+ }
+ return false;
+ }
+
+ /**
+ * Build an SQL 'where' fragment for timestamp-based sorting from a since_id
+ * parameter, matching notices posted after the given one (exclusive), and
+ * if necessary add it to the data object's query.
+ *
+ * @param DB_DataObject $obj
+ * @param int $id
+ * @param string $idField
+ * @param string $createdField
+ * @return mixed string or false if no match
+ */
+ public static function addWhereSinceId(DB_DataObject $obj, $id, $idField='id', $createdField='created')
+ {
+ $since = self::whereSinceId($id);
+ if ($since) {
+ $obj->whereAdd($since);
+ }
+ }
+
+ /**
+ * Build an SQL 'where' fragment for timestamp-based sorting from a max_id
+ * parameter, matching notices posted before the given one (inclusive).
+ *
+ * If the referenced notice can't be found, will return false.
+ *
+ * @param int $id
+ * @param string $idField
+ * @param string $createdField
+ * @return mixed string or false if no match
+ */
+ public static function whereMaxId($id, $idField='id', $createdField='created')
+ {
+ $max = Notice::getAsTimestamp($id);
+ if ($max) {
+ return sprintf("($createdField < '%s') or ($createdField = '%s' and $idField <= %d)", $max, $max, $id);
+ }
+ return false;
+ }
+
+ /**
+ * Build an SQL 'where' fragment for timestamp-based sorting from a max_id
+ * parameter, matching notices posted before the given one (inclusive), and
+ * if necessary add it to the data object's query.
+ *
+ * @param DB_DataObject $obj
+ * @param int $id
+ * @param string $idField
+ * @param string $createdField
+ * @return mixed string or false if no match
+ */
+ public static function addWhereMaxId(DB_DataObject $obj, $id, $idField='id', $createdField='created')
+ {
+ $max = self::whereMaxId($id);
+ if ($max) {
+ $obj->whereAdd($max);
+ }
+ }
}
diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php
index ea38bb350..c8031ddf3 100644
--- a/classes/Notice_tag.php
+++ b/classes/Notice_tag.php
@@ -55,15 +55,10 @@ class Notice_tag extends Memcached_DataObject
$nt->selectAdd();
$nt->selectAdd('notice_id');
- if ($since_id != 0) {
- $nt->whereAdd('notice_id > ' . $since_id);
- }
-
- if ($max_id != 0) {
- $nt->whereAdd('notice_id <= ' . $max_id);
- }
+ Notice::addWhereSinceId($nt, $since_id, 'notice_id');
+ Notice::addWhereMaxId($nt, $max_id, 'notice_id');
- $nt->orderBy('notice_id DESC');
+ $nt->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$nt->limit($offset, $limit);
diff --git a/classes/Profile.php b/classes/Profile.php
index 1f959eb0d..977947b6f 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -215,26 +215,29 @@ class Profile extends Memcached_DataObject
function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id)
{
// XXX It would be nice to do this without a join
+ // (necessary to do it efficiently on accounts with long history)
$notice = new Notice();
$query =
"select id from notice join notice_tag on id=notice_id where tag='".
$notice->escape($tag) .
- "' and profile_id=" . $notice->escape($this->id);
+ "' and profile_id=" . intval($this->id);
- if ($since_id != 0) {
- $query .= " and id > $since_id";
+ $since = Notice::whereSinceId($since_id, 'id', 'notice.created');
+ if ($since) {
+ $query .= " and ($since)";
}
- if ($max_id != 0) {
- $query .= " and id <= $max_id";
+ $max = Notice::whereMaxId($max_id, 'id', 'notice.created');
+ if ($max) {
+ $query .= " and ($max)";
}
- $query .= ' order by id DESC';
+ $query .= ' order by notice.created DESC, id DESC';
if (!is_null($offset)) {
- $query .= " LIMIT $limit OFFSET $offset";
+ $query .= " LIMIT " . intval($limit) . " OFFSET " . intval($offset);
}
$notice->query($query);
@@ -252,58 +255,22 @@ class Profile extends Memcached_DataObject
{
$notice = new Notice();
- // Temporary hack until notice_profile_id_idx is updated
- // to (profile_id, id) instead of (profile_id, created, id).
- // It's been falling back to PRIMARY instead, which is really
- // very inefficient for a profile that hasn't posted in a few
- // months. Even though forcing the index will cause a filesort,
- // it's usually going to be better.
- if (common_config('db', 'type') == 'mysql') {
- $index = '';
- $query =
- "select id from notice force index (notice_profile_id_idx) ".
- "where profile_id=" . $notice->escape($this->id);
-
- if ($since_id != 0) {
- $query .= " and id > $since_id";
- }
-
- if ($max_id != 0) {
- $query .= " and id <= $max_id";
- }
-
- $query .= ' order by id DESC';
-
- if (!is_null($offset)) {
- $query .= " LIMIT $limit OFFSET $offset";
- }
-
- $notice->query($query);
- } else {
- $index = '';
-
- $notice->profile_id = $this->id;
-
- $notice->selectAdd();
- $notice->selectAdd('id');
-
- if ($since_id != 0) {
- $notice->whereAdd('id > ' . $since_id);
- }
+ $notice->profile_id = $this->id;
- if ($max_id != 0) {
- $notice->whereAdd('id <= ' . $max_id);
- }
+ $notice->selectAdd();
+ $notice->selectAdd('id');
- $notice->orderBy('id DESC');
+ Notice::addWhereSinceId($notice, $since_id);
+ Notice::addWhereMaxId($notice, $max_id);
- if (!is_null($offset)) {
- $notice->limit($offset, $limit);
- }
+ $notice->orderBy('created DESC, id DESC');
- $notice->find();
+ if (!is_null($offset)) {
+ $notice->limit($offset, $limit);
}
+ $notice->find();
+
$ids = array();
while ($notice->fetch()) {
diff --git a/classes/Reply.php b/classes/Reply.php
index da8a4f685..371c16cf4 100644
--- a/classes/Reply.php
+++ b/classes/Reply.php
@@ -50,15 +50,10 @@ class Reply extends Memcached_DataObject
$reply = new Reply();
$reply->profile_id = $user_id;
- if ($since_id != 0) {
- $reply->whereAdd('notice_id > ' . $since_id);
- }
-
- if ($max_id != 0) {
- $reply->whereAdd('notice_id <= ' . $max_id);
- }
+ Notice::addWhereSinceId($reply, $since_id, 'notice_id', 'modified');
+ Notice::addWhereMaxId($reply, $max_id, 'notice_id', 'modified');
- $reply->orderBy('notice_id DESC');
+ $reply->orderBy('modified DESC, notice_id DESC');
if (!is_null($offset)) {
$reply->limit($offset, $limit);
diff --git a/classes/User.php b/classes/User.php
index 4baa6d1b3..04b9b4cc6 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -750,19 +750,14 @@ class User extends Memcached_DataObject
$notice->profile_id = $this->id;
$notice->whereAdd('repeat_of IS NOT NULL');
- $notice->orderBy('id DESC');
+ $notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
- if ($since_id != 0) {
- $notice->whereAdd('id > ' . $since_id);
- }
-
- if ($max_id != 0) {
- $notice->whereAdd('id <= ' . $max_id);
- }
+ Notice::addWhereSinceId($notice, $since_id);
+ Notice::addWhereMaxId($notice, $max_id);
$ids = array();
@@ -795,17 +790,17 @@ class User extends Memcached_DataObject
'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' .
'WHERE original.profile_id = ' . $this->id . ' ';
- if ($since_id != 0) {
- $qry .= 'AND original.id > ' . $since_id . ' ';
+ $since = Notice::whereSinceId($since_id, 'original.id', 'original.created');
+ if ($since) {
+ $qry .= "AND ($since) ";
}
- if ($max_id != 0) {
- $qry .= 'AND original.id <= ' . $max_id . ' ';
+ $max = Notice::whereMaxId($max_id, 'original.id', 'original.created');
+ if ($max) {
+ $qry .= "AND ($max) ";
}
- // NOTE: we sort by fave time, not by notice time!
-
- $qry .= 'ORDER BY original.id DESC ';
+ $qry .= 'ORDER BY original.created, original.id DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
diff --git a/classes/User_group.php b/classes/User_group.php
index 60217e960..cffc78645 100644
--- a/classes/User_group.php
+++ b/classes/User_group.php
@@ -100,15 +100,10 @@ class User_group extends Memcached_DataObject
$inbox->selectAdd();
$inbox->selectAdd('notice_id');
- if ($since_id != 0) {
- $inbox->whereAdd('notice_id > ' . $since_id);
- }
-
- if ($max_id != 0) {
- $inbox->whereAdd('notice_id <= ' . $max_id);
- }
+ Notice::addWhereSinceId($inbox, $since_id, 'notice_id');
+ Notice::addWhereMaxId($inbox, $max_id, 'notice_id');
- $inbox->orderBy('notice_id DESC');
+ $inbox->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$inbox->limit($offset, $limit);
diff --git a/db/096to097.sql b/db/096to097.sql
new file mode 100644
index 000000000..209a3a881
--- /dev/null
+++ b/db/096to097.sql
@@ -0,0 +1,26 @@
+-- Add indexes for sorting changes in 0.9.7
+
+-- Allows sorting public timeline, api/statuses/repeats, and conversations by timestamp efficiently
+alter table notice
+ add index notice_created_id_is_local_idx (created,id,is_local),
+
+ add index notice_repeat_of_created_id_idx (repeat_of, created, id),
+ drop index notice_repeatof_idx,
+
+ add index notice_conversation_created_id_idx (conversation, created, id),
+ drop index notice_conversation_idx;
+
+-- Allows sorting tag-filtered public timeline by timestamp efficiently
+alter table notice_tag add index notice_tag_tag_created_notice_id_idx (tag, created, notice_id);
+
+-- Needed for sorting reply/mentions timelines
+alter table reply add index reply_profile_id_modified_notice_id_idx (profile_id, modified, notice_id);
+
+-- Needed for sorting group messages by timestamp
+alter table group_inbox add index group_inbox_group_id_created_notice_id_idx (group_id, created, notice_id);
+
+-- Helps make some reverse role lookups more efficient if there's a lot of assigned accounts
+alter table profile_role add index profile_role_role_created_profile_id_idx (role, created, profile_id);
+
+-- Fix for sorting a user's group memberships by order joined
+alter table group_member add index group_member_profile_id_created_idx (profile_id, created);
diff --git a/db/statusnet.sql b/db/statusnet.sql
index 5426ace92..debe6f095 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -126,11 +126,21 @@ create table notice (
location_ns integer comment 'namespace for location',
repeat_of integer comment 'notice this is a repeat of' references notice (id),
+ -- For public timeline...
+ index notice_created_id_is_local_idx (created,id,is_local),
+
+ -- For profile timelines...
index notice_profile_id_idx (profile_id,created,id),
- index notice_conversation_idx (conversation),
- index notice_created_idx (created),
+
+ -- For api/statuses/repeats...
+ index notice_repeat_of_created_id_idx (repeat_of, created, id),
+
+ -- For conversation views
+ index notice_conversation_created_id_idx (conversation, created, id),
+
+ -- Are these needed/used?
index notice_replyto_idx (reply_to),
- index notice_repeatof_idx (repeat_of),
+
FULLTEXT(content)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
@@ -151,7 +161,10 @@ create table reply (
constraint primary key (notice_id, profile_id),
index reply_notice_id_idx (notice_id),
index reply_profile_id_idx (profile_id),
- index reply_replied_id_idx (replied_id)
+ index reply_replied_id_idx (replied_id),
+
+ -- Needed for sorting reply/mentions timelines
+ index reply_profile_id_modified_notice_id_idx (profile_id, modified, notice_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
@@ -296,7 +309,10 @@ create table notice_tag (
constraint primary key (tag, notice_id),
index notice_tag_created_idx (created),
- index notice_tag_notice_id_idx (notice_id)
+ index notice_tag_notice_id_idx (notice_id),
+
+ -- For sorting tag-filtered public timeline
+ index notice_tag_tag_created_notice_id_idx (tag, created, notice_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
/* Synching with foreign services */
@@ -442,7 +458,10 @@ create table group_member (
constraint primary key (group_id, profile_id),
index group_member_profile_id_idx (profile_id),
- index group_member_created_idx (created)
+ index group_member_created_idx (created),
+
+ -- To pull up a list of someone's groups in order joined
+ index group_member_profile_id_created_idx (profile_id, created)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
@@ -463,7 +482,10 @@ create table group_inbox (
constraint primary key (group_id, notice_id),
index group_inbox_created_idx (created),
- index group_inbox_notice_id_idx (notice_id)
+ index group_inbox_notice_id_idx (notice_id),
+
+ -- Needed for sorting group messages by timestamp
+ index group_inbox_group_id_created_notice_id_idx (group_id, created, notice_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
@@ -603,7 +625,8 @@ create table profile_role (
role varchar(32) not null comment 'string representing the role',
created datetime not null comment 'date the role was granted',
- constraint primary key (profile_id, role)
+ constraint primary key (profile_id, role),
+ index profile_role_role_created_profile_id_idx (role, created, profile_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
diff --git a/lib/router.php b/lib/router.php
index e40eb517f..b7159e613 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -529,6 +529,11 @@ class Router
'id' => Nickname::INPUT_FMT,
'format' => '(xml|json)'));
+ $m->connect('api/users/profile_image/:screen_name.:format',
+ array('action' => 'ApiUserProfileImage',
+ 'screen_name' => Nickname::DISPLAY_FMT,
+ 'format' => '(xml|json)'));
+
// direct messages
$m->connect('api/direct_messages.:format',
diff --git a/plugins/LdapCommon/LdapCommon.php b/plugins/LdapCommon/LdapCommon.php
index 579fe4b64..3afcd824f 100644
--- a/plugins/LdapCommon/LdapCommon.php
+++ b/plugins/LdapCommon/LdapCommon.php
@@ -140,7 +140,7 @@ class LdapCommon
function checkPassword($username, $password)
{
- $entry = $this->get_user($username);
+ $entry = $this->get_user($username,array('dn' => 'dn'));
if(!$entry){
return false;
}else{
@@ -168,7 +168,7 @@ class LdapCommon
//throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time'));
return false;
}
- $entry = $this->get_user($username);
+ $entry = $this->get_user($username,array('dn' => 'dn'));
if(!$entry){
return false;
}else{
diff --git a/plugins/SQLProfile/SQLProfilePlugin.php b/plugins/SQLProfile/SQLProfilePlugin.php
new file mode 100644
index 000000000..1e49d9005
--- /dev/null
+++ b/plugins/SQLProfile/SQLProfilePlugin.php
@@ -0,0 +1,66 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Check DB queries for filesorts and such and log em.
+ *
+ * @package SQLProfilePlugin
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+class SQLProfilePlugin extends Plugin
+{
+ private $recursionGuard = false;
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'SQLProfile',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Brion Vibber',
+ 'homepage' => 'http://status.net/wiki/Plugin:SQLProfile',
+ 'rawdescription' =>
+ _m('Debug tool to watch for poorly indexed DB queries'));
+
+ return true;
+ }
+
+ function onStartDBQuery($obj, $query, &$result)
+ {
+ if (!$this->recursionGuard && preg_match('/\bselect\b/i', $query)) {
+ $this->recursionGuard = true;
+ $xobj = clone($obj);
+ $explain = $xobj->query('EXPLAIN ' . $query);
+ $this->recursionGuard = false;
+
+ while ($xobj->fetch()) {
+ $extra = $xobj->Extra;
+ $evil = (strpos($extra, 'Using filesort') !== false) ||
+ (strpos($extra, 'Using temporary') !== false);
+ if ($evil) {
+ $xquery = $xobj->sanitizeQuery($query);
+ common_log(LOG_DEBUG, "$extra | $xquery");
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/plugins/TwitterBridge/scripts/initialize_notice_to_status.php b/plugins/TwitterBridge/scripts/initialize_notice_to_status.php
index d1acfd53f..f4a60c479 100644
--- a/plugins/TwitterBridge/scripts/initialize_notice_to_status.php
+++ b/plugins/TwitterBridge/scripts/initialize_notice_to_status.php
@@ -44,7 +44,7 @@ $n->query('SELECT notice.id, notice.uri ' .
'AND notice_to_status.status_id IS NULL');
while ($n->fetch()) {
- if (preg_match('#^http://twitter.com/[\w_.]+/status/(\d+)$#', $n->uri, $match)) {
+ if (preg_match('/^http://twitter.com(/#!)?/[\w_.]+/status/(\d+)$/', $n->uri, $match)) {
$status_id = $match[1];
Notice_to_status::saveNew($n->id, $status_id);
}
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
index e8d11f3b6..a993f8ff8 100644
--- a/plugins/TwitterBridge/twitter.php
+++ b/plugins/TwitterBridge/twitter.php
@@ -45,7 +45,7 @@ function add_twitter_user($twitter_id, $screen_name)
$fuser = new Foreign_user();
$fuser->nickname = $screen_name;
- $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $fuser->uri = 'http://twitter.com/#!/' . $screen_name;
$fuser->id = $twitter_id;
$fuser->service = TWITTER_SERVICE;
$fuser->created = common_sql_now();
diff --git a/plugins/TwitterBridge/twitterimport.php b/plugins/TwitterBridge/twitterimport.php
index 143543d8e..3a4c0c43c 100644
--- a/plugins/TwitterBridge/twitterimport.php
+++ b/plugins/TwitterBridge/twitterimport.php
@@ -207,7 +207,7 @@ class TwitterImport
*/
function makeStatusURI($username, $id)
{
- return 'http://twitter.com/'
+ return 'http://twitter.com/#!/'
. $username
. '/status/'
. $id;
@@ -264,7 +264,7 @@ class TwitterImport
function ensureProfile($user)
{
// check to see if there's already a profile for this user
- $profileurl = 'http://twitter.com/' . $user->screen_name;
+ $profileurl = 'http://twitter.com/#!/' . $user->screen_name;
$profile = $this->getProfileByUrl($user->screen_name, $profileurl);
if (!empty($profile)) {
@@ -618,15 +618,15 @@ class TwitterImport
static function tagLink($tag)
{
- return "<a href='https://twitter.com/search?q=%23{$tag}' class='hashtag'>{$tag}</a>";
+ return "<a href='https://search.twitter.com/search?q=%23{$tag}' class='hashtag'>{$tag}</a>";
}
static function atLink($screenName, $fullName=null)
{
if (!empty($fullName)) {
- return "<a href='http://twitter.com/{$screenName}' title='{$fullName}'>{$screenName}</a>";
+ return "<a href='http://twitter.com/#!/{$screenName}' title='{$fullName}'>{$screenName}</a>";
} else {
- return "<a href='http://twitter.com/{$screenName}'>{$screenName}</a>";
+ return "<a href='http://twitter.com/#!/{$screenName}'>{$screenName}</a>";
}
}
diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php
index 345510a0d..a17911b03 100644
--- a/plugins/TwitterBridge/twitteroauthclient.php
+++ b/plugins/TwitterBridge/twitteroauthclient.php
@@ -43,10 +43,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/
class TwitterOAuthClient extends OAuthClient
{
- public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
- public static $authorizeURL = 'https://twitter.com/oauth/authorize';
- public static $signinUrl = 'https://twitter.com/oauth/authenticate';
- public static $accessTokenURL = 'https://twitter.com/oauth/access_token';
+ public static $requestTokenURL = 'https://api.twitter.com/oauth/request_token';
+ public static $authorizeURL = 'https://api.twitter.com/oauth/authorize';
+ public static $signinUrl = 'https://api.twitter.com/oauth/authenticate';
+ public static $accessTokenURL = 'https://api.twitter.com/oauth/access_token';
/**
* Constructor
@@ -157,7 +157,7 @@ class TwitterOAuthClient extends OAuthClient
*/
function verifyCredentials()
{
- $url = 'https://twitter.com/account/verify_credentials.json';
+ $url = 'https://api.twitter.com/1/account/verify_credentials.json';
$response = $this->oAuthGet($url);
$twitter_user = json_decode($response);
return $twitter_user;
@@ -175,7 +175,7 @@ class TwitterOAuthClient extends OAuthClient
*/
function statusesUpdate($status, $params=array())
{
- $url = 'https://twitter.com/statuses/update.json';
+ $url = 'https://api.twitter.com/1/statuses/update.json';
if (is_numeric($params)) {
$params = array('in_reply_to_status_id' => intval($params));
}
@@ -200,7 +200,7 @@ class TwitterOAuthClient extends OAuthClient
function statusesHomeTimeline($since_id = null, $max_id = null,
$cnt = null, $page = null)
{
- $url = 'https://twitter.com/statuses/home_timeline.json';
+ $url = 'https://api.twitter.com/1/statuses/home_timeline.json';
$params = array('include_entities' => 'true');
@@ -235,7 +235,7 @@ class TwitterOAuthClient extends OAuthClient
function statusesFriends($id = null, $user_id = null, $screen_name = null,
$page = null)
{
- $url = "https://twitter.com/statuses/friends.json";
+ $url = "https://api.twitter.com/1/statuses/friends.json";
$params = array();
@@ -273,7 +273,7 @@ class TwitterOAuthClient extends OAuthClient
function friendsIds($id = null, $user_id = null, $screen_name = null,
$page = null)
{
- $url = "https://twitter.com/friends/ids.json";
+ $url = "https://api.twitter.com/1/friends/ids.json";
$params = array();