Merge branch '0.9.x' into 1.0.x

Conflicts:
	classes/Memcached_DataObject.php
This commit is contained in:
Brion Vibber 2010-12-17 17:13:21 -08:00
commit d8a3a88ec8
20 changed files with 466 additions and 143 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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');

View File

@ -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();

View File

@ -339,11 +339,15 @@ class Memcached_DataObject extends Safe_DataObject
$start = microtime(true);
$fail = false;
$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;
$limit = common_config('db', 'log_slow_queries');

View File

@ -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);
}
}
}

View File

@ -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);
}
Notice::addWhereSinceId($nt, $since_id, 'notice_id');
Notice::addWhereMaxId($nt, $max_id, 'notice_id');
if ($max_id != 0) {
$nt->whereAdd('notice_id <= ' . $max_id);
}
$nt->orderBy('notice_id DESC');
$nt->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$nt->limit($offset, $limit);

View File

@ -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,57 +255,21 @@ 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::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
if ($max_id != 0) {
$notice->whereAdd('id <= ' . $max_id);
}
$notice->orderBy('id DESC');
$notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
$notice->find();
}
$ids = array();

View File

@ -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);
}
Notice::addWhereSinceId($reply, $since_id, 'notice_id', 'modified');
Notice::addWhereMaxId($reply, $max_id, 'notice_id', 'modified');
if ($max_id != 0) {
$reply->whereAdd('notice_id <= ' . $max_id);
}
$reply->orderBy('notice_id DESC');
$reply->orderBy('modified DESC, notice_id DESC');
if (!is_null($offset)) {
$reply->limit($offset, $limit);

View File

@ -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";

View File

@ -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);
}
Notice::addWhereSinceId($inbox, $since_id, 'notice_id');
Notice::addWhereMaxId($inbox, $max_id, 'notice_id');
if ($max_id != 0) {
$inbox->whereAdd('notice_id <= ' . $max_id);
}
$inbox->orderBy('notice_id DESC');
$inbox->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$inbox->limit($offset, $limit);

26
db/096to097.sql Normal file
View File

@ -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);

View File

@ -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;

View File

@ -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',

View File

@ -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{

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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>";
}
}

View File

@ -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();