summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorEvan Prodromou <evan@controlyourself.ca>2009-08-21 15:42:11 -0400
committerEvan Prodromou <evan@controlyourself.ca>2009-08-21 15:42:11 -0400
commitb2664e1ae2e2cf66585cdd8696d88efdd053eb3b (patch)
tree3e406bc5502c0937f2cf81e0b4a6a1b714a1b403 /lib
parentc78772b2748f70acc8158b665218fe53b277a031 (diff)
parent9f07921b45190b462e1a798622068e24ef31e124 (diff)
Merge branch '0.8.x' into 0.9.x
Conflicts: actions/updateprofile.php actions/userauthorization.php classes/User_group.php index.php install.php lib/accountsettingsaction.php lib/logingroupnav.php
Diffstat (limited to 'lib')
-rw-r--r--lib/action.php77
-rw-r--r--lib/arraywrapper.php4
-rw-r--r--lib/common.php20
-rw-r--r--lib/connectsettingsaction.php30
-rw-r--r--lib/designsettings.php17
-rw-r--r--lib/error.php8
-rw-r--r--lib/facebookaction.php36
-rw-r--r--lib/facebookutil.php232
-rw-r--r--lib/htmloutputter.php55
-rw-r--r--lib/jsonsearchresultslist.php4
-rw-r--r--lib/mail.php55
-rw-r--r--lib/noticelist.php7
-rw-r--r--lib/oauthclient.php225
-rw-r--r--lib/parallelizingdaemon.php229
-rw-r--r--lib/profilesection.php2
-rw-r--r--lib/router.php37
-rw-r--r--lib/search_engines.php2
-rw-r--r--lib/servererroraction.php5
-rw-r--r--lib/twitter.php378
-rw-r--r--lib/twitterapi.php151
-rw-r--r--lib/twitteroauthclient.php220
-rw-r--r--lib/unqueuemanager.php4
-rw-r--r--lib/util.php86
23 files changed, 1231 insertions, 653 deletions
diff --git a/lib/action.php b/lib/action.php
index 158870fa8..4d724fba5 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -196,21 +196,12 @@ class Action extends HTMLOutputter // lawsuit
if (Event::handle('StartShowStyles', array($this))) {
if (Event::handle('StartShowLaconicaStyles', array($this))) {
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
- 'media' => 'screen, projection, tv'));
+ $this->cssLink('css/display.css',null,'screen, projection, tv');
if (common_config('site', 'mobile')) {
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
- // TODO: "handheld" CSS for other mobile devices
- 'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
+ // TODO: "handheld" CSS for other mobile devices
+ $this->cssLink('css/mobile.css','base','only screen and (max-device-width: 480px)'); // Mobile WebKit
}
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/print.css', 'base') . '?version=' . LACONICA_VERSION,
- 'media' => 'print'));
+ $this->cssLink('css/print.css','base','print');
Event::handle('EndShowLaconicaStyles', array($this));
}
@@ -256,26 +247,14 @@ class Action extends HTMLOutputter // lawsuit
{
if (Event::handle('StartShowScripts', array($this))) {
if (Event::handle('StartShowJQueryScripts', array($this))) {
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/jquery.min.js')),
- ' ');
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/jquery.form.js')),
- ' ');
-
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/jquery.joverlay.min.js')),
- ' ');
-
+ $this->script('js/jquery.min.js');
+ $this->script('js/jquery.form.js');
+ $this->script('js/jquery.joverlay.min.js');
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/xbImportNode.js')),
- ' ');
- $this->element('script', array('type' => 'text/javascript',
- 'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
- ' ');
+ $this->script('js/xbImportNode.js');
+ $this->script('js/util.js');
// Frame-busting code to avoid clickjacking attacks.
$this->element('script', array('type' => 'text/javascript'),
'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
@@ -426,6 +405,14 @@ class Action extends HTMLOutputter // lawsuit
function showPrimaryNav()
{
$user = common_current_user();
+ $connect = '';
+ if (common_config('xmpp', 'enabled')) {
+ $connect = 'imsettings';
+ } else if (common_config('sms', 'enabled')) {
+ $connect = 'smssettings';
+ } else if (common_config('twitter', 'enabled')) {
+ $connect = 'twittersettings';
+ }
$this->elementStart('dl', array('id' => 'site_nav_global_primary'));
$this->element('dt', null, _('Primary site navigation'));
@@ -437,12 +424,9 @@ class Action extends HTMLOutputter // lawsuit
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$this->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
- if (common_config('xmpp', 'enabled')) {
- $this->menuItem(common_local_url('imsettings'),
- _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
- } else {
- $this->menuItem(common_local_url('smssettings'),
- _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
+ if ($connect) {
+ $this->menuItem(common_local_url($connect),
+ _('Connect'), _('Connect to services'), false, 'nav_connect');
}
if (common_config('invite', 'enabled')) {
$this->menuItem(common_local_url('invite'),
@@ -455,17 +439,24 @@ class Action extends HTMLOutputter // lawsuit
_('Logout'), _('Logout from the site'), false, 'nav_logout');
}
else {
- if (!common_config('site', 'closed')) {
- $this->menuItem(common_local_url('register'),
- _('Register'), _('Create an account'), false, 'nav_register');
+ if (!common_config('site', 'openidonly')) {
+ if (!common_config('site', 'closed')) {
+ $this->menuItem(common_local_url('register'),
+ _('Register'), _('Create an account'), false, 'nav_register');
+ }
+ $this->menuItem(common_local_url('login'),
+ _('Login'), _('Login to the site'), false, 'nav_login');
+ } else {
+ $this->menuItem(common_local_url('openidlogin'),
+ _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
}
- $this->menuItem(common_local_url('login'),
- _('Login'), _('Login to the site'), false, 'nav_login');
}
$this->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
- $this->menuItem(common_local_url('peoplesearch'),
- _('Search'), _('Search for people or text'), false, 'nav_search');
+ if ($user || !common_config('site', 'private')) {
+ $this->menuItem(common_local_url('peoplesearch'),
+ _('Search'), _('Search for people or text'), false, 'nav_search');
+ }
Event::handle('EndPrimaryNav', array($this));
}
$this->elementEnd('ul');
diff --git a/lib/arraywrapper.php b/lib/arraywrapper.php
index a8a12b3bb..47ae057dc 100644
--- a/lib/arraywrapper.php
+++ b/lib/arraywrapper.php
@@ -25,12 +25,14 @@ class ArrayWrapper
{
var $_items = null;
var $_count = 0;
+ var $N = 0;
var $_i = -1;
function __construct($items)
{
$this->_items = $items;
$this->_count = count($this->_items);
+ $this->N = $this->_count;
}
function fetch()
@@ -76,4 +78,4 @@ class ArrayWrapper
$item =& $this->_items[$this->_i];
return call_user_func_array(array($item, $name), $args);
}
-} \ No newline at end of file
+}
diff --git a/lib/common.php b/lib/common.php
index a9eef13ff..84ee5be15 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -82,7 +82,7 @@ if (isset($server)) {
if (isset($path)) {
$_path = $path;
} else {
- $_path = array_key_exists('SCRIPT_NAME', $_SERVER) ?
+ $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
_sn_to_path($_SERVER['SCRIPT_NAME']) :
null;
}
@@ -109,6 +109,7 @@ $config =
'broughtbyurl' => null,
'closed' => false,
'inviteonly' => false,
+ 'openidonly' => false,
'private' => false,
'ssl' => 'never',
'sslserver' => null,
@@ -172,6 +173,8 @@ $config =
'host' => null, # only set if != server
'debug' => false, # print extra debug info
'public' => array()), # JIDs of users who want to receive the public stream
+ 'openid' =>
+ array('enabled' => true),
'invite' =>
array('enabled' => true),
'sphinx' =>
@@ -186,11 +189,20 @@ $config =
array('piddir' => '/var/run',
'user' => false,
'group' => false),
+ 'emailpost' =>
+ array('enabled' => true),
+ 'sms' =>
+ array('enabled' => true),
+ 'twitter' =>
+ array('enabled' => true),
'twitterbridge' =>
array('enabled' => false),
'integration' =>
array('source' => 'Laconica', # source attribute for Twitter
'taguri' => $_server.',2009'), # base for tag URIs
+ 'twitter' =>
+ array('consumer_key' => null,
+ 'consumer_secret' => null),
'memcached' =>
array('enabled' => false,
'server' => 'localhost',
@@ -369,6 +381,12 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
$config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/laconica.ini';
}
+// Ignore openidonly if OpenID is disabled
+
+if (!$config['openid']['enabled']) {
+ $config['site']['openidonly'] = false;
+}
+
// XXX: how many of these could be auto-loaded on use?
require_once 'Validate.php';
diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php
index 30629680e..02c468a35 100644
--- a/lib/connectsettingsaction.php
+++ b/lib/connectsettingsaction.php
@@ -99,25 +99,27 @@ class ConnectSettingsNav extends Widget
function show()
{
# action => array('prompt', 'title')
- $menu =
- array('imsettings' =>
- array(_('IM'),
- _('Updates by instant messenger (IM)')),
- 'smssettings' =>
- array(_('SMS'),
- _('Updates by SMS')),
- 'twittersettings' =>
- array(_('Twitter'),
- _('Twitter integration options')));
+ $menu = array();
+ if (common_config('xmpp', 'enabled')) {
+ $menu['imsettings'] =
+ array(_('IM'),
+ _('Updates by instant messenger (IM)'));
+ }
+ if (common_config('sms', 'enabled')) {
+ $menu['smssettings'] =
+ array(_('SMS'),
+ _('Updates by SMS'));
+ }
+ if (common_config('twitter', 'enabled')) {
+ $menu['twittersettings'] =
+ array(_('Twitter'),
+ _('Twitter integration options'));
+ }
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
- if ($menuaction == 'imsettings' &&
- !common_config('xmpp', 'enabled')) {
- continue;
- }
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
diff --git a/lib/designsettings.php b/lib/designsettings.php
index 1b0e62166..a48ec9d22 100644
--- a/lib/designsettings.php
+++ b/lib/designsettings.php
@@ -311,13 +311,7 @@ class DesignSettingsAction extends AccountSettingsAction
function showStylesheets()
{
parent::showStylesheets();
- $farbtasticStyle =
- common_path('theme/base/css/farbtastic.css?version='.LACONICA_VERSION);
-
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => $farbtasticStyle,
- 'media' => 'screen, projection, tv'));
+ $this->cssLink('css/farbtastic.css','base','screen, projection, tv');
}
/**
@@ -330,13 +324,8 @@ class DesignSettingsAction extends AccountSettingsAction
{
parent::showScripts();
- $farbtasticPack = common_path('js/farbtastic/farbtastic.js');
- $userDesignGo = common_path('js/userdesign.go.js');
-
- $this->element('script', array('type' => 'text/javascript',
- 'src' => $farbtasticPack));
- $this->element('script', array('type' => 'text/javascript',
- 'src' => $userDesignGo));
+ $this->script('js/farbtastic/farbtastic.js');
+ $this->script('js/farbtastic/farbtastic.go.js');
}
/**
diff --git a/lib/error.php b/lib/error.php
index bbf9987cf..3127c83fe 100644
--- a/lib/error.php
+++ b/lib/error.php
@@ -72,7 +72,7 @@ class ErrorAction extends Action
$status_string = $this->status[$this->code];
header('HTTP/1.1 '.$this->code.' '.$status_string);
}
-
+
/**
* Display content.
*
@@ -97,11 +97,11 @@ class ErrorAction extends Action
{
return true;
}
-
- function showPage()
+
+ function showPage()
{
parent::showPage();
-
+
// We don't want to have any more output after this
exit();
}
diff --git a/lib/facebookaction.php b/lib/facebookaction.php
index 08141177a..4edd3a077 100644
--- a/lib/facebookaction.php
+++ b/lib/facebookaction.php
@@ -94,34 +94,13 @@ class FacebookAction extends Action
function showStylesheets()
{
- // Add a timestamp to the file so Facebook cache wont ignore our changes
- $ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
-
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
-
- $theme = common_config('site', 'theme');
-
- $ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css');
-
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/display.css', null) . '?ts=' . $ts));
-
- $ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css');
-
- $this->element('link', array('rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts));
+ $this->cssLink('css/display.css', 'base');
+ $this->cssLink('css/facebookapp.css', 'base');
}
function showScripts()
{
- // Add a timestamp to the file so Facebook cache wont ignore our changes
- $ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
-
- $this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts));
+ $this->script('js/facebookapp.js');
}
/**
@@ -274,8 +253,13 @@ class FacebookAction extends Action
$this->elementStart('dd');
$this->elementStart('p');
$this->text(sprintf($loginmsg_part1, common_config('site', 'name')));
- $this->element('a',
- array('href' => common_local_url('register')), _('Register'));
+ if (!common_config('site', 'openidonly')) {
+ $this->element('a',
+ array('href' => common_local_url('register')), _('Register'));
+ } else {
+ $this->element('a',
+ array('href' => common_local_url('openidlogin')), _('Register'));
+ }
$this->text($loginmsg_part2);
$this->elementEnd('p');
$this->elementEnd('dd');
diff --git a/lib/facebookutil.php b/lib/facebookutil.php
index b7688f04f..e31a71f5e 100644
--- a/lib/facebookutil.php
+++ b/lib/facebookutil.php
@@ -36,7 +36,7 @@ function getFacebook()
$facebook = new Facebook($apikey, $secret);
}
- if (!$facebook) {
+ if (empty($facebook)) {
common_log(LOG_ERR, 'Could not make new Facebook client obj!',
__FILE__);
}
@@ -44,71 +44,37 @@ function getFacebook()
return $facebook;
}
-function updateProfileBox($facebook, $flink, $notice) {
- $fbaction = new FacebookAction($output='php://output', $indent=true, $facebook, $flink);
- $fbaction->updateProfileBox($notice);
-}
-
function isFacebookBound($notice, $flink) {
if (empty($flink)) {
return false;
}
+ // Avoid a loop
+
+ if ($notice->source == 'Facebook') {
+ common_log(LOG_INFO, "Skipping notice $notice->id because its " .
+ 'source is Facebook.');
+ return false;
+ }
+
// If the user does not want to broadcast to Facebook, move along
+
if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
common_log(LOG_INFO, "Skipping notice $notice->id " .
'because user has FOREIGN_NOTICE_SEND bit off.');
return false;
}
- $success = false;
+ // If it's not a reply, or if the user WANTS to send @-replies,
+ // then, yeah, it can go to Facebook.
- // If it's not a reply, or if the user WANTS to send @-replies...
if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
-
- $success = true;
-
- // The two condition below are deal breakers:
-
- // Avoid a loop
- if ($notice->source == 'Facebook') {
- common_log(LOG_INFO, "Skipping notice $notice->id because its " .
- 'source is Facebook.');
- $success = false;
- }
-
- $facebook = getFacebook();
- $fbuid = $flink->foreign_id;
-
- try {
-
- // Check to see if the user has given the FB app status update perms
- $result = $facebook->api_client->
- users_hasAppPermission('publish_stream', $fbuid);
-
- if ($result != 1) {
- $result = $facebook->api_client->
- users_hasAppPermission('status_update', $fbuid);
- }
- if ($result != 1) {
- $user = $flink->getUser();
- $msg = "Not sending notice $notice->id to Facebook " .
- "because user $user->nickname hasn't given the " .
- 'Facebook app \'status_update\' or \'publish_stream\' permission.';
- common_debug($msg);
- $success = false;
- }
-
- } catch(FacebookRestClientException $e){
- common_log(LOG_ERR, $e->getMessage());
- $success = false;
- }
-
+ return true;
}
- return $success;
+ return false;
}
@@ -119,88 +85,65 @@ function facebookBroadcastNotice($notice)
if (isFacebookBound($notice, $flink)) {
+ // Okay, we're good to go, update the FB status
+
$status = null;
$fbuid = $flink->foreign_id;
-
$user = $flink->getUser();
-
- // Get the status 'verb' (prefix) the user has set
+ $attachments = $notice->attachments();
try {
- $prefix = $facebook->api_client->
- data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
+
+ // Get the status 'verb' (prefix) the user has set
+
+ // XXX: Does this call count against our per user FB request limit?
+ // If so we should consider storing verb elsewhere or not storing
+
+ $prefix = $facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX,
+ $fbuid);
$status = "$prefix $notice->content";
- } catch(FacebookRestClientException $e) {
- common_log(LOG_WARNING, $e->getMessage());
- common_log(LOG_WARNING,
- 'Unable to get the status verb setting from Facebook ' .
- "for $user->nickname (user id: $user->id).");
- }
+ $can_publish = $facebook->api_client->users_hasAppPermission('publish_stream',
+ $fbuid);
- // Okay, we're good to go, update the FB status
+ $can_update = $facebook->api_client->users_hasAppPermission('status_update',
+ $fbuid);
- try {
- $result = $facebook->api_client->
- users_hasAppPermission('publish_stream', $fbuid);
- if($result == 1){
- // authorized to use the stream api, so use it
- $fbattachment = null;
- $attachments = $notice->attachments();
- if($attachments){
- $fbattachment=array();
- $fbattachment['media']=array();
- //facebook only supports one attachment per item
- $attachment = $attachments[0];
- $fbmedia=array();
- if(strncmp($attachment->mimetype,'image/',strlen('image/'))==0){
- $fbmedia['type']='image';
- $fbmedia['src']=$attachment->url;
- $fbmedia['href']=$attachment->url;
- $fbattachment['media'][]=$fbmedia;
-/* Video doesn't seem to work. The notice never makes it to facebook, and no error is reported.
- }else if(strncmp($attachment->mimetype,'video/',strlen('image/'))==0 || $attachment->mimetype="application/ogg"){
- $fbmedia['type']='video';
- $fbmedia['video_src']=$attachment->url;
- // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
- // says that preview_img is required... but we have no value to put in it
- // $fbmedia['preview_img']=$attachment->url;
- if($attachment->title){
- $fbmedia['video_title']=$attachment->title;
- }
- $fbmedia['video_type']=$attachment->mimetype;
- $fbattachment['media'][]=$fbmedia;
-*/
- }else if($attachment->mimetype=='audio/mpeg'){
- $fbmedia['type']='mp3';
- $fbmedia['src']=$attachment->url;
- $fbattachment['media'][]=$fbmedia;
- }else if($attachment->mimetype=='application/x-shockwave-flash'){
- $fbmedia['type']='flash';
- // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
- // says that imgsrc is required... but we have no value to put in it
- // $fbmedia['imgsrc']='';
- $fbmedia['swfsrc']=$attachment->url;
- $fbattachment['media'][]=$fbmedia;
- }else{
- $fbattachment['name']=($attachment->title?$attachment->title:$attachment->url);
- $fbattachment['href']=$attachment->url;
- }
- }
- $facebook->api_client->stream_publish($status, $fbattachment, null, null, $fbuid);
- }else{
+ if (!empty($attachments) && $can_publish == 1) {
+ $fbattachment = format_attachments($attachments);
+ $facebook->api_client->stream_publish($status, $fbattachment,
+ null, null, $fbuid);
+ common_log(LOG_INFO,
+ "Posted notice $notice->id w/attachment " .
+ "to Facebook user's stream (fbuid = $fbuid).");
+ } elseif ($can_update == 1 || $can_publish == 1) {
$facebook->api_client->users_setStatus($status, $fbuid, false, true);
+ common_log(LOG_INFO,
+ "Posted notice $notice->id to Facebook " .
+ "as a status update (fbuid = $fbuid).");
+ } else {
+ $msg = "Not sending notice $notice->id to Facebook " .
+ "because user $user->nickname hasn't given the " .
+ 'Facebook app \'status_update\' or \'publish_stream\' permission.';
+ common_log(LOG_WARNING, $msg);
+ }
+
+ // Finally, attempt to update the user's profile box
+
+ if ($can_publish == 1 || $can_update == 1) {
+ updateProfileBox($facebook, $flink, $notice);
}
- } catch(FacebookRestClientException $e) {
+
+ } catch (FacebookRestClientException $e) {
$code = $e->getCode();
- common_log(LOG_ERR, 'Facebook returned error code ' .
- $code . ': ' . $e->getMessage());
- common_log(LOG_ERR,
- 'Unable to update Facebook status for ' .
- "$user->nickname (user id: $user->id)!");
+ common_log(LOG_WARNING, 'Facebook returned error code ' .
+ $code . ': ' . $e->getMessage());
+ common_log(LOG_WARNING,
+ 'Unable to update Facebook status for ' .
+ "$user->nickname (user id: $user->id)!");
if ($code == 200 || $code == 250) {
@@ -209,25 +152,62 @@ function facebookBroadcastNotice($notice)
// see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML
remove_facebook_app($flink);
+
+ } else {
+
+ // Try sending again later.
+
+ return false;
}
}
+ }
- // Now try to update the profile box
+ return true;
- try {
- updateProfileBox($facebook, $flink, $notice);
- } catch(FacebookRestClientException $e) {
- common_log(LOG_ERR, 'Facebook returned error code ' .
- $e->getCode() . ': ' . $e->getMessage());
- common_log(LOG_WARNING,
- 'Unable to update Facebook profile box for ' .
- "$user->nickname (user id: $user->id).");
- }
+}
+function updateProfileBox($facebook, $flink, $notice) {
+ $fbaction = new FacebookAction($output = 'php://output',
+ $indent = true, $facebook, $flink);
+ $fbaction->updateProfileBox($notice);
+}
+
+function format_attachments($attachments)
+{
+ $fbattachment = array();
+ $fbattachment['media'] = array();
+
+ // Facebook only supports one attachment per item
+
+ $attachment = $attachments[0];
+ $fbmedia = array();
+
+ if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) {
+ $fbmedia['type'] = 'image';
+ $fbmedia['src'] = $attachment->url;
+ $fbmedia['href'] = $attachment->url;
+ $fbattachment['media'][] = $fbmedia;
+ } else if ($attachment->mimetype == 'audio/mpeg') {
+ $fbmedia['type'] = 'mp3';
+ $fbmedia['src'] = $attachment->url;
+ $fbattachment['media'][] = $fbmedia;
+ }else if ($attachment->mimetype == 'application/x-shockwave-flash') {
+ $fbmedia['type'] = 'flash';
+
+ // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
+ // says that imgsrc is required... but we have no value to put in it
+ // $fbmedia['imgsrc']='';
+
+ $fbmedia['swfsrc'] = $attachment->url;
+ $fbattachment['media'][] = $fbmedia;
+ }else{
+ $fbattachment['name'] = ($attachment->title ?
+ $attachment->title : $attachment->url);
+ $fbattachment['href'] = $attachment->url;
}
- return true;
+ return $fbattachment;
}
function remove_facebook_app($flink)
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index 06603ac05..683a5e0b7 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -109,10 +109,11 @@ class HTMLOutputter extends XMLOutputter
header('Content-Type: '.$type);
$this->extraHeaders();
-
- $this->startXML('html',
- '-//W3C//DTD XHTML 1.0 Strict//EN',
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+ if( ! substr($type,0,strlen('text/html'))=='text/html' ){
+ // Browsers don't like it when <?xml it output for non-xhtml documents
+ $this->xw->startDocument('1.0', 'UTF-8');
+ }
+ $this->xw->writeDTD('html');
$language = $this->getLanguage();
@@ -339,6 +340,52 @@ class HTMLOutputter extends XMLOutputter
}
/**
+ * output a script (almost always javascript) tag
+ *
+ * @param string $src relative or absolute script path
+ * @param string $type 'type' attribute value of the tag
+ *
+ * @return void
+ */
+ function script($src, $type='text/javascript')
+ {
+ $url = parse_url($src);
+ if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
+ {
+ $src = common_path($src) . '?version=' . LACONICA_VERSION;
+ }
+ $this->element('script', array('type' => $type,
+ 'src' => $src),
+ ' ');
+ }
+
+ /**
+ * output a css link
+ *
+ * @param string $src relative path within the theme directory, or an absolute path
+ * @param string $theme 'theme' that contains the stylesheet
+ * @param string media 'media' attribute of the tag
+ *
+ * @return void
+ */
+ function cssLink($src,$theme=null,$media=null)
+ {
+ $url = parse_url($src);
+ if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
+ {
+ if(file_exists(theme_file($src,$theme))){
+ $src = theme_path($src, $theme) . '?version=' . LACONICA_VERSION;
+ }else{
+ $src = common_path($src);
+ }
+ }
+ $this->element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/css',
+ 'href' => $src,
+ 'media' => $media));
+ }
+
+ /**
* output an HTML textarea and associated elements
*
* @param string $id element ID, must be unique on page
diff --git a/lib/jsonsearchresultslist.php b/lib/jsonsearchresultslist.php
index 7beea9328..34a3d530e 100644
--- a/lib/jsonsearchresultslist.php
+++ b/lib/jsonsearchresultslist.php
@@ -207,7 +207,7 @@ class ResultItem
$replier_profile = null;
if ($this->notice->reply_to) {
- $reply = Notice::staticGet(intval($notice->reply_to));
+ $reply = Notice::staticGet(intval($this->notice->reply_to));
if ($reply) {
$replier_profile = $reply->getProfile();
}
@@ -224,7 +224,7 @@ class ResultItem
$user = User::staticGet('id', $this->profile->id);
- $this->iso_language_code = $this->user->language;
+ $this->iso_language_code = $user->language;
$this->source = $this->getSourceLink($this->notice->source);
diff --git a/lib/mail.php b/lib/mail.php
index 0050ad810..2e471fd6d 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -596,32 +596,44 @@ function mail_notify_attn($user, $notice)
$bestname = $sender->getBestName();
common_init_locale($user->language);
-
+
+ if ($notice->conversation != $notice->id) {
+ $conversationEmailText = "The full conversation can be read here:\n\n".
+ "\t%5\$s\n\n ";
+ $conversationUrl = common_local_url('conversation',
+ array('id' => $notice->conversation)).'#notice-'.$notice->id;
+ } else {
+ $conversationEmailText = "%5\$s";
+ $conversationUrl = null;
+ }
+
$subject = sprintf(_('%s sent a notice to your attention'), $bestname);
-
- $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
+
+ $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
"The notice is here:\n\n".
"\t%3\$s\n\n" .
"It reads:\n\n".
"\t%4\$s\n\n" .
+ $conversationEmailText .
"You can reply back here:\n\n".
- "\t%5\$s\n\n" .
+ "\t%6\$s\n\n" .
"The list of all @-replies for you here:\n\n" .
- "%6\$s\n\n" .
+ "%7\$s\n\n" .
"Faithfully yours,\n" .
"%2\$s\n\n" .
- "P.S. You can turn off these email notifications here: %7\$s\n"),
- $bestname,
- common_config('site', 'name'),
+ "P.S. You can turn off these email notifications here: %8\$s\n"),
+ $bestname,//%1
+ common_config('site', 'name'),//%2
common_local_url('shownotice',
- array('notice' => $notice->id)),
- $notice->content,
+ array('notice' => $notice->id)),//%3
+ $notice->content,//%4
+ $conversationUrl,//%5
common_local_url('newnotice',
- array('replyto' => $sender->nickname)),
+ array('replyto' => $sender->nickname)),//%6
common_local_url('replies',
- array('nickname' => $user->nickname)),
- common_local_url('emailsettings'));
-
+ array('nickname' => $user->nickname)),//%7
+ common_local_url('emailsettings'));//%8
+
common_init_locale();
mail_to_user($user, $subject, $body);
}
@@ -645,13 +657,14 @@ function mail_twitter_bridge_removed($user)
$subject = sprintf(_('Your Twitter bridge has been disabled.'));
- $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that your " .
- 'link to Twitter has been disabled. Your Twitter credentials ' .
- 'have either changed (did you recently change your Twitter ' .
- 'password?) or you have otherwise revoked our access to your ' .
- "Twitter account.\n\n" .
- 'You can re-enable your Twitter bridge by visiting your ' .
- "Twitter settings page:\n\n\t%2\$s\n\n" .
+ $site_name = common_config('site', 'name');
+
+ $body = sprintf(_('Hi, %1$s. We\'re sorry to inform you that your ' .
+ 'link to Twitter has been disabled. We no longer seem to have ' .
+ 'permission to update your Twitter status. (Did you revoke ' .
+ '%3$s\'s access?)' . "\n\n" .
+ 'You can re-enable your Twitter bridge by visiting your ' .
+ "Twitter settings page:\n\n\t%2\$s\n\n" .
"Regards,\n%3\$s\n"),
$profile->getBestName(),
common_local_url('twittersettings'),
diff --git a/lib/noticelist.php b/lib/noticelist.php
index a8d5059ca..5429d943f 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -350,11 +350,10 @@ class NoticeListItem extends Widget
function showNoticeLink()
{
- $noticeurl = common_local_url('shownotice',
+ if($this->notice->is_local){
+ $noticeurl = common_local_url('shownotice',
array('notice' => $this->notice->id));
- // XXX: we need to figure this out better. Is this right?
- if (strcmp($this->notice->uri, $noticeurl) != 0 &&
- preg_match('/^http/', $this->notice->uri)) {
+ }else{
$noticeurl = $this->notice->uri;
}
$this->out->elementStart('a', array('rel' => 'bookmark',
diff --git a/lib/oauthclient.php b/lib/oauthclient.php
new file mode 100644
index 000000000..b66a24be4
--- /dev/null
+++ b/lib/oauthclient.php
@@ -0,0 +1,225 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for doing OAuth calls as a consumer
+ *
+ * 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 Action
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once 'OAuth.php';
+
+/**
+ * Exception wrapper for cURL errors
+ *
+ * @category Integration
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+class OAuthClientCurlException extends Exception
+{
+}
+
+/**
+ * Base class for doing OAuth calls as a consumer
+ *
+ * @category Integration
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+class OAuthClient
+{
+ var $consumer;
+ var $token;
+
+ /**
+ * Constructor
+ *
+ * Can be initialized with just consumer key and secret for requesting new
+ * tokens or with additional request token or access token
+ *
+ * @param string $consumer_key consumer key
+ * @param string $consumer_secret consumer secret
+ * @param string $oauth_token user's token
+ * @param string $oauth_token_secret user's secret
+ *
+ * @return nothing
+ */
+ function __construct($consumer_key, $consumer_secret,
+ $oauth_token = null, $oauth_token_secret = null)
+ {
+ $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
+ $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
+ $this->token = null;
+
+ if (isset($oauth_token) && isset($oauth_token_secret)) {
+ $this->token = new OAuthToken($oauth_token, $oauth_token_secret);
+ }
+ }
+
+ /**
+ * Gets a request token from the given url
+ *
+ * @param string $url OAuth endpoint for grabbing request tokens
+ *
+ * @return OAuthToken $token the request token
+ */
+ function getRequestToken($url)
+ {
+ $response = $this->oAuthGet($url);
+ parse_str($response);
+ $token = new OAuthToken($oauth_token, $oauth_token_secret);
+ return $token;
+ }
+
+ /**
+ * Builds a link that can be redirected to in order to
+ * authorize a request token.
+ *
+ * @param string $url endpoint for authorizing request tokens
+ * @param OAuthToken $request_token the request token to be authorized
+ * @param string $oauth_callback optional callback url
+ *
+ * @return string $authorize_url the url to redirect to
+ */
+ function getAuthorizeLink($url, $request_token, $oauth_callback = null)
+ {
+ $authorize_url = $url . '?oauth_token=' .
+ $request_token->key;
+
+ if (isset($oauth_callback)) {
+ $authorize_url .= '&oauth_callback=' . urlencode($oauth_callback);
+ }
+
+ return $authorize_url;
+ }
+
+ /**
+ * Fetches an access token
+ *
+ * @param string $url OAuth endpoint for exchanging authorized request tokens
+ * for access tokens
+ *
+ * @return OAuthToken $token the access token
+ */
+ function getAccessToken($url)
+ {
+ $response = $this->oAuthPost($url);
+ parse_str($response);
+ $token = new OAuthToken($oauth_token, $oauth_token_secret);
+ return $token;
+ }
+
+ /**
+ * Use HTTP GET to make a signed OAuth request
+ *
+ * @param string $url OAuth endpoint
+ *
+ * @return mixed the request
+ */
+ function oAuthGet($url)
+ {
+ $request = OAuthRequest::from_consumer_and_token($this->consumer,
+ $this->token, 'GET', $url, null);
+ $request->sign_request($this->sha1_method,
+ $this->consumer, $this->token);
+
+ return $this->httpRequest($request->to_url());
+ }
+
+ /**
+ * Use HTTP POST to make a signed OAuth request
+ *
+ * @param string $url OAuth endpoint
+ * @param array $params additional post parameters
+ *
+ * @return mixed the request
+ */
+ function oAuthPost($url, $params = null)
+ {
+ $request = OAuthRequest::from_consumer_and_token($this->consumer,
+ $this->token, 'POST', $url, $params);
+ $request->sign_request($this->sha1_method,
+ $this->consumer, $this->token);
+
+ return $this->httpRequest($request->get_normalized_http_url(),
+ $request->to_postdata());
+ }
+
+ /**
+ * Make a HTTP request using cURL.
+ *
+ * @param string $url Where to make the
+ * @param array $params post parameters
+ *
+ * @return mixed the request
+ */
+ function httpRequest($url, $params = null)
+ {
+ $options = array(
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_USERAGENT => 'Laconica',
+ CURLOPT_CONNECTTIMEOUT => 120,
+ CURLOPT_TIMEOUT => 120,
+ CURLOPT_HTTPAUTH => CURLAUTH_ANY,
+ CURLOPT_SSL_VERIFYPEER => false,
+
+ // Twitter is strict about accepting invalid "Expect" headers
+
+ CURLOPT_HTTPHEADER => array('Expect:')
+ );
+
+ if (isset($params)) {
+ $options[CURLOPT_POST] = true;
+ $options[CURLOPT_POSTFIELDS] = $params;
+ }
+
+ $ch = curl_init($url);
+ curl_setopt_array($ch, $options);
+ $response = curl_exec($ch);
+
+ if ($response === false) {
+ $msg = curl_error($ch);
+ $code = curl_errno($ch);
+ throw new OAuthClientCurlException($msg, $code);
+ }
+
+ curl_close($ch);
+
+ return $response;
+ }
+
+}
diff --git a/lib/parallelizingdaemon.php b/lib/parallelizingdaemon.php
new file mode 100644
index 000000000..dc28b5643
--- /dev/null
+++ b/lib/parallelizingdaemon.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for making daemons that can do several tasks in parallel.
+ *
+ * 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 Daemon
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+declare(ticks = 1);
+
+/**
+ * Daemon able to spawn multiple child processes to do work in parallel
+ *
+ * @category Daemon
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class ParallelizingDaemon extends Daemon
+{
+ private $_children = array();
+ private $_interval = 0; // seconds
+ private $_max_children = 0; // maximum number of children
+ private $_debug = false;
+
+ /**
+ * Constructor
+ *
+ * @param string $id the name/id of this daemon
+ * @param int $interval sleep this long before doing everything again
+ * @param int $max_children maximum number of child processes at a time
+ * @param boolean $debug debug output flag
+ *
+ * @return void
+ *
+ **/
+
+ function __construct($id = null, $interval = 60, $max_children = 2,
+ $debug = null)
+ {
+ parent::__construct(true); // daemonize
+
+ $this->_interval = $interval;
+ $this->_max_children = $max_children;
+ $this->_debug = $debug;
+
+ if (isset($id)) {
+ $this->set_id($id);
+ }
+ }
+
+ /**
+ * Run the daemon
+ *
+ * @return void
+ */
+
+ function run()
+ {
+ if (isset($this->_debug)) {
+ echo $this->name() . " - Debugging output enabled.\n";
+ }
+
+ do {
+
+ $objects = $this->getObjects();
+
+ foreach ($objects as $o) {
+
+ // Fork a child for each object
+
+ $pid = pcntl_fork();
+
+ if ($pid == -1) {
+ die ($this->name() . ' - Couldn\'t fork!');
+ }
+
+ if ($pid) {
+
+ // Parent
+
+ if (isset($this->_debug)) {
+ echo $this->name() .
+ " - Forked new child - pid $pid.\n";
+
+ }
+
+ $this->_children[] = $pid;
+
+ } else {
+
+ // Child
+
+ // Do something with each object
+
+ $this->childTask($o);
+
+ exit();
+ }
+
+ // Remove child from ps list as it finishes
+
+ while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) {
+
+ if (isset($this->_debug)) {
+ echo $this->name() . " - Child $c finished.\n";
+ }
+
+ $this->removePs($this->_children, $c);
+ }
+
+ // Wait! We have too many damn kids.
+
+ if (sizeof($this->_children) >= $this->_max_children) {
+
+ if (isset($this->_debug)) {
+ echo $this->name() . " - Too many children. Waiting...\n";
+ }
+
+ if (($c = pcntl_wait($status, WUNTRACED)) > 0) {
+
+ if (isset($this->_debug)) {
+ echo $this->name() .
+ " - Finished waiting for child $c.\n";
+ }
+
+ $this->removePs($this->_children, $c);
+ }
+ }
+ }
+
+ // Remove all children from the process list before restarting
+ while (($c = pcntl_wait($status, WUNTRACED)) > 0) {
+
+ if (isset($this->_debug)) {
+ echo $this->name() . " - Child $c finished.\n";
+ }
+
+ $this->removePs($this->_children, $c);
+ }
+
+ // Rest for a bit
+
+ if (isset($this->_debug)) {
+ echo $this->name() . ' - Waiting ' . $this->_interval .
+ " secs before running again.\n";
+ }
+
+ if ($this->_interval > 0) {
+ sleep($this->_interval);
+ }
+
+ } while (true);
+ }
+
+ /**
+ * Remove a child process from the list of children
+ *
+ * @param array &$plist array of processes
+ * @param int $ps process id
+ *
+ * @return void
+ */
+
+ function removePs(&$plist, $ps)
+ {
+ for ($i = 0; $i < sizeof($plist); $i++) {
+ if ($plist[$i] == $ps) {
+ unset($plist[$i]);
+ $plist = array_values($plist);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get a list of objects to work on in parallel
+ *
+ * @return array An array of objects to work on
+ */
+
+ function getObjects()
+ {
+ die('Implement ParallelizingDaemon::getObjects().');
+ }
+
+ /**
+ * Do something with each object in parallel
+ *
+ * @param mixed $object data to work on
+ *
+ * @return void
+ */
+
+ function childTask($object)
+ {
+ die("Implement ParallelizingDaemon::childTask($object).");
+ }
+
+} \ No newline at end of file
diff --git a/lib/profilesection.php b/lib/profilesection.php
index 9ff243fb5..d463a07b0 100644
--- a/lib/profilesection.php
+++ b/lib/profilesection.php
@@ -97,7 +97,7 @@ class ProfileSection extends Section
$this->out->elementEnd('a');
$this->out->elementEnd('span');
$this->out->elementEnd('td');
- if ($profile->value) {
+ if (isset($profile->value)) {
$this->out->element('td', 'value', $profile->value);
}
diff --git a/lib/router.php b/lib/router.php
index 19839b997..04c6dd414 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -88,6 +88,10 @@ class Router
$m->connect('doc/:title', array('action' => 'doc'));
+ // Twitter
+
+ $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
+
// facebook
$m->connect('facebook', array('action' => 'facebookhome'));
@@ -113,15 +117,8 @@ class Router
$m->connect('main/tagother/:id', array('action' => 'tagother'));
- $m->connect('main/oembed.xml',
- array('action' => 'api',
- 'method' => 'oembed.xml',
- 'apiaction' => 'oembed'));
-
- $m->connect('main/oembed.json',
- array('action' => 'api',
- 'method' => 'oembed.json',
- 'apiaction' => 'oembed'));
+ $m->connect('main/oembed',
+ array('action' => 'oembed'));
// these take a code
@@ -409,6 +406,28 @@ class Router
'apiaction' => 'laconica'));
// Groups
+ //'list' has to be handled differently, as php will not allow a method to be named 'list'
+ $m->connect('api/laconica/groups/list/:argument',
+ array('action' => 'api',
+ 'method' => 'list_groups',
+ 'apiaction' => 'groups'));
+ foreach (array('xml', 'json', 'rss', 'atom') as $e) {
+ $m->connect('api/laconica/groups/list.' . $e,
+ array('action' => 'api',
+ 'method' => 'list_groups.' . $e,
+ 'apiaction' => 'groups'));
+ }
+
+ $m->connect('api/laconica/groups/:method',
+ array('action' => 'api',
+ 'apiaction' => 'statuses'),
+ array('method' => '(list_all|)(\.(atom|rss|xml|json))?'));
+
+ $m->connect('api/statuses/:method/:argument',
+ array('action' => 'api',
+ 'apiaction' => 'statuses'),
+ array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
+
$m->connect('api/laconica/groups/:method/:argument',
array('action' => 'api',
'apiaction' => 'groups'));
diff --git a/lib/search_engines.php b/lib/search_engines.php
index 772f41883..7c26363fc 100644
--- a/lib/search_engines.php
+++ b/lib/search_engines.php
@@ -120,7 +120,7 @@ class MySQLSearch extends SearchEngine
} else if ('identica_notices' === $this->table) {
// Don't show imported notices
- $this->target->whereAdd('notice.is_local != ' . NOTICE_GATEWAY);
+ $this->target->whereAdd('notice.is_local != ' . Notice::GATEWAY);
if (strtolower($q) != $q) {
$this->target->whereAdd("( MATCH(content) AGAINST ('" . addslashes($q) .
diff --git a/lib/servererroraction.php b/lib/servererroraction.php
index db7352166..c46f3228b 100644
--- a/lib/servererroraction.php
+++ b/lib/servererroraction.php
@@ -52,6 +52,7 @@ require_once INSTALLDIR.'/lib/error.php';
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
+
class ServerErrorAction extends ErrorAction
{
function __construct($message='Error', $code=500)
@@ -66,6 +67,10 @@ class ServerErrorAction extends ErrorAction
505 => 'HTTP Version Not Supported');
$this->default = 500;
+
+ // Server errors must be logged.
+
+ common_log(LOG_ERR, "ServerErrorAction: $code $message");
}
// XXX: Should these error actions even be invokable via URI?
diff --git a/lib/twitter.php b/lib/twitter.php
index 47af32e61..280cdb0a3 100644
--- a/lib/twitter.php
+++ b/lib/twitter.php
@@ -17,83 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('LACONICA')) { exit(1); }
-
-define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
-
-function get_twitter_data($uri, $screen_name, $password)
-{
-
- $options = array(
- CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password),
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_FAILONERROR => true,
- CURLOPT_HEADER => false,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_USERAGENT => "Laconica",
- CURLOPT_CONNECTTIMEOUT => 120,
- CURLOPT_TIMEOUT => 120,
- # Twitter is strict about accepting invalid "Expect" headers
- CURLOPT_HTTPHEADER => array('Expect:')
- );
-
- $ch = curl_init($uri);
- curl_setopt_array($ch, $options);
- $data = curl_exec($ch);
- $errmsg = curl_error($ch);
-
- if ($errmsg) {
- common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
- __FILE__);
-
- if (defined('SCRIPT_DEBUG')) {
- print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
- }
- }
-
- curl_close($ch);
-
- return $data;
-}
-
-function twitter_json_data($uri, $screen_name, $password)
-{
- $json_data = get_twitter_data($uri, $screen_name, $password);
-
- if (!$json_data) {
- return false;
- }
-
- $data = json_decode($json_data);
-
- if (!$data) {
- return false;
- }
-
- return $data;
-}
-
-function twitter_user_info($screen_name, $password)
-{
- $uri = "http://twitter.com/users/show/$screen_name.json";
- return twitter_json_data($uri, $screen_name, $password);
+if (!defined('LACONICA')) {
+ exit(1);
}
-function twitter_friends_ids($screen_name, $password)
-{
- $uri = "http://twitter.com/friends/ids/$screen_name.json";
- return twitter_json_data($uri, $screen_name, $password);
-}
+define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
function update_twitter_user($twitter_id, $screen_name)
{
$uri = 'http://twitter.com/' . $screen_name;
-
$fuser = new Foreign_user();
$fuser->query('BEGIN');
- // Dropping down to SQL because regular db_object udpate stuff doesn't seem
+ // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
// to work so good with tables that have multiple column primary keys
// Any time we update the uri for a forein user we have to make sure there
@@ -102,35 +39,14 @@ function update_twitter_user($twitter_id, $screen_name)
$qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
$qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
- $result = $fuser->query($qry);
-
- if ($result) {
- common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
- if (defined('SCRIPT_DEBUG')) {
- print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
- }
- }
+ $fuser->query($qry);
// Update the user
+
$qry = 'UPDATE foreign_user SET nickname = ';
$qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
$qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
- $result = $fuser->query($qry);
-
- if (!$result) {
- common_log(LOG_WARNING,
- "Couldn't update foreign_user data for Twitter user: $screen_name");
- common_log_db_error($fuser, 'UPDATE', __FILE__);
- if (defined('SCRIPT_DEBUG')) {
- print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - ";
- print common_log_objstring($fuser) . "\n";
- $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- print "DB_DataObject Error: " . $error->getMessage() . "\n";
- }
- return false;
- }
-
$fuser->query('COMMIT');
$fuser->free();
@@ -147,23 +63,22 @@ function add_twitter_user($twitter_id, $screen_name)
// Clear out any bad old foreign_users with the new user's legit URL
// This can happen when users move around or fakester accounts get
// repoed, and things like that.
+
$luser = new Foreign_user();
$luser->uri = $new_uri;
$luser->service = TWITTER_SERVICE;
$result = $luser->delete();
- if ($result) {
+ if (empty($result)) {
common_log(LOG_WARNING,
"Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
- if (defined('SCRIPT_DEBUG')) {
- print "Removed invalid Twitter user squatting on uri: $new_uri\n";
- }
}
$luser->free();
unset($luser);
// Otherwise, create a new Twitter user
+
$fuser = new Foreign_user();
$fuser->nickname = $screen_name;
@@ -173,21 +88,12 @@ function add_twitter_user($twitter_id, $screen_name)
$fuser->created = common_sql_now();
$result = $fuser->insert();
- if (!$result) {
+ if (empty($result)) {
common_log(LOG_WARNING,
"Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
common_log_db_error($fuser, 'INSERT', __FILE__);
- if (defined('SCRIPT_DEBUG')) {
- print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
- print common_log_objstring($fuser) . "\n";
- $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
- print "DB_DataObject Error: " . $error->getMessage() . "\n";
- }
} else {
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
- if (defined('SCRIPT_DEBUG')) {
- print "Added new Twitter user: $screen_name ($twitter_id).\n";
- }
}
return $result;
@@ -199,23 +105,20 @@ function save_twitter_user($twitter_id, $screen_name)
// Check to see whether the Twitter user is already in the system,
// and update its screen name and uri if so.
+
$fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
- if ($fuser) {
+ if (!empty($fuser)) {
$result = true;
// Only update if Twitter screen name has changed
+
if ($fuser->nickname != $screen_name) {
$result = update_twitter_user($twitter_id, $screen_name);
common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
"$fuser->id to $screen_name, was $fuser->nickname");
-
- if (defined('SCRIPT_DEBUG')) {
- print 'Updated nickname (and URI) for Twitter user ' .
- "$fuser->id to $screen_name, was $fuser->nickname\n";
- }
}
return $result;
@@ -230,119 +133,6 @@ function save_twitter_user($twitter_id, $screen_name)
return true;
}
-function retreive_twitter_friends($twitter_id, $screen_name, $password)
-{
- $friends = array();
-
- $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
- $friends_ids = twitter_friends_ids($screen_name, $password);
-
- if (!$friends_ids) {
- return $friends;
- }
-
- if (defined('SCRIPT_DEBUG')) {
- print "Twitter 'social graph' ids method says $screen_name has " .
- count($friends_ids) . " friends.\n";
- }
-
- // Calculate how many pages to get...
- $pages = ceil(count($friends_ids) / 100);
-
- if ($pages == 0) {
- common_log(LOG_WARNING,
- "Twitter bridge - $screen_name seems to have no friends.");
- if (defined('SCRIPT_DEBUG')) {
- print "$screen_name seems to have no friends.\n";
- }
- }
-
- for ($i = 1; $i <= $pages; $i++) {
-
- $data = get_twitter_data($uri . $i, $screen_name, $password);
-
- if (!$data) {
- common_log(LOG_WARNING,
- "Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
- if (defined('SCRIPT_DEBUG')) {
- print "Couldn't retrieve page $i of $screen_name's friends.\n";
- }
- continue;
- }
-
- $more_friends = json_decode($data);
-
- if (!$more_friends) {
-
- common_log(LOG_WARNING,
- "Twitter bridge - No data for page $i of $screen_name's friends.");
- if (defined('SCRIPT_DEBUG')) {
- print "No data for page $i of $screen_name's friends.\n";
- }
- continue;
- }
-
- $friends = array_merge($friends, $more_friends);
- }
-
- return $friends;
-}
-
-function save_twitter_friends($user, $twitter_id, $screen_name, $password)
-{
-
- $friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
-
- if (empty($friends)) {
- common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
- if (defined('SCRIPT_DEBUG')) {
- print "Couldn't get friends data from Twitter for $screen_name.\n";
- }
- return false;
- }
-
- foreach ($friends as $friend) {
-
- $friend_name = $friend->screen_name;
- $friend_id = (int) $friend->id;
-
- // Update or create the Foreign_user record
- if (!save_twitter_user($friend_id, $friend_name)) {
- common_log(LOG_WARNING,
- "Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
- if (defined('SCRIPT_DEBUG')) {
- print "Couldn't save $screen_name's friend, $friend_name.\n";
- }
- continue;
- }
-
- // Check to see if there's a related local user
- $flink = Foreign_link::getByForeignID($friend_id, 1);
-
- if ($flink) {
-
- // Get associated user and subscribe her
- $friend_user = User::staticGet('id', $flink->user_id);
- if (!empty($friend_user)) {
- $result = subs_subscribe_to($user, $friend_user);
-
- if ($result === true) {
- common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
- if (defined('SCRIPT_DEBUG')) {
- print("Subscribed $friend_user->nickname to $user->nickname.\n");
- }
- } else {
- if (defined('SCRIPT_DEBUG')) {
- print "$result ($friend_user->nickname to $user->nickname)\n";
- }
- }
- }
- }
- }
-
- return true;
-}
-
function is_twitter_bound($notice, $flink) {
// Check to see if notice should go to Twitter
@@ -351,7 +141,7 @@ function is_twitter_bound($notice, $flink) {
// If it's not a Twitter-style reply, or if the user WANTS to send replies.
if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
- return true;
+ return true;
}
}
@@ -360,104 +150,73 @@ function is_twitter_bound($notice, $flink) {
function broadcast_twitter($notice)
{
-
$flink = Foreign_link::getByUserID($notice->profile_id,
- TWITTER_SERVICE);
+ TWITTER_SERVICE);
if (is_twitter_bound($notice, $flink)) {
- $fuser = $flink->getForeignUser();
- $twitter_user = $fuser->nickname;
- $twitter_password = $flink->credentials;
- $uri = 'http://www.twitter.com/statuses/update.json';
+ $user = $flink->getUser();
// XXX: Hack to get around PHP cURL's use of @ being a a meta character
$statustxt = preg_replace('/^@/', ' @', $notice->content);
- $options = array(
- CURLOPT_USERPWD => "$twitter_user:$twitter_password",
- CURLOPT_POST => true,
- CURLOPT_POSTFIELDS =>
- array(
- 'status' => $statustxt,
- 'source' => common_config('integration', 'source')
- ),
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_FAILONERROR => true,
- CURLOPT_HEADER => false,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_USERAGENT => "Laconica",
- CURLOPT_CONNECTTIMEOUT => 120, // XXX: How long should this be?
- CURLOPT_TIMEOUT => 120,
-
- # Twitter is strict about accepting invalid "Expect" headers
- CURLOPT_HTTPHEADER => array('Expect:')
- );
-
- $ch = curl_init($uri);
- curl_setopt_array($ch, $options);
- $data = curl_exec($ch);
- $errmsg = curl_error($ch);
- $errno = curl_errno($ch);
-
- if (!empty($errmsg)) {
- common_debug("cURL error ($errno): $errmsg - " .
- "trying to send notice for $twitter_user.",
- __FILE__);
-
- $user = $flink->getUser();
-
- if ($errmsg == 'The requested URL returned error: 401') {
- common_debug(sprintf('User %s (user id: %s) ' .
- 'has bad Twitter credentials!',
- $user->nickname, $user->id));
-
- // Bad credentials we need to delete the foreign_link
- // to Twitter and inform the user.
-
- remove_twitter_link($flink);
-
- return true;
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
- } else {
+ $client = new TwitterOAuthClient($token->key, $token->secret);
- // Some other error happened, so we should try to
- // send again later
+ $status = null;
- return false;
- }
+ try {
+ $status = $client->statusesUpdate($statustxt);
+ } catch (OAuthClientCurlException $e) {
- }
-
- curl_close($ch);
+ if ($e->getMessage() == 'The requested URL returned error: 401') {
- if (empty($data)) {
- common_debug("No data returned by Twitter's " .
- "API trying to send update for $twitter_user",
- __FILE__);
+ $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
+ 'Twitter OAuth access token.',
+ $user->nickname, $user->id);
+ common_log(LOG_WARNING, $errmsg);
- // XXX: Not sure this represents a failure to send, but it
- // probably does
+ // Bad auth token! We need to delete the foreign_link
+ // to Twitter and inform the user.
- return false;
-
- } else {
+ remove_twitter_link($flink);
+ return true;
- // Twitter should return a status
- $status = json_decode($data);
+ } else {
- if (empty($status)) {
- common_debug("Unexpected data returned by Twitter " .
- " API trying to send update for $twitter_user",
- __FILE__);
+ // Some other error happened, so we should probably
+ // try to send again later.
- // XXX: Again, this could represent a failure posting
- // or the Twitter API might just be behaving flakey.
- // We're treating it as a failure to post.
+ $errmsg = sprintf('cURL error trying to send notice to Twitter ' .
+ 'for user %1$s (user id: %2$s) - ' .
+ 'code: %3$s message: $4$s.',
+ $user->nickname, $user->id,
+ $e->getCode(), $e->getMessage());
+ common_log(LOG_WARNING, $errmsg);
return false;
}
}
+
+ if (empty($status)) {
+
+ // This could represent a failure posting,
+ // or the Twitter API might just be behaving flakey.
+
+ $errmsg = sprint('No data returned by Twitter API when ' .
+ 'trying to send update for %1$s (user id %2$s).',
+ $user->nickname, $user->id);
+ common_log(LOG_WARNING, $errmsg);
+
+ return false;
+ }
+
+ // Notice crossed the great divide
+
+ $msg = sprintf('Twitter bridge posted notice %s to Twitter.',
+ $notice->id);
+ common_log(LOG_INFO, $msg);
}
return true;
@@ -474,22 +233,25 @@ function remove_twitter_link($flink)
if (empty($result)) {
common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
- "Foreign_link for $user->nickname (user id: $user->id)!");
+ "Foreign_link for $user->nickname (user id: $user->id)!");
common_log_db_error($flink, 'DELETE', __FILE__);
}
// Notify the user that her Twitter bridge is down
- $result = mail_twitter_bridge_removed($user);
+ if (isset($user->email)) {
+
+ $result = mail_twitter_bridge_removed($user);
- if (!$result) {
+ if (!$result) {
- $msg = 'Unable to send email to notify ' .
- "$user->nickname (user id: $user->id) " .
- 'that their Twitter bridge link was ' .
- 'removed!';
+ $msg = 'Unable to send email to notify ' .
+ "$user->nickname (user id: $user->id) " .
+ 'that their Twitter bridge link was ' .
+ 'removed!';
- common_log(LOG_WARNING, $msg);
+ common_log(LOG_WARNING, $msg);
+ }
}
}
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
index 4115d9dcb..583007208 100644
--- a/lib/twitterapi.php
+++ b/lib/twitterapi.php
@@ -188,20 +188,20 @@ class TwitterapiAction extends Action
// Enclosures
$attachments = $notice->attachments();
- $enclosures = array();
- foreach ($attachments as $attachment) {
- if ($attachment->isEnclosure()) {
- $enclosure = array();
- $enclosure['url'] = $attachment->url;
- $enclosure['mimetype'] = $attachment->mimetype;
- $enclosure['size'] = $attachment->size;
- $enclosures[] = $enclosure;
- }
- }
+ if (!empty($attachments)) {
- if (!empty($enclosures)) {
- $twitter_status['attachments'] = $enclosures;
+ $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) {
@@ -233,6 +233,24 @@ class TwitterapiAction extends Action
return $twitter_group;
}
+ function twitter_rss_group_array($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 twitter_rss_entry_array($notice)
{
$profile = $notice->getProfile();
@@ -644,6 +662,65 @@ class TwitterapiAction extends Action
}
+ function show_rss_groups($group, $title, $link, $subtitle)
+ {
+
+ $this->init_document('rss');
+
+ $this->elementStart('channel');
+ $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->twitter_rss_group_array($g);
+ $this->show_twitter_rss_item($twitter_group);
+ }
+ } else {
+ while ($group->fetch()) {
+ $twitter_group = $this->twitter_rss_group_array($group);
+ $this->show_twitter_rss_item($twitter_group);
+ }
+ }
+
+ $this->elementEnd('channel');
+ $this->end_twitter_rss();
+ }
+
+ function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
+ {
+
+ $this->init_document('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->end_document('atom');
+
+ }
+
function show_json_timeline($notice)
{
@@ -668,6 +745,52 @@ class TwitterapiAction extends Action
$this->end_document('json');
}
+ function show_json_groups($group)
+ {
+
+ $this->init_document('json');
+
+ $groups = array();
+
+ if (is_array($group)) {
+ foreach ($group as $g) {
+ $twitter_group = $this->twitter_group_array($g);
+ array_push($groups, $twitter_group);
+ }
+ } else {
+ while ($group->fetch()) {
+ $twitter_group = $this->twitter_group_array($group);
+ array_push($groups, $twitter_group);
+ }
+ }
+
+ $this->show_json_objects($groups);
+
+ $this->end_document('json');
+ }
+
+ function show_xml_groups($group)
+ {
+
+ $this->init_document('xml');
+ $this->elementStart('groups', array('type' => 'array'));
+
+ if (is_array($group)) {
+ foreach ($group as $g) {
+ $twitter_group = $this->twitter_group_array($g);
+ $this->show_twitter_xml_group($twitter_group);
+ }
+ } else {
+ while ($group->fetch()) {
+ $twitter_group = $this->twitter_group_array($group);
+ $this->show_twitter_xml_group($twitter_group);
+ }
+ }
+
+ $this->elementEnd('groups');
+ $this->end_document('xml');
+ }
+
function show_single_json_group($group)
{
$this->init_document('json');
@@ -844,9 +967,9 @@ class TwitterapiAction extends Action
$this->endXML();
}
- function show_profile($profile, $content_type='xml', $notice=null)
+ function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
{
- $profile_array = $this->twitter_user_array($profile, true);
+ $profile_array = $this->twitter_user_array($profile, $includeStatuses);
switch ($content_type) {
case 'xml':
$this->show_twitter_xml_user($profile_array);
diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php
new file mode 100644
index 000000000..b7dc4a80c
--- /dev/null
+++ b/lib/twitteroauthclient.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Class for doing OAuth calls against Twitter
+ *
+ * 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 Integration
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Class for talking to the Twitter API with OAuth.
+ *
+ * @category Integration
+ * @package Laconica
+ * @author Zach Copley <zach@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ *
+ */
+class TwitterOAuthClient extends OAuthClient
+{
+ public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
+ public static $authorizeURL = 'https://twitter.com/oauth/authorize';
+ public static $accessTokenURL = 'https://twitter.com/oauth/access_token';
+
+ /**
+ * Constructor
+ *
+ * @param string $oauth_token the user's token
+ * @param string $oauth_token_secret the user's token secret
+ *
+ * @return nothing
+ */
+ function __construct($oauth_token = null, $oauth_token_secret = null)
+ {
+ $consumer_key = common_config('twitter', 'consumer_key');
+ $consumer_secret = common_config('twitter', 'consumer_secret');
+
+ parent::__construct($consumer_key, $consumer_secret,
+ $oauth_token, $oauth_token_secret);
+ }
+
+ // XXX: the following two functions are to support the horrible hack
+ // of using the credentils field in Foreign_link to store both
+ // the access token and token secret. This hack should go away with
+ // 0.9, in which we can make DB changes and add a new column for the
+ // token itself.
+
+ static function packToken($token)
+ {
+ return implode(chr(0), array($token->key, $token->secret));
+ }
+
+ static function unpackToken($str)
+ {
+ $vals = explode(chr(0), $str);
+ return new OAuthToken($vals[0], $vals[1]);
+ }
+
+ /**
+ * Builds a link to Twitter's endpoint for authorizing a request token
+ *
+ * @param OAuthToken $request_token token to authorize
+ *
+ * @return the link
+ */
+ function getAuthorizeLink($request_token)
+ {
+ return parent::getAuthorizeLink(self::$authorizeURL,
+ $request_token,
+ common_local_url('twitterauthorization'));
+ }
+
+ /**
+ * Calls Twitter's /account/verify_credentials API method
+ *
+ * @return mixed the Twitter user
+ */
+ function verifyCredentials()
+ {
+ $url = 'https://twitter.com/account/verify_credentials.json';
+ $response = $this->oAuthGet($url);
+ $twitter_user = json_decode($response);
+ return $twitter_user;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/update API method
+ *
+ * @param string $status text of the status
+ * @param int $in_reply_to_status_id optional id of the status it's
+ * a reply to
+ *
+ * @return mixed the status
+ */
+ function statusesUpdate($status, $in_reply_to_status_id = null)
+ {
+ $url = 'https://twitter.com/statuses/update.json';
+ $params = array('status' => $status,
+ 'in_reply_to_status_id' => $in_reply_to_status_id);
+ $response = $this->oAuthPost($url, $params);
+ $status = json_decode($response);
+ return $status;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/friends_timeline API method
+ *
+ * @param int $since_id show statuses after this id
+ * @param int $max_id show statuses before this id
+ * @param int $cnt number of statuses to show
+ * @param int $page page number
+ *
+ * @return mixed an array of statuses
+ */
+ function statusesFriendsTimeline($since_id = null, $max_id = null,
+ $cnt = null, $page = null)
+ {
+
+ $url = 'https://twitter.com/statuses/friends_timeline.json';
+ $params = array('since_id' => $since_id,
+ 'max_id' => $max_id,
+ 'count' => $cnt,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->oAuthGet($url);
+ $statuses = json_decode($response);
+ return $statuses;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/friends API method
+ *
+ * @param int $id id of the user whom you wish to see friends of
+ * @param int $user_id numerical user id
+ * @param int $screen_name screen name
+ * @param int $page page number
+ *
+ * @return mixed an array of twitter users and their latest status
+ */
+ function statusesFriends($id = null, $user_id = null, $screen_name = null,
+ $page = null)
+ {
+ $url = "https://twitter.com/statuses/friends.json";
+
+ $params = array('id' => $id,
+ 'user_id' => $user_id,
+ 'screen_name' => $screen_name,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->oAuthGet($url);
+ $friends = json_decode($response);
+ return $friends;
+ }
+
+ /**
+ * Calls Twitter's /stutuses/friends/ids API method
+ *
+ * @param int $id id of the user whom you wish to see friends of
+ * @param int $user_id numerical user id
+ * @param int $screen_name screen name
+ * @param int $page page number
+ *
+ * @return mixed a list of ids, 100 per page
+ */
+ function friendsIds($id = null, $user_id = null, $screen_name = null,
+ $page = null)
+ {
+ $url = "https://twitter.com/friends/ids.json";
+
+ $params = array('id' => $id,
+ 'user_id' => $user_id,
+ 'screen_name' => $screen_name,
+ 'page' => $page);
+ $qry = http_build_query($params);
+
+ if (!empty($qry)) {
+ $url .= "?$qry";
+ }
+
+ $response = $this->oAuthGet($url);
+ $ids = json_decode($response);
+ return $ids;
+ }
+
+}
diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php
index 34c7188b2..920313c0c 100644
--- a/lib/unqueuemanager.php
+++ b/lib/unqueuemanager.php
@@ -79,7 +79,7 @@ class UnQueueManager
function _isLocal($notice)
{
- return ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
- $notice->is_local == NOTICE_LOCAL_NONPUBLIC);
+ return ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC);
}
} \ No newline at end of file
diff --git a/lib/util.php b/lib/util.php
index 0402e2401..3fa90d31a 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -412,73 +412,43 @@ function common_render_text($text)
function common_replace_urls_callback($text, $callback, $notice_id = null) {
// Start off with a regex
$regex = '#'.
- '(?:'.
+ '(?:^|\s+)('.
'(?:'.
'(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://'.
'|'.
'(?:mailto|aim|tel|xmpp):'.
+ ')?'.
+ '(?:'.
+ '(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4
+ '|(?:'.
+ '(?:[0-9a-f]{1,4}:){1,1}(?::[0-9a-f]{1,4}){1,6}|'. //IPv6
+ '(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}|'.
+ '(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}|'.
+ '(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}|'.
+ '(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}|'.
+ '(?:[0-9a-f]{1,4}:){1,6}(?::[0-9a-f]{1,4}){1,1}|'.
+ '(?:(?:[0-9a-f]{1,4}:){1,7}|:):|'.
+ ':(?::[0-9a-f]{1,4}){1,7}|'.
+ '(?:(?:(?:[0-9a-f]{1,4}:){6})(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})|'.
+ '(?:(?:[0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})|'.
+ '(?:[0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'.
+ '(?:[0-9a-f]{1,4}:){1,1}(?::[0-9a-f]{1,4}){1,4}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'.
+ '(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,3}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'.
+ '(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,2}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'.
+ '(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,1}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'.
+ '(?:(?:[0-9a-f]{1,4}:){1,5}|:):(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'.
+ ':(?::[0-9a-f]{1,4}){1,5}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'.
+ ')|'.
+ '(?:[^.\s/:]+\.)+'. //DNS
+ '(?:museum|travel|onion|[a-z]{2,4})'.
')'.
- '[^.\s]+\.[^\s]+'.
- '|'.
- '(?:[^.\s/:]+\.)+'.
- '(?:museum|travel|[a-z]{2,4})'.
'(?:[:/][^\s]*)?'.
')'.
'#ix';
preg_match_all($regex, $text, $matches);
-
// Then clean up what the regex left behind
$offset = 0;
- foreach($matches[0] as $orig_url) {
- $url = htmlspecialchars_decode($orig_url);
-
- // Make sure we didn't pick up an email address
- if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue;
-
- // Remove surrounding punctuation
- $url = trim($url, '.?!,;:\'"`([<');
-
- // Remove surrounding parens and the like
- preg_match('/[)\]>]+$/', $url, $trailing);
- if (isset($trailing[0])) {
- preg_match_all('/[(\[<]/', $url, $opened);
- preg_match_all('/[)\]>]/', $url, $closed);
- $unopened = count($closed[0]) - count($opened[0]);
-
- // Make sure not to take off more closing parens than there are at the end
- $unopened = ($unopened > mb_strlen($trailing[0])) ? mb_strlen($trailing[0]):$unopened;
-
- $url = ($unopened > 0) ? mb_substr($url, 0, $unopened * -1):$url;
- }
-
- // Remove trailing punctuation again (in case there were some inside parens)
- $url = rtrim($url, '.?!,;:\'"`');
-
- // Make sure we didn't capture part of the next sentence
- preg_match('#((?:[^.\s/]+\.)+)(museum|travel|[a-z]{2,4})#i', $url, $url_parts);
-
- // Were the parts capitalized any?
- $last_part = (mb_strtolower($url_parts[2]) !== $url_parts[2]) ? true:false;
- $prev_part = (mb_strtolower($url_parts[1]) !== $url_parts[1]) ? true:false;
-
- // If the first part wasn't cap'd but the last part was, we captured too much
- if ((!$prev_part && $last_part)) {
- $url = mb_substr($url, 0 , mb_strpos($url, '.'.$url_parts['2'], 0));
- }
-
- // Capture the new TLD
- preg_match('#((?:[^.\s/]+\.)+)(museum|travel|[a-z]{2,4})#i', $url, $url_parts);
-
- $tlds = array('ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', 'zw');
-
- if (!in_array($url_parts[2], $tlds)) continue;
-
- // Make sure we didn't capture a hash tag
- if (strpos($url, '#') === 0) continue;
-
- // Put the url back the way we found it.
- $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
-
+ foreach($matches[1] as $url) {
// Call user specified func
if (empty($notice_id)) {
$modified_url = call_user_func($callback, $url);
@@ -895,8 +865,8 @@ function common_enqueue_notice($notice)
$transports[] = 'jabber';
}
- if ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
- $notice->is_local == NOTICE_LOCAL_NONPUBLIC) {
+ if ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC) {
$transports = array_merge($transports, $localTransports);
if ($xmpp) {
$transports[] = 'public';